Tool Release – Winstrument: An Instrumentation Framework for Windows Application Assessments

by George Osterweil

Winstrument is a modular framework built on top of Frida designed to help testers reverse engineer Windows applications and assess their attack surface.


Winstrument is built on top of Frida, a powerful dynamic instrumentation framework which aids reverse engineering and debugging by injecting into a process a Javascript runtime with an API for hooking functions and modifying function arguments.

There are a lot of Frida-based tools to do various tasks, but they tend to be small single-purpose scripts, and it can be difficult to find one to accomplish a particular reversing task. Additionally, Frida’s structure tends to lead to a sizable amount of boilerplate code to handle callbacks and messages, making writing new scripts somewhat tedious.

Winstrument aims to solve these problems by making a simple, modular, framework that makes it easy to instrument a process with Frida scripts, manage their output, and write new functionality as needed. Some tooling already exists in this area, such as Objection on iOS and Android. However, there is a notable lack of such tooling for Windows. Winstrument is a Frida-based platform that makes it easy to analyze Windows applications.


I built Winstrument as part of my internship in summer 2019 at NCC Group. Since my background is primarily Linux-oriented, I wanted to learn more about the Windows API system calls and improve my Windows privilege escalation knowledge. Also, some of my coworkers suggested I look into learning Frida, since it makes some normally-painful debugging tasks easier, and is generally useful.

In building Winstrument, I encountered several implementation challenges.

The first challenge I encountered was that Frida is missing a lot of documentation, especially when it comes to language bindings (e.g. for Python). I couldn’t find any formal API docs for the Python at all, so I largely had to rely on example code and reading Frida’s own Python source. Although being able to do that is good experience, I would have liked to focus less on that and instead spend longer working on my own code instead of digging through Frida’s GitHub repositories.

Another difficulty I encountered was identifying what system calls that I should hook for each module, and which DLL I needed to search for those exports in. MSDN has very good documentation of the available system calls, but with all the variants of similar functionality, it was difficult to determine which calls programs tended to actually make in the wild. Sysinternals’ Procmon^procmon was very helpful for helping me figure out what to hook, especially its stack trace view for a particular syscall. In terms of locating the exports, Kernel32 contained the majority of what I needed, but some of the calls appeared in AdvApi32 or KernelBase instead. In a few cases, some functions like RegOpenKeyExW appeared both in AdvApi32.dll and in KernelBase.dll. In these cases, I opted to hook KernelBase. From what I could tell, calls to functions KernelBase in Windows 7 onward are forwarded to their implementations in AdvApi32.dll and Kernel32.dll, so hooking Kernelbase should be able to catch everything.

One final issue I ran into while developing Winstrument was handling child processes nicely. It turns out that Frida does support handling this through a mechanism it calls Child Gating, but finding documentation on this functionality was difficult. As Frida’s Reactor scheduling code is multi-threaded, it also introduced some concurrency headaches, especially when handling exceptions.


Winstrument’s primary functionality is in its modules. With the built-in set of
modules, you can:

  • View files read and written by a process, and the number of byres read or written.
  • See writes and reads to the registry.
  • See LoadLibrary() calls, and check relative DLL loads to identify potential DLL search order hijacking.
  • View socket activity
  • See child processes and arguments spawned by CreateProcess
  • Inspect system calls related to named pipes
  • Check for registry key loads to CLSID keys that don’t exist (Potential COM hijacking)
  • See when a process impersonates another user

Winstrument is designed to be extensible. The built-in modules provide a basic set of features, but there’s absolutely a lot more functionality it could have. That’s why Winstrument provides a module API that removes as much boilerplate as possible from the process of writing new Frida scripts. Simply write your Javascript, subclass the base module in Python, override whatever functionality you need, and you’re good to go. Winstrument handles the logic of process spawning and instrumentation for you, so you can focus on the unique functionality of your script.

Winstrument ships with a REPL for managing modules, spawning, and instrumenting your target process. From the REPL, you can:

  • View available modules
  • Dynamically load desired modules to instrument
  • Configure settings and persist them in TOML format
  • Instrument the target with the selected modules and store the output in a SQLite DB
  • View and export output in a variety of formats including table, JSON and grep-able output


Most interaction with Winstrument takes place through the REPL. For information on installing and using Winstrument, take a look at the project on GitHub.

Here’s an example of instrumenting Paint using Winstrument’s file_rw module:

> list
Loaded Modules:
Available Modules:
> use file_rw
> set target "C:\Windows\System32\mspaint.exe"
> run
Spawned 7720
instrumented process with pid: 7720 and path: C:\Windows\System32\mspaint.exe
detached from 7720 for reason process-terminated

