This challenge contains a buffer overflow vulnerability again, however this time we don't have many functions to play with in the PLT/GOT.
First let's check the protections of the binary using checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
We see that nothing except NX is enabled. This is different compared to the previous challenges that had Full RELRO, this challenge only has Partial RELRO, meaning there's possibly some GOT overwriting we need to do in order to win.
Then I loaded the binary in Ghidra and it was very minimal, essentially calling just the vuln()
function.
void vuln(void)
{
undefined local_48 [64];
read(0,local_48,200);
return;
}
Here we see the buffer overflow vulnerability, where read()
reads 200 bytes which is over the defined size 64.
After the contest I have seen that this is supposed to be solved using the ret2dlresolve
technique, however at the time of the contest I was unaware of this technique, therefore I have yet again decided to leak libc and aim for an arbitrary code execution exploit.
After trying and failing at this task, I realized that I don't necessarily need to leak the libc base address, it is also enough if I can find a gadget to offset the current read()
address in the GOT to call something useful.
The idea is as follows:
- Offset read GOT by some bytes to point to a one_gadget
- Call
vuln()
again which will callread@plt
that will now point to our one_gadget instead.
A one_gadget is a location in libc that will execute a shell for us.
This means that we don't have to actually setup the full ROP chain and find out where the /bin/sh
strings is.
The small caveat is that some restrictions apply in order to make these gadgets work, which is often some variables or memory locations being 0.
To find one gadgets we can use the one_gadget
program (from the community/one_gadget
package in archlinux)
➜ void one_gadget glibc/libc.so.6
egrep: warning: egrep is obsolescent; using grep -E
egrep: warning: egrep is obsolescent; using grep -E
egrep: warning: egrep is obsolescent; using grep -E
egrep: warning: egrep is obsolescent; using grep -E
0xc961a execve("/bin/sh", r12, r13)
constraints:
[r12] == NULL || r12 == NULL
[r13] == NULL || r13 == NULL
0xc961d execve("/bin/sh", r12, rdx)
constraints:
[r12] == NULL || r12 == NULL
[rdx] == NULL || rdx == NULL
0xc9620 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
Here we have our gadgets and the constraints, however we still need to figure out how to make this appear in the GOT instead of read()
Here I used the ROPGadget
command (from the community/ropgadget
in archlinux) to find some gadgets in the binary.
Here I have found many gadgets with the add
instructions, however not many of them were particularly useful to me, either the written data was uncontrollable or I haven't had control over the register holding the destination.
However a gadget did seem promising if I could control the involved registers:
add dword ptr [rbp - 0x3d], ebx ; nop dword ptr [rax + rax] ; ret
This will perform an addition a nop and then return, perfect for us if we control rbp
and ebx
.
Now we only need to control rbp
and ebx
, which can be done using ret2csu
.
Specifically I will jump to the following section:
004011b2 5b POP RBX
004011b3 5d POP RBP
004011b4 41 5c POP R12
004011b6 41 5d POP R13
004011b8 41 5e POP R14
004011ba 41 5f POP R15
004011bc c3 RET
This will allow us the set rbp
and rbx
, furthermore by zeroing r12
and r13
we can make the first gadget returned by one_gadget
work.
Therefore the process will look like this:
- Setup
rbp
andrbx
to targetread@got
and add offset from one_gadget to read - Call the
add
gadget - Call
vuln()
again
The payload will have the following layout:
[8 - CSU gadget address] + [8 - rbx overwrite, the offset] + [8 - pointer to read@got] + [32 - zero out r12-r15] + [8 - address of memory add gadget] + [8 - address of vuln]
Running solve.py
will get us a shell, which we can use to get the flag.