Skip to main content

B1ll_Gat35 picoCTF 2019 Solution

Reverse a Java binary with a complex license key check and satisfy all the required conditions.

Published: April 2, 2026Updated: August 25, 2026

Description

A challenge about Windows internals. Analyze the Windows executable to find the flag.

Download the executable.

bash
wget <url>/B1ll_Gat35.exe

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Examine the PE binary
    Observation
    The challenge hands you a Windows .exe. Start cheap: file to confirm the binary type, strings to check for plaintext secrets, before committing time to a decompiler.
    Run file and strings on the .exe file. Identify the executable type (32-bit vs 64-bit, .NET or native). Native PE files can be analyzed in Ghidra or Radare2.
    bash
    file B1ll_Gat35.exe
    bash
    strings B1ll_Gat35.exe | head -50
    bash
    strings B1ll_Gat35.exe | grep -i pico

    Expected output

    PICOCTF{These are the access codes to the vault: 1063340}
    What didn't work first

    Tried: Run 'strings B1ll_Gat35.exe | grep -i flag' expecting a flag-shaped line and find nothing.

    The flag format here is PICOCTF{...} (all caps, non-standard) rather than picoCTF{...}, so a case-insensitive grep for 'flag' misses it entirely. Grep for 'pico' (case-insensitive) or look at the raw strings output and scan for curly braces to catch non-standard capitalization.

    Tried: Open the .exe in a hex editor and search for 'MZ' expecting to find .NET metadata tables like #Strings or #US, then pull the flag from there.

    If file reports a native PE rather than a .NET assembly, there are no managed metadata streams, only raw section data. The readable flag string sits in .rdata where strings finds it, not in IL metadata tables. Save the dnSpy path for binaries that file reports as CIL/.NET.

    Learn more

    PE (Portable Executable) is Windows' binary format, analogous to ELF on Linux. A PE file has a DOS header (starts with MZ), a PE header, section headers, and sections including .text (code), .data (initialized data), .rdata (read-only data, where strings live), and .rsrc (resources).

    If it is a .NET assembly, use dnSpy or dotPeek for decompilation. If it is a native binary, use Ghidra, x64dbg, or IDA.

  2. Step 2Load in Ghidra or run in Wine
    Observation
    strings turns up a flag-shaped string but nothing about the password comparison. Loading the binary in Ghidra should show both the expected key value and how the check works.
    On Linux, use Ghidra for static analysis. If you need dynamic analysis, run the executable under Wine. On Windows, use x64dbg for dynamic debugging.
    bash
    ghidra B1ll_Gat35.exe &
    bash
    # Or with Wine:
    bash
    wine B1ll_Gat35.exe
    What didn't work first

    Tried: Use objdump -d B1ll_Gat35.exe on Linux to disassemble and look for the password comparison.

    objdump can disassemble PE files but does not resolve Windows API symbols or imports, so a call to lstrcmpA shows up as an unlabeled indirect call. Ghidra's auto-analysis resolves the IAT and annotates both pointer arguments, which makes the expected string obvious.

    Tried: Run Wine immediately without Ghidra to observe runtime behavior and enter guesses interactively.

    Wine on Linux may be missing the Windows runtime DLLs the program needs, so it crashes before the password prompt appears. Static analysis in Ghidra finds the expected string without a working Wine setup, and beats guessing at a runtime prompt.

    Learn more

    Ghidra handles PE files natively on any OS. After auto-analysis, navigate to the main function (search for the entry point in the Symbol Tree). The Windows API calls (MessageBox, CreateFile, etc.) provide context for understanding what the program does.

  3. Step 3Find the password check and extract the flag
    Observation
    Ghidra's decompilation shows an lstrcmpA call with one argument pointing at a literal starting 'The key is:'. So the program wants that whole string verbatim, not a bare number, and patching the conditional jump would skip the check altogether.
    The program prompts for a number and then a key, and a check function compares your input against an expected string. The catch: the expected input is the full string 'The key is: 4253360', not the bare number 4253360, so entering just the number fails the comparison. Enter the complete string to pass the check (or patch the conditional jump so the success branch prints the flag regardless).
    bash
    # Enter exactly:  The key is: 4253360
    bash
    # Alternatively, in x64dbg/Ghidra patch the JNZ after the strcmp to fall through to the success branch.
    What didn't work first

    Tried: Enter just the number 4253360 at the key prompt after seeing it in Ghidra's decompilation.

    The lstrcmpA call compares your input against the full string 'The key is: 4253360', not the bare number. Type only 4253360 and the comparison returns non-zero, so the jump takes the failure branch. The 'The key is: ' prefix has to be typed too.

    Tried: Patch the JE instruction after the comparison to JMP (unconditional jump) so the success branch always runs.

    There is usually no JE to convert: the compiler emits the mismatch jump instead (test eax, eax followed by JNZ to the failure block), so the JE you think you are looking at is the failure jump under another name. What you want is that JNZ replaced with NOPs, or with a short JMP that lands inside the success block. Patch the wrong jump and the failure path runs every time.

    Learn more

    Windows API functions for string comparison: lstrcmpA (ANSI), lstrcmpW (Unicode/Wide). In Ghidra these appear as calls with two pointer arguments; one is your input and one is the expected value. Here the expected value is the literal The key is: 4253360, which is easy to misread as just the number.

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.

Flag

Reveal flag

PICOCTF{These are the access codes to the vault: 1063340}

The check compares your key input against the full string 'The key is: 4253360' (not the bare number). Enter that exact string, or patch the conditional jump, to reach the success branch. Note the flag uses the non-standard PICOCTF{...} format.

Key takeaway

Compiled password checks keep the expected value in the binary's read-only data section, where strings reads it directly. Static analysis tools like Ghidra put both sides of the comparison on screen at once, turning a runtime secret into a static artifact. The same holds for license-key validators, mobile API secrets, and anti-cheat checks: a secret shipped inside a binary is not a secret, because the attacker owns the binary and can read or patch it.

Related reading

Useful tools for Reverse Engineering

Where to go next