module    time                 target              function    fh      path                                                                                                                     mode             bytes
--------  -------------------  ------------------  ----------  ------  -----------------------------------------------------------------------------------------------------------------------  -------------  -------
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x4d4   C:\Windows\Fonts\staticcache.dat                                                                                         GENERIC_READ        60
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x60c   C:\WINDOWS\Registration\R000000000001.clb                                                                                GENERIC_READ      5941
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x6d4   C:\Users\oster\AppData\Local\IconCache.db                                                                                GENERIC_READ    140547
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x75c   \\.\MountPointManager                                                                                                    0x0                174
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x768   C:\Users                                                                                                                 0x100081           504
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x798   C:\Users\oster\Desktop\desktop.ini                                                                                       GENERIC_READ      2070
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x7d8   C:\Users\oster\Pictures\Camera Roll\desktop.ini                                                                          GENERIC_READ       570
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x87c   C:\Users\Public\Desktop\desktop.ini                                                                                      GENERIC_READ       174
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x8d4   C:\Users\oster\Dropbox\desktop.ini                                                                                       GENERIC_READ       176
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x908   C:\                                                                                                                      0x100081           402
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x914   C:\                                                                                                                      0x100081           298
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0xd28   C:\Users\oster\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\f01b4d95cf55d32a.automaticDestinations-ms  GENERIC_READ    294329
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0xddc   C:\Users\oster\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\f01b4d95cf55d32a.automaticDestinations-ms  GENERIC_READ     90728
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  ReadFile    0x5f4   C:\Users\oster\Pictures\Untitled.png                                                                                     GENERIC_READ        40
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  WriteFile   0x450   C:\WINDOWS\Debug\WIA\wiatrace.log                                                                                        GENERIC_WRITE     2940
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  WriteFile   0xebc   \\.\MountPointManager                                                                                                    0x0               5941
file_rw   2020-07-10 20:57:30  C:/.../mspaint.exe  WriteFile   0x1010  C:\Users\oster

Perhaps unsurprisingly, Paint doesn’t do all that much. Most of the reads and writes there are actually from the File Explorer dialog when saving.

As a slightly more interesting example, the following is Winstrument’s output from instrumenting an instance of itself (instrumenting notepad):

Spawned 31076
instrumented process with pid: 31076 and path: C:\Windows\System32\notepad.exe
Child removed: 31076
instrumented process with pid: 53004 and path: C:\Users\oster\AppData\Local\Temp\frida-69a0a581de60aa12d04f3bc2e3bde82d\frida-winjector-helper-32.exe
Child removed: 53004
instrumented process with pid: 33820 and path: C:\Users\oster\AppData\Local\Temp\frida-69a0a581de60aa12d04f3bc2e3bde82d\frida-winjector-helper-32.exe
Child removed: 33820
instrumented process with pid: 31092 and path: C:\Users\oster\AppData\Local\Temp\frida-69a0a581de60aa12d04f3bc2e3bde82d\frida-winjector-helper-64.exe
Child removed: 31092
detached from 31076 for reason process-terminated

module    time                 target                                dll                                              writeable_path
--------  -------------------  ------------------------------------  -----------------------------------------------  ------------------------------------------------------------------------------------------------------
dlls      2020-07-10 22:54:48  C:/.../winstrument.exe                ntdll.dll                                        C:\Python38\Scripts
dlls      2020-07-10 22:54:48  c:/.../python.exe                     pywintypes38.dll                                 c:\python38\lib\site-packages\pywin32_system32\pywintypes38.dll
dlls      2020-07-10 22:54:48  c:/.../python.exe                     pythoncom38.dll                                  c:\python38
dlls      2020-07-10 22:54:48  c:/.../python.exe                     frida-winjector-helper-32.exe                    C:\Users\oster\AppData\Local\Temp\frida-69a0a581de60aa12d04f3bc2e3bde82d\frida-winjector-helper-32.exe
dlls      2020-07-10 22:54:48  C:/.../frida-winjector-helper-32.exe  iphlpapi.dll                                     C:\Users\oster\AppData\Local\Temp\frida-69a0a581de60aa12d04f3bc2e3bde82d
dlls      2020-07-10 22:54:48  C:/.../notepad.exe                    frida-agent.dll                                  C:\Users\oster\AppData\Local\Temp\frida-69a0a581de60aa12d04f3bc2e3bde82d\64\frida-agent.dll

This example sheds some light on the internal workings of Winstrument and Frida.

We see that when Winstrument launches a process, it spawns frida-winjector-helper-32.exe and frida-winjector-helper-64.exe in addition to the process itself. Frida then injects its own DLL into the program to hook syscalls, namely frida-agent.dll. The Frida injector process itself appears to use some functions from iphlpapi.dll. The DLLS ntdll.dll, pywintypes38.dll, and pythoncom38.dll are used as part of the Winstrument’s own dlls module. The dlls module uses pywin32 to make system calls to determine which DLL paths in the search order are writeable by the current user. This can be seen in the ‘writeable_paths’ column on the output above.


Winstrument makes analyzing Windows apps easy, helping you quickly identify application functionality that might be insecure or warrant further review. Winstrument is easy to extend with custom modules for integrating additional hooks.

While I hope that Winstrument is useful in its current form, it is still very much a research project that I developed over the course of around a month. As such, the tool should be considered experimental and it may freeze or behave weirdly when instrumenting some programs.

I still have many future plans for improvements, and I expect to keep working on it over the coming months. Among other features, I plan to add more modules to hook more things, make the module API more flexible, and add better support for configuring per-module settings.

Winstrument is open source under GPLv3. If you write any new modules or add new features, please consider contributing them back to help make Winstrument more useful for everyone. I hope that the initial release is just the beginning, and that Winstrument can grow over time to become even better for Windows instrumentation.

For more info, head over to Winstrument’s GitHub repository.

If you want to try it yourself, just pip install winstrument and run the winstrument command.