Autorev 1 picoCTF 2026 Solution

Published: March 20, 2026

Description

You think you can reverse engineer? Let's test out your speed. Connect to the server - it sends you 20 binary files one at a time (1 second each) and you must extract the secret from each one.

Launch the challenge instance and connect via netcat.

The server sends a large hex-encoded binary, then prompts for the secret. You have 1 second per binary.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Understand the binary structure
    Observation
    I noticed the server sends a hex-encoded binary per round and prompts for a secret within 1 second, which suggested I needed to understand the binary's structure and find where the hardcoded secret is stored before writing any automated solver.
    Redirect the server output to a file. Convert the hex to binary. Disassemble it and look at main(). The binary actually prints the secret value to stdout before prompting (one verified approach observed this debug leak but used the disassembly route instead). The binary then prints "What's the secret?", reads your answer with scanf, and compares it against a hardcoded value. The secret is the immediate value stored via a 'mov' instruction referencing rbp.
    bash
    nc <HOST> <PORT_FROM_INSTANCE> | head -1 | xxd -r -p > /tmp/binary
    bash
    chmod +x /tmp/binary
    bash
    objdump -d /tmp/binary | grep -A5 'main'
    bash
    # Look for: cmp DWORD PTR [rbp-0x8], 0x<HEXVALUE>
    What didn't work first

    Tried: Run the binary directly and read what it prints to stdout, hoping the debug leak gives the secret

    The binary does print the secret before prompting, but you only have 1 second per round and 20 rounds, so manual inspection is impossible at that speed. The automated solver must extract the value from the disassembly without executing the binary, because execution in a loop introduces timing overhead that causes timeouts.

    Tried: Use 'strings /tmp/binary' to find the hardcoded secret value

    The secret is stored as a 4-byte integer immediate in a 'mov' instruction, not as a printable ASCII string. 'strings' only surfaces null-terminated character sequences, so a numeric constant like 499839010 never appears in its output. Disassembly with objdump or capstone is needed to decode the raw instruction bytes into the immediate value.

    Learn more

    The binary's main function prints a prompt, then reads your answer with scanf, and compares it against a hardcoded value in the form cmp [rbp-4], eax or cmp [rbp-8], imm.

    The secret value appears as a hex immediate in the disassembly. Converting from hex to decimal gives the answer. For example, 0x1DD93C22 = 499839010 decimal.

  2. Step 2
    Write an automated solver
    Observation
    I noticed the 1-second-per-round and 20-round constraint made manual inspection impossible, which suggested writing an automated Python script using pwntools and capstone to disassemble in-process and extract the immediate value from the rbp-relative 'mov' instruction without touching disk.
    Write a Python script that connects, receives each hex-encoded binary string, disassembles the binary bytes using a library such as capstone, then applies a regex to the disassembly text to find the 'mov' instruction that stores an immediate into an rbp-relative slot (e.g. [rbp-0x8]). Send the decimal value of that immediate back. Repeat for all 20 rounds.
    bash
    pip install pwntools
    python
    python3 << 'EOF'
    from pwn import *
    import re, struct
    
    r = remote("<HOST>", <PORT_FROM_INSTANCE>)
    
    for _ in range(20):
        # Receive until "bytes" appears in the prompt
        r.recvuntil(b"bytes")
        hex_data = r.recvline().strip()
        binary = bytes.fromhex(hex_data.decode())
    
        # Disassemble and search for the mov instruction storing the secret
        # One approach: disassemble with capstone, then regex for the immediate
        from capstone import Cs, CS_ARCH_X86, CS_MODE_64
        md = Cs(CS_ARCH_X86, CS_MODE_64)
        secret = 0
        for insn in md.disasm(binary, 0x0):
            # Look for mov targeting rbp-0x8 (or rbp-0x4) with an immediate value
            m = re.search(r'mov dword ptr \[rbp - (?:8|4|0x8|0x4)\], (0x[0-9a-f]+|\d+)', insn.mnemonic + ' ' + insn.op_str)
            if m:
                secret = int(m.group(1), 0)
                break
    
        r.recvuntil(b"secret")
        r.sendline(str(secret).encode())
    
    print(r.recvall(timeout=5).decode())
    EOF

    Expected output

    picoCTF{4u7o_r3v_g0_brrr_...}
    What didn't work first

    Tried: Use objdump inside the Python loop instead of capstone to extract the immediate

    objdump requires writing the binary to disk with a subprocess call, then parsing its stdout. Under the 1-second-per-round constraint this adds two process-spawn overheads per iteration and reliably causes timeouts by round 5 or 6. Capstone operates purely in-process on the bytes already in memory, making it fast enough to finish extraction well within the time window.

    Tried: Match only 'mov dword ptr [rbp - 4]' in the regex and miss rounds where the slot is [rbp - 8]

    The server generates binaries where the compiler may place the secret in either [rbp-0x4] or [rbp-0x8] depending on the variant. A regex anchored to a single offset silently returns 0 for mismatched rounds, causing those answers to be wrong without any visible error. The correct pattern must use an alternation covering both offsets, e.g. '(rbp - (?:8|4|0x8|0x4))'.

    Learn more

    One approach: disassemble the binary bytes with a library like capstone, then apply a regex to the mnemonic output looking for a mov dword ptr [rbp - 8] (or [rbp - 4]) instruction that carries an immediate value. The opcode for mov [rbp-8] is c7 45 f8, while mov [rbp-4] is c7 45 fc. That approach describes the key instruction as moving a value into [rbp-8], so the correct opcode prefix may be c7 45 f8, not c7 45 fc.

    All 20 rounds share this structure with only the immediate (the secret) differing. Converting the extracted immediate to a decimal string and sending it back earns a point for that round.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
  • File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.

Flag

Reveal flag

picoCTF{4u7o_r3v_g0_brrr_...}

The server sends 20 hex-encoded binaries. Each contains a hardcoded secret in a 'mov DWORD PTR [rbp-0x8], imm' (or similar rbp-relative) instruction. Disassemble with capstone, extract the immediate via regex, and send the decimal result back. Repeat 20 times to get the flag.

Key takeaway

Scripted reverse engineering uses disassembly libraries like Capstone to parse binary code programmatically, extracting values (constants, addresses, immediates) without human inspection. The pattern of receive-binary, disassemble, extract-value, respond is the backbone of automated CTF solvers and also appears in real-world binary analysis pipelines for firmware triage, malware unpacking, and vulnerability research at scale. Recognizing recurring compiler idioms (rbp-relative stack slots, immediate comparisons) is what makes the extraction regex reliable across generated variants.

Related reading

Want more picoCTF 2026 writeups?

Useful tools for Reverse Engineering

What to try next