I was searching online tools similar to strace to track Windows binary WinAPI calls.

Not so much interesting content surfaced at first glance.

Many articles referred to an old tool: API Monitor from rohitab. However, this program is no longer actively maintained and its source code is not available.

Another option was using frida. It’s easy to install and use, is scriptable, and can be adapted to several tasks.

It can easily log Windows API calls made by a binary, and generally track any other function or piece of code executed by a process.

Basic usage

After installing the tool, it can be used to launch a process and decide what to track.

Track a WinAPI or a WinAPI pattern

C:\Code> frida-trace.exe -i "WriteFile" .\HelloWorld.exe

Instrumenting...
WriteFile: Loaded handler at "C:\Code\__handlers__\KERNELBASE.dll\WriteFile.js"
WriteFile: Loaded handler at "C:\Code\__handlers__\KERNEL32.DLL\WriteFile.js"
Hello World!
Started tracing 2 functions. Web UI available at http://localhost:27098/
           /* TID 0x10e8 */
     8 ms  WriteFile()
     8 ms     | WriteFile()
     8 ms  WriteFile()
     8 ms     | WriteFile()

Track a module

C:\Code> frida-trace.exe -i "KERNELBASE.dll" .\HelloWorld.exe

QueryUnbiasedInterruptTimePrecise: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\QueryUnbiasedInterruptTimePrecise.js"
UrlHashW: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\UrlHashW.js"
PathCchAddExtension: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\PathCchAddExtension.js"
GetAppliedGPOListInternalA: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\GetAppliedGPOListInternalA.js"
...
RegDeleteValueA: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\RegDeleteValueA.js"
OpenThreadToken: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\OpenThreadToken.js"
PathRelativePathToA: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\PathRelativePathToA.js"
SetPriorityClass: Loaded handler at "C:\Code\__handlers\KERNELBASE.dll\SetPriorityClass.js"
VerifyPackageFamilyNameA: Loaded handler at "C:\Code\__handlers_KERNELBASE.dllVerifyPackageFamilyNameA.js"
Warning: Skipping "GetCurrentThread": unable to intercept function at 7609FF20; please file a bug
Warning: Skipping "GetCurrentProcess": unable to intercept function at 75FCEEB0; please file a bug
Warning: Skipping "DebugBreak": unable to intercept function at 76048DC0; please file a bug
Warning: Skipping "LoadAppInitDlls": unable to intercept function at 76002D70; please file a bug
Warning: Skipping "DeleteProcThreadAttributeList": unable to intercept function at 75FCACE0; please file a bug
Started tracing 1773 functions. Web UI available at http://localhost:27116/
Hello World!
           /* TID 0x1d50 */
  1433 ms  WTSGetServiceSessionId()
  1433 ms  WTSGetServiceSessionId()
  1433 ms  WTSGetServiceSessionId()
  1433 ms  WTSGetServiceSessionId()
  1434 ms  WTSGetServiceSessionId()
  1434 ms  WTSGetServiceSessionId()
  1434 ms  WTSGetServiceSessionId()
  1434 ms  GetLastError()
...
  1441 ms  WTSGetServiceSessionId()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  GetLastError()
  1442 ms  FlsGetValue()
  1442 ms  WriteFile()
  1442 ms  GetLastError()
  1442 ms  GetProcAddressForCaller()
  1442 ms  FlsGetValue()
  1442 ms  GetLastError()
  1442 ms  FlsGetValue()
  1442 ms  WriteFile()
  1442 ms  GetLastError()
  1442 ms  FlsGetValue()
  1442 ms  GetLastError()
  1442 ms  FlsGetValue()
  1442 ms  GetModuleHandleW()
  1442 ms  GetModuleHandleW()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  WTSGetServiceSessionId()
  1442 ms  LoadLibraryExW()
  1442 ms     | WTSGetServiceSessionId()
  1442 ms  GetProcAddressForCaller()
  1442 ms  AppPolicyGetProcessTerminationMethod()
  1442 ms  GetModuleHandleExW()
  1442 ms     | WTSGetServiceSessionId()
