Description
A Windows binary claims to write the flag to flag.txt, but the file path is wrong and WriteFile writes zero bytes because nNumberOfBytesToWrite is 0. 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.
pip install frida-tools# Auto-generate handler stubs:frida-trace -i CreateFile -i WriteFile bininst2.exe# This creates a __handlers__ folder with JS files for each functionSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Hook CreateFile to fix the pathObservationI noticed that running bininst2.exe produced no flag.txt and no visible output, which suggested the file creation was failing; inspecting the CreateFileW argument via frida-trace revealed a literal placeholder path, pointing to the need to intercept and replace the filename argument before the call proceeds.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 withflag.txtso 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; },What didn't work first
Tried: Reading args[0] with readUtf8String() instead of readUtf16String()
Windows API W-suffix functions (CreateFileW) pass strings as UTF-16 wide characters. readUtf8String() treats each byte independently, so most characters appear garbled or empty and the path printed is meaningless. readUtf16String() reads two bytes per character, matching the actual encoding Windows uses.
Tried: Skipping the this.newPath assignment and only setting args[0] = newPath inside onEnter
Memory.allocUtf16String() returns a NativePointer backed by a Frida-managed allocation. Without a persistent reference on the JavaScript side, the garbage collector may free the buffer before CreateFileW reads it, causing a crash or a corrupted path. Storing the pointer in this.newPath keeps the allocation live for the duration of the call.
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.exespawns 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. Settingargs[0] = newPathreplaces the pointer with a new allocation, redirecting the file creation to the correct path. Thethis.newPathassignment keeps a JavaScript reference alive so the native memory is not garbage-collected before the function finishes.Step 2
Hook WriteFile to capture the flag dataObservationI noticed that even after fixing the CreateFile path, no data appeared in flag.txt, which suggested the WriteFile call itself was broken; the description stated nNumberOfBytesToWrite was 0, pointing to the need to read the buffer directly from the Frida hook before the write attempted.The WriteFile call has a bug where nNumberOfBytesToWrite is 0, so no bytes are written at all even though the buffer contains the full flag. 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:bashfrida-trace -i CreateFile -i WriteFile bininst2.exeWhat didn't work first
Tried: Reading the WriteFile buffer with readUtf16String() instead of readUtf8String()
WriteFile is the non-Unicode variant and the buffer it receives is raw bytes (here a Base64 ASCII string), not a wide UTF-16 string. readUtf16String() pairs bytes together and interprets them as UTF-16 code points, producing garbled characters and cutting the output short. readUtf8String(len) reads exactly len bytes as ASCII/UTF-8, which matches the actual buffer encoding.
Tried: Printing args[2].toInt32() to verify the byte count before reading the buffer
The bug in the binary is that args[2] (nNumberOfBytesToWrite) is 0 at the time of the call, which is why nothing is written to disk. Reading args[2] first confirms it is 0, but using that zero as the length argument to readUtf8String(len) returns an empty string. You must hardcode a known length or read until a null terminator instead of trusting the broken argument.
Learn more
The
WriteFileWindows API takes a handle, a buffer pointer, a byte count, and an output pointer for bytes written. When the byte count is zero, the file is created but completely 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.
Step 3
Decode the Base64 flagObservationI noticed the buffer content printed by the WriteFile hook was a long alphanumeric string with no special characters and a length divisible by 4, which are characteristic signs of Base64 encoding and suggested running base64 -d to recover the plaintext flag.The WriteFile buffer contains a Base64 string. Decode it to recover the picoCTF flag.bash# The buffer output will be something like:bash# cGljb0NURntmcjFkYV9mMHJfYjFuX2luNXRydW0zbnQ0dGlvbiF9bashecho 'cGljb0NURntmcjFkYV9mMHJfYjFuX2luNXRydW0zbnQ0dGlvbiF9' | base64 -dExpected output
picoCTF{fr1da_f0r_b1n_in5trum3nt4tion!_b21a...}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
Reveal 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.