Hola! As a follow-up to my latest Patreon live session we will be doing a basic introduction to using Frida for application introspection and hooking on Windows. Throughout the post we will be using frida-trace as it offers a convenient live stream where we can inspect the changes made to our function hooks in real time. Once you get your head around the JavaScript syntax you can layer that knowledge onto the various Frida bindings (Python/C/Node/Swift/.Net/QML).



Why Frida? It gives a simple interface where you can rapidly develop complex hooking logic and make changes to it as your requirements/understanding evolves (compare that to the costly process of redeploying C++ function hooks). What to use Frida for? As our title suggests introspection (looking inside the app to analyze it's behavior) and hooking (changing the behavior of the app). From a security perspective Frida is a research tool, not suited for weaponized deployment. That being said, Frida can be used to prototype offensive hooks which can later be implemented in a different framework such as EasyHook for deployment.



Resources:

+ Frida (@fridadotre) - here

+ Peeking under the hood with Frida (Sam Rubenstein) - here

Registry Introspection

In this section we will look at passively monitoring registry activity within arbitrary Windows applications. To start of, we will have a look at RegOpenKeyExW which is most commonly used to open handles to registry keys. Below we can see the C++ function prototype.

One thing to keep in mind is that most API's will have an ANSI and a Unicode version. For our purposes we should assume that the Windows application we are hooking will be using the Unicode version. Let's briefly attach Frida to a process and define/print all of these arguments.

Frida makes this process exceedingly easy. When using trace, Frida creates a "__handlers__" folder in the current directory where it populates JS files with onEnter/onLeave prototypes for any function you have specified. Getting out function arguments is as easy as printing arguments in an array. The JS handler for the image above is shown below.

As we can see, we are able to pull out all of those function arguments, but for the purposes of quickly looking at registry activity the most telling argument is probably the lpSubKey. Of course the string pointer is not particularly useful but we can easily rewrite our onEnter function to pull out the unicode string as show below.

If we save our changes and perform some new activity in the app we see the full subkey registry paths being accessed by the application.

If you look closer at the output you will see that there is something missing. We don't actually get the registry hive, this is because the call is using a previously opened handle to whichever hive it is querying. We can tell what the hive is by doing lookup's on the used handles and tracking them across calls but that is outside the scope of this post. Notice also that the output is colorized based on thread context.



If you have been following some of the malarkey with COM Hijacking for privilege escalation and persistence you can understand that there may be a use case for listing registry access to paths containing "CLSID\" where the result of the call is a failure (most likely because the subkey is being queried in HKEY_CURRENT_USER). We can quickly modify our POC as follows to capture those calls.

Saving our POC returns only attempts to open CLSID subkeys where the result was not ERROR_SUCCESS.

Similarly we could track which queries were successful. What if we wanted to know which key values were being accessed after successfully opening a subkey handle? Typically this would happen by using RegQueryValueEx. If we now hook both these functions we can implement some naive logic where we store the handle returned by a successful call to RegOpenKeyEx and on calling RegQueryValueEx we compare the input handle with the one we saved, if they match we can print the value which is being queried. We can see the code to achieve this below.

Refreshing our POC now only returns those entries we are filtering on.

This is a simple example but you can see that Frida allows you to easily instrument functions and play around with them without a costly Compile->Test->Compile cycle.

Hooking MessageBox

We have seen so far how we can do passive recon, in this section we will see how we can influence the behavior of an application. As a basic example I chose to use MessageBox as it is kind of the "Hello World" of the Windows API. To allow us to dynamically test our function hooks I wrote a small Windows forms test harness in C#, you can see the main functionality below.

Using the same techniques we saw in the previous section with can quickly hook the application and write some basic JS to dump out the MessageBox parameters. Notice the use of readAnsiString to match MessageBoxA.

Frida has a number of functions to edit/allocate memory primitives, I suggest the reader has a look at the API documentation. For our simple demonstration I modified the JS to implement two types of hooks: (1) if lpText is "Bob" change it to "Alice" and (2) if the uType is 6 change it to 0.

We can observe the results in the image below. Notice that the parameters are checked individually so you could have a condition where both/none/one of the hooks is active.

Userland Process Hiding -> SystemProcessInformation

For the final part of this post I want to briefly show a more complex hooking example. If you have worked with undocumented API's on Windows at all the chances are good you have used NtQuerySystemInformation and some of it's information classes. One of these classes is the SystemProcessInformation class (0x5). As it turns out SystemProcessInformation is the authoritative source for userland processes so any applications that get process listings through whichever API's will end up filtering down to NtQuerySystemInformation whether they know it or not (Task Manager/Process Explorer/Process Hacker/.....).



I recently wrote a PowerShell wrapper for this function, Get-SystemProcessInformation, so I thought it would be a neat idea to try and hook this function with Frida to demonstrate userland process hiding.



SystemProcessInformation Memory Layout

To understand how the hook works we need to know what NtQuerySystemInformation actually returns when the SystemProcessInformation class is used. Hopefully the layout below helps provide some clarity.

NTSTATUS WINAPI NtQuerySystemInformation( _In_ UINT SystemInformationClass , // SYSTEM_INFORMATION_CLASS _Inout_ PVOID SystemInformation , // A pointer to a buffer that receives the requested information _In_ ULONG SystemInformationLength, // Byte count allocated for the request _Out_opt_ PULONG ReturnLength // Pointer to the variable to receives the output size ); SystemInformationClass => SystemProcessInformation = 0x5 SystemInformation => Pointer, eg 0x11223344556 -----------------------| | | | [Points at an array of SYSTEM_PROCESS_INFORMATION Structs] | | |-------------------------------------------------| | |--------------------------------------| | [Int]NextEntryOffset (eg:0x1fb) | --------------------> | | | | | | | | | | ....... | | | | | | | | | | | | [UNICODE_STRING]ImageName | | [2nd Entry = 1st Entry + 0x1fb] | |-> svchost.exe | | | | | | | | | | | | ....... | | | | | | | | | SYSTEM_THREAD_INFORMATION structs | | |--------------------------------------| | | [Int]NextEntryOffset (eg:0x222) | <-------------------> | | | | | | | | | | ....... | | | | | | | | | | | | [UNICODE_STRING]ImageName | | [3rd Entry = 2nd Entry + 0x222] | |-> powershell.exe | | | | | | | | | | | | ....... | | | | | | | | | SYSTEM_THREAD_INFORMATION structs | | |--------------------------------------| | | [Int]NextEntryOffset (eg:0x3a0) | <-------------------> | | | | | | | | | | ....... | | | | | | | | | | | | [UNICODE_STRING]ImageName | | [4th Entry = 3rd Entry + 0x3a0] | |-> notepad.exe | | | | | | | | | | | | ....... | | | | | | | | | SYSTEM_THREAD_INFORMATION structs | | |--------------------------------------| | ..... <------------>



This representation is not totally accurate because there are some fixed processes at the start of the array but it's serviceable. The key point is that each blob varies in size depending on how many SYSTEM_THREAD_INFORMATION structs are appended to the SYSTEM_PROCESS_INFORMATION struct.



SystemProcessInformation Hooking

Let's say we wanted to hide all PowerShell processes in that list, all we would need to do is traverse the list and rewrite the entry before PowerShell so the NextEntryOffset points to the next entry in the list.

|--------------------------------------| | [Int]NextEntryOffset (eg: 0x1fb 0x41d) | --------------------> | I | | | I | | | I | | | ....... I==================================> [3nd Entry = 1st Entry + 0x1fb + 0x222 | | | => 1st Entry + 0x41d] | | | | | | | [UNICODE_STRING]ImageName | | | |-> svchost.exe | | | | | | | | | | | | ....... | | | | | | | | | SYSTEM_THREAD_INFORMATION structs | | |--------------------------------------| | | [Int]NextEntryOffset (eg:0x222) | | | | | | | | | | | | ....... | | | | | | | | | | | | [UNICODE_STRING]ImageName | | | |-> powershell.exe | | | | | | | | | | | | ....... | | | | | | | | | SYSTEM_THREAD_INFORMATION structs | | |--------------------------------------| | | [Int]NextEntryOffset (eg:0x3a0) | <-------------------> | | | | | | | | | | ....... | | | | | | | | | | | | [UNICODE_STRING]ImageName | | [4th Entry = 3rd Entry + 0x3a0] | |-> notepad.exe | | | | | | | | | | | | ....... | | | | | | | | | SYSTEM_THREAD_INFORMATION structs | | |--------------------------------------| | ..... <------------>



This may seem a bit complicated but all we need to do is intercept the API call as it is about to return, loop over the list by reading offsets/unicode strings and overwrite a single integer per identified PowerShell process. You can see my Frida implementation below. Please note that these offsets were only tested on x64 Win10 (though they should be valid for Win7-10 x64).

I uploaded a short video demonstrating the SystemProcessInformation hook on YouTube. Btw, in theory there is an edge-case issue with the code (though I never triggered it) which I didn't fix, a free cookie for anyone that can tell me what it is.