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:
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:
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:
>winstrument
> list
Loaded Modules:
Available Modules:
dlls
com_hijack
file_rw
process
pipes
impersonate
registry
socket
> 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.