Binary Instrumentation I

Bypassing sleep functions using Frida

Challenge Info

I have been learning to use the Windows API to do cool stuff! Can you wake up my program to get the flag?
Download the exe here. Unzip the archive with the password picoctf

This challenge is #1 of a 2 part series

Binary Instrumentation II

Basic forensics & info

File command for basic info:

1
2
3
4
C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1\bininst1>file bininst1.exe
bininst1.exe: PE32+ executable (console) x86-64, for MS Windows

FLARE-VM Fri 05/02/2025 18:41:00.51

Running the program:

1
2
3
4
C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1\bininst1>bininst1.exe
Hi, I have the flag for you just right here!
I'll just take a quick nap before I print it out for you, should only take me a decade or so!
zzzzzzzz....

Knowing that the program is a Windows executable, and based off the output when running it, I assume we’ll have to target the Windows API Sleep call using Frida, a dynamic instrumentation toolkit that will let us trace, monitor, and modify the behavior of applications. Specifically, we will use frida-trace to trace function calls (Sleep, in this case`):

1
2
3
4
5
6
7
8
9
10
11
C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1>frida-trace -i Sleep -f bininst1.exe
Instrumenting...
Sleep: Auto-generated handler at "C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1\__handlers__\KERNELBASE.dll\Sleep.js"
Sleep: Auto-generated handler at "C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1\__handlers__\KERNEL32.DLL\Sleep.js"
Started tracing 2 functions. Web UI available at http://localhost:50498/
Hi, I have the flag for you just right here!
I'll just take a quick nap before I print it out for you, should only take me a decade or so!
zzzzzzzz....
/* TID 0x179c */
31 ms Sleep()
31 ms | Sleep()

When we run frida-trace to instrument a function like Sleep, it automatically creates JavaScript handler files for each implementation of that function it finds. This is a powerful feature of Frida that allows us to not just observe, but also modify program behavior at runtime.
In our case, Frida generated two handler files:

  • \__handlers__\KERNELBASE.DLL\Sleep.js
  • \__handlers__\KERNEL32.DLL\Sleep.js

For clarification, both Sleep.js files are the same. Frida generates two handlers because of how Windows API functions are implemented:

  1. KERNEL32.DLL is the higher-level library that applications typically link against
  2. KERNELBASE.dll is the lower-level implementation that KERNEL32.DLL often forwards calls to

In most cases, the Sleep function in KERNEL32.DLL will just simply forward the call to KERNELBASE.dll. For this challenge, we should be able to modify either one because:

  • If the program calls Sleep directly from KERNEL32.DLL, modifying that handler will work
  • If KERNEL32.DLL forwards to KERNELBASE.dll, modifying the KERNELBASE handler will work
  • If we modify both, we’re covered either way

So, for the sake of this challenge, I will be modifying the KERNEL32.dll one.

KERNEL32.DLL\Sleep.js

The file for your convenience:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* Auto-generated by Frida. Please modify to match the signature of Sleep.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: https://frida.re/docs/javascript-api/
*/

defineHandler({
onEnter(log, args, state) {
log('Sleep()');
},

onLeave(log, retval, state) {
}
});
  1. defineHandler({ ... }) - This is a Frida function that registers a new handler for the targeted function (Sleep in this case).

  2. onEnter(log, args, state) - This callback function is executed right before the actual Sleep function is called:

    • log - A function you can use to print messages to the Frida console
    • args - An array containing the function arguments (in this case, Sleep takes one argument for the sleep duration in milliseconds)
    • state - An object where you can store data to share between onEnter and onLeave
  3. log('Sleep()') - This simply logs “Sleep()” to the console when the function is called, but doesn’t include any details about the arguments.

  4. onLeave(log, retval, state) - This callback is executed after the Sleep function returns:

    • retval - Contains the return value of the function (this is empty, so it actually doesn’t even do anything when Sleep returns)

Solution

To solve the challenge, we’d need to modify this file and change the Sleep duration argument. Ultimately, i decided to come up with a modification that not only replaces the sleep duration with 0, but also logs the original sleep duration, just for troubleshooting / analysis purposes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defineHandler({
onEnter(log, args, state) {
// logs the original sleep duration
log(`Sleep(${args[0]}) - Original duration: ${args[0].toInt32()} ms`);

// replaces the sleep duration with 0
args[0] = ptr("0");

log("Sleep duration changed to 0 ms");
},

onLeave(log, retval, state) {
log("Sleep function completed");
}
});

After modifying & saving, we can re-run frida-trace and see if it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1 > frida-trace -i Sleep -f .\bininst1.exe
Instrumenting...
Sleep: Loaded handler at "C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1\__handlers__\KERNELBASE.dll\Sleep.js"
Sleep: Loaded handler at "C:\Users\river\Desktop\ctf\pico\BinaryInstrumentation1\__handlers__\KERNEL32.DLL\Sleep.js"
Started tracing 2 functions. Web UI available at http://localhost:51063/
Hi, I have the flag for you just right here!
I'll just take a quick nap before I print it out for you, should only take me a decade or so!
zzzzzzzz....
Ok, I'm Up! The flag is: cGljb0NURnt3NGtlX20zX3VwX3cxdGhfZnIxZGFfZjI3YWNjMzh9
/* TID 0x106c */
16 ms Sleep()
16 ms | Sleep(0xfffffffe) - Original duration: -2 ms
16 ms | Sleep duration changed to 0 ms
16 ms Sleep function completed
16 ms Sleep()
16 ms | Sleep(0xfffffffe) - Original duration: -2 ms
16 ms | Sleep duration changed to 0 ms
16 ms Sleep function completed
...
Process terminated
FLARE-VM 05/02/2025 19:24:13

Interestingly, the original duration was -2ms, which would have taken forever

The flag looks like it’s encoded via base64, you can either put it through CyberChef or decode it the cool way:

1
2
[marcial@arch ~/desktop/cyber/pico/binaryinstrumentation1]$ echo "cGljb0NURnt3NGtlX20zX3VwX3cxdGhfZnIxZGFfZjI3YWNjMzh9" | base64 -d 
picoCTF{w4ke_m3_up_w1th_fr1da_f27acc38}

flag: picoCTF{w4ke_m3_up_w1th_fr1da_f27acc38}