Binary Instrumentation 2 picoCTF 2025 Solution

Published: April 2, 2025

Description

A Windows binary claims to write the flag to flag.txt, but the file path is wrong and WriteFile seems to write only one byte. Use Frida to hook CreateFile and WriteFile, fix the path, and intercept the flag data before it is written.

Unzip with the password picoctf and confirm the binary with file bininst2.exe.

Run bininst2.exe to see it runs but produces no visible output and no flag.txt.

Install Frida (pip install frida-tools) on a Windows machine.

Use frida-trace -i CreateFile -i WriteFile bininst2.exe to auto-generate handler stubs, then edit those handlers to intercept and fix the calls.

bash
pip install frida-tools
bash
# Auto-generate handler stubs:
bash
frida-trace -i CreateFile -i WriteFile bininst2.exe
bash
# This creates a __handlers__ folder with JS files for each function
  1. Step 1Hook CreateFile to fix the path
    The auto-generated CreateFile handler shows the binary is passing <insert path here> as the filename - a literal placeholder that will always fail. Edit the CreateFileW handler to print the filename argument and replace it with flag.txt so the file can be created.
    js
    // In __handlers__/kernel32.dll/CreateFileW.js
    onEnter(log, args, state) {
      log("CreateFileW called, filename: " + args[0].readUtf16String());
      // Replace the broken path with flag.txt
      var newPath = Memory.allocUtf16String("flag.txt");
      this.newPath = newPath;  // keep a reference so it stays alive
      args[0] = newPath;
    },
    Learn more

    frida-trace is a command-line tool that auto-generates Frida handler stubs for named functions. Running frida-trace -i CreateFile -i WriteFile bininst2.exe spawns the process and creates JavaScript files in a __handlers__ folder - one per intercepted function. The stubs just log calls initially; you edit them to add custom logic like reading or replacing arguments.

    The args[0].readUtf16String() call reads the first argument to CreateFileW as a wide (UTF-16) string, which is how Windows API functions expect string parameters. Setting args[0] = newPath replaces the pointer with a new allocation, redirecting the file creation to the correct path. The this.newPath assignment keeps a JavaScript reference alive so the native memory is not garbage-collected before the function finishes.

  2. Step 2Hook WriteFile to capture the flag data
    The WriteFile call has a bug where it writes only one byte. Hook WriteFile to print the buffer before it is written. The buffer contains the Base64-encoded flag.
    js
    // In __handlers__/kernel32.dll/WriteFile.js
    onEnter(log, args, state) {
      var buf = args[1];
      var len = args[2].toInt32();
      log("WriteFile buffer: " + buf.readUtf8String(len));
    },
    bash
    # Re-run frida-trace with the edited handlers:
    bash
    frida-trace -i CreateFile -i WriteFile bininst2.exe
    Learn more

    The WriteFile Windows API takes a handle, a buffer pointer, a byte count, and an output pointer for bytes written. When the byte count is wrong (here, only 1), the file is created but mostly empty. Hooking the function and printing the buffer contents at call time shows the full data the binary intended to write, even though the write itself fails to store it correctly.

    This technique of intercepting file writes to capture data before it lands on disk is a standard malware analysis technique. Encrypted ransomware, for example, passes plaintext data through WriteFile before encrypting it to disk; hooking WriteFile lets an analyst recover the original files.

  3. Step 3Decode the Base64 flag
    The WriteFile buffer contains a Base64 string. Decode it to recover the picoCTF flag.
    bash
    # The buffer output will be something like:
    bash
    # cGljb0NURntmcjFkYV9mMHJfYjFuX2luNXRydW0zbnQ0dGlvbiF9
    bash
    echo 'cGljb0NURntmcjFkYV9mMHJfYjFuX2luNXRydW0zbnQ0dGlvbiF9' | base64 -d
    Learn more

    The binary encodes the flag as Base64 before writing it, which is why the file would be unreadable even if WriteFile worked correctly. By intercepting the buffer in the WriteFile hook, you capture the encoded bytes and can decode them directly without needing to fix the write itself.

Flag

picoCTF{fr1da_f0r_b1n_in5trum3nt4tion!_b21a...}

Use frida-trace to auto-generate handler stubs, edit CreateFile to fix the filename and WriteFile to print the buffer, then decode the Base64 output.

Want more picoCTF 2025 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next