Solving rop32 challenge
You can download the original file here.
This challenge was a linux executable
with the following source code in PicoCTF:
The vulnerability resides in the use of gets()
which is very well known for being suceptible of buffer overflows
.
Below we can see that the stack is not executable and the NX bit
is enabled:
As we can see, the NX bit
is enabled, because of this we cannot execute code in the stack. Therefore using ROP makes totally sense.
In the end, we will try to get our shell using only rop gadgets
.
The next step is to find the 16 bytes offset
of buf
local variable in the vuln()
function, and ultimately overwrite the return address at the current function’s stack frame
and gain control over eip
.
Using cyclic
to gain time is useful. cyclic
is a string sequence generator which is a De Bruijn function wrapper.
Step by step, we ask to cyclic
a string of 32 bytes. There is no particular reason that I choosed a 32 bytes string for cyclic
but is big enough to overwrite eip
. Copying the resulting string to the program input overflows the buffer overwriting the Instruction Pointer
.
The overwritten eip
renders the value 0x61616168
, converted to string is haaa
and returning with the result to cyclic
finally we obtain 28
as the offset.
x86 - 4 bytes pointers
ESP +---------> +-------------------+
| local vars/etc | 20 bytes (offset 0) +
EBP +---------> +-------------------+ |
| EBP | 4 bytes (offset 24) |
+-------------------+ | cyclic string
| EIP / RET | 4 bytes (offset 28) |
+-------------------+ |
| arguments | 4 bytes (offset 32) +
+-------------------+
The state of the stack before the leave
instruction in vuln()
is the following:
pwndbg> x/12x $esp
0xffffcce0: 0x61616161 0x61616162 0x61616163 0x61616164
0xffffccf0: 0x61616165 0x61616166 0x61616167 0x61616168
0xffffcd00: 0x00000000 0x0804f02b 0x080da000 0x000003e8
And in the ret
instruction, the stack state changes to the following:
pwndbg> x/12x $esp
0xffffccfc: 0x61616168 0x00000000 0x0804f02b 0x080da000
0xffffcd0c: 0x000003e8 0xffffcd30 0x080da000 0x00000000
0xffffcd1c: 0x08048f6f 0x0806f0af 0x080da000 0x080da000
After gathering all this information about the behaviour of the program, we start to write our exploit. In ROP the idea is that instead of writing code in the buffer and jump to it, what we do is manipulate the stack frame
in a way that we inject valid memory addresses that reside within the original binary. The program ROPGadget reinterprets a binary mangling with the offsets in a way that it finds gadgets
to use with the property of a chain of instructions that ends in ret
, call
, int 0x80
or syscall
.
$ ROPgadget --depth=3 --binary ./vuln |grep 'pop .* ; ret'
[...]
0x080a8e36 : pop eax ; ret
0x0805c524 : pop eax ; ret 0xfffe
0x080c249f : pop eax ; retf
0x080c0c44 : pop ebp ; daa ; retf 0xd1cb
0x0804834c : pop ebp ; ret
0x0805bf9f : pop ebp ; ret 0xffff
0x0805d8b2 : pop ebp ; ret 4
0x080a1dcb : pop ebp ; ret 8
[...]
For instance, several gadgets
in this binary allow us to manipulate the processor registers in a way that we can inject values to the registers mangling with the stack
in a clever way. pop eax; ret
allows us to inject the value 0x41414141
into eax
using the gadget located at 0x080a8e36
in the binary:
S T A C K
+----------------------+
| 0x080a8e36 | <-------+ pop eax ; ret
+----------------------+
| 0x41414114 | <-------+ eax <- 0x41414141
+----------------------+
| Next RET |
+----------------------+
This way we can manipulate the execution flow of the program modifiying the ret
addresses.
Exploit
The goal is to execute the execve
linux syscall using only a rop chain
starting by our controlled eip
to gain a shell in the server by executing /bin/sh
.
The binary in the server that we want to exploit has the SUID
flag enabled and with reading permissions for flag.txt
which is the resource that we want to read. The exploit executes /bin//sh
, and after exploitation we can issue commands to the server.
While writing the exploit, I’ve encountered a problem that the final string to pass as input to the vulnerable program was “cut at half”. This was because of this gadget:
0x080a8e36 : pop eax ; ret
The problem with the memory address 0x080a8e36
is that the byte 0x0a
is present in the address and in the context of passing the input by console, it is interpreted as the newline character
, executing the input but in half.
That is the reason that instead of using pop eax; ret
I choosed the following gadget
to avoid this problem:
#0x080a8e36 : pop eax ; ret # bad bytes in the address
#0x08056334 : pop eax ; pop edx ; pop ebx ; ret # this gadget address doesn't contains 0x0a
pop_eax = p32(0x080a8e36)
*pop_eax_3 = p32(0x08056334) # watch out, this destroys edx and ebx*
For this reason, I need to take special care when loading values to the eax
register with pop_eax_3
- I called it like that to remind me also that 3
registers are involved in this gadget - because it also loads values to edx
and ebx
destroying the state of my registers.
This is the final exploit:
Here is the flag:
picoCTF{rOp_t0_b1n_sH_cb4c373e}
Solving rop64 challenge
The steps to write the exploit of this challenge are nearly the same as the rop32
one but:
- The
execve
syscall parameters are passed by registers and not by stack - We have enough space to write the string
/bin/sh\x00
in a register in one step, and thanks to that theropchain
is a little bit less complex - In 64 bits we should not call
int 0x80
, we should usesyscall
instruction instead
This is the exploit:
And the flag picoCTF{rOp_t0_b1n_sH_w1tH_n3w_g4dg3t5_11cdd436}