Process terminated

As you can see it logs every time a hooked function is called by the binary.

Writing custom hooks

The details of how Frida performs the code hooking procedure will not be discussed in this blog article. However, it is possible to write custom hooks in JavaScript to, for example, print the arguments with which a function is called and potentially other relevant information for analysis, such as data buffers.

For example, a WriteFile JS hook could be written matching the WinAPI signature.

Take a look at WriteFile signature.

BOOL WriteFile(
  [in]                HANDLE       hFile,
  [in]                LPCVOID      lpBuffer,
  [in]                DWORD        nNumberOfBytesToWrite,
  [out, optional]     LPDWORD      lpNumberOfBytesWritten,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

Write js code.

'use strict'


var WriteFile = Module.findExportByName("kernel32.dll", "WriteFile");
if (WriteFile) {
    Interceptor.attach(WriteFile, {
        onEnter: function (args) {
            // HANDLE hFile
            this.hFile = args[0].toInt32();
            console.log("[+] WriteFile called with hFile: " + this.hFile);

            // LPCVOID lpBuffer
            this.lpBuffer = args[1];
            console.log("[+] lpBuffer: " + this.lpBuffer);

            // DWORD nNumberOfBytesToWrite
            this.nNumberOfBytesToWrite = args[2].toInt32();
            console.log("[+] nNumberOfBytesToWrite: " + this.nNumberOfBytesToWrite);

            // LPDWORD lpNumberOfBytesWritten
            this.lpNumberOfBytesWritten = args[3];
            console.log("[+] lpNumberOfBytesWritten: " + this.lpNumberOfBytesWritten);

            // LPOVERLAPPED lpOverlapped
            this.lpOverlapped = args[4];
            console.log("[+] lpOverlapped: " + this.lpOverlapped);

            // Print buffer content (optional, for debugging)
            if (this.lpBuffer && this.nNumberOfBytesToWrite > 0) {
                var bufferContent = Memory.readByteArray(this.lpBuffer, this.nNumberOfBytesToWrite);
                console.log("[+] Buffer Content: " + hexdump(bufferContent, { length: this.nNumberOfBytesToWrite }));
            }
        },
        onLeave: function (retval) {
            console.log("[+] WriteFile returned: " + retval.toInt32());
            if (this.lpNumberOfBytesWritten) {
                var bytesWritten = Memory.readU32(this.lpNumberOfBytesWritten);
                console.log("[+] Bytes Written: " + bytesWritten);
            }
        }
    });
} else {
    console.log("[-] WriteFile not found in kernel32.dll");
}

And execute frida:

PS C:\Code> frida -l .\writefile-hook.js .\HelloWorld.exe

Spawned `.\HelloWorld.exe`. Resuming main thread!
[+] WriteFile called with hFile: 200
Hello World![+] lpBuffer: 0x12fe6d0
[+] nNumberOfBytesToWrite: 12
[+] lpNumberOfBytesWritten: 0x12fe6cc
[+] lpOverlapped: 0x0
[+] Buffer Content:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  48 65 6c 6c 6f 20 57 6f 72 6c 64 21              Hello World!
[+] WriteFile returned: 1
[+] Bytes Written: 12
[+] WriteFile called with hFile: 200
[+] lpBuffer: 0x12fe724
[+] nNumberOfBytesToWrite: 2
[+] lpNumberOfBytesWritten: 0x12fe720
[Local::HelloWorld.exe ]-> [+] lpOverlapped: 0x0
[+] Buffer Content:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  0d 0a                                            ..
[+] WriteFile returned: 1
[+] Bytes Written: 2
Process terminated
[Local::HelloWorld.exe ]->
Thank you for using Frida!

Wonderful. It logs the call, it prints arguments and it’s also able to read the buffer and print content.

Frida is a powerful and, at the same time, simple-to-use tool that can be used to track WinAPI calls in a process for malware analysis, development, and reverse engineering purposes.