Skip to main content

Autorev 1 picoCTF 2026 Solution

The server streams twenty binaries a second apart, so only an automated decompile-and-extract solver keeps up.

Published: March 20, 2026Updated: September 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 1Understand the binary structure
    Observation
    Each round the server sends a hex-encoded binary and wants the secret back within a second. Learn where the secret is stored in that binary before writing any 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: mov DWORD PTR [rbp-0x8], 0x<HEXVALUE>   (the immediate is the secret)
    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 a second per round leaves no time to look. The solver has to read the value out of the disassembly rather than run the binary, because running it in a loop costs enough time to blow the deadline.

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

    The secret is a 4-byte integer immediate inside a mov instruction, not a printable string. strings only finds null-terminated character sequences, so a numeric constant never shows up. Decoding the instruction bytes takes objdump or capstone.

    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 = 500775970 decimal.

  2. Step 2Write an automated solver
    Observation
    One second a round across 20 rounds rules out doing this by hand. Script it with pwntools and capstone, disassembling in process and reading the immediate out of the rbp-relative mov without ever 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 means writing the binary to disk and spawning a subprocess, then parsing its output. Two process spawns per round against a one-second budget times out reliably by round five or six. Capstone works in process on the bytes you already have.

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

    The compiler puts the secret at either rbp-0x4 or rbp-0x8 depending on the variant. A regex anchored to one offset quietly returns zero on the rounds that do not match, and those answers are wrong with no visible error. Use an alternation covering both.

    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. In raw bytes the instruction is c7 /0 imm32: the ModR/M byte 45 selects [rbp + disp8], and the displacement byte is the two's-complement offset, so c7 45 f8 writes to [rbp-8] and c7 45 fc writes to [rbp-4]. Grepping the bytes for either prefix is a viable fallback if the textual regex misses.

    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 a disassembly library like Capstone to parse code programmatically, pulling out constants, addresses, and immediates with no human in the loop. Receive a binary, disassemble, extract, respond: that loop is the backbone of automated solvers and of real analysis pipelines for firmware triage, malware unpacking, and vulnerability research at scale. Recognizing recurring compiler idioms is what makes the extraction hold across generated variants.

Related reading

Useful tools for Reverse Engineering

Where to go next