Dynamic Invocation in .NET to bypass hooks


TLDR: This blogpost showcases several methods of dynamic invocation that can be leveraged to bypass inline and IAT hooks. A proof of concept can be found here: https://github.com/NVISO-BE/DInvisibleRegistry

A while ago, a noticeable shift in red team tradecraft happened. More and more tooling is getting created in C# or ported from PowerShell to C#.
PowerShell became better shielded against offensive tradecraft thanks to a variety of changes, ranging from AMSI (Anti Malware Scan Interface) to Script Block logging and more.
One of the cool features of C# is the ability to call the Win32 API and manipulate low-level functions like you normally would in C or C++
The process of leveraging these API functions in C# is dubbed Platform Invoking (P/invoke for short). Microsoft made this possible thanks to the System.Runtime.InteropServices namespace in C#. All of which is being “managed” by the CLR (Common Language Runtime). The graphic below shows how using P/Invoke, you can bridge the gap between unmanaged and managed code.

Consuming Unmanaged DLL Functions | Microsoft Docs
How P/invoke bridges the cap between managed and unmanaged code.
source – https://docs.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions

There is an operational (from an offensive point of view) drawback to leveraging .NET as well, however. Since the CLR is responsible for the translation between .NET to machine-readable code, the executable is not directly translated into this code. This means that the executable stores its entire codebase in the assembly, and is thus very easily reverse-engineered.

On top of assemblies being reversed engineered, we are also moving more and more into an EDR (Endpoint Detection and Response) world. Organizations are (thankfully) increasing their (cyber)security posture around the globe, making the lives of operators harder, which is a good thing. As Cybersecurity consultants it is our job to help organizations increase their cybersecurity posture so we are glad that this is moving in the right direction.

EDR’s catch offensive tradecraft, even when executed in memory (without touching disk, also commonly referred to as “fileless”) by hooking into processes and subverting their execution on certain functions. This allows the EDR to inspect what is happening, and if it likes what it sees, the EDR will let the function call pass and normal execution of the program will be achieved. @CCob posted a very nice blog post series about this concept, and how to bypass the hooks. A good EDR will “hook” in the lowest level possible,this would be ntdll.dll (this dll is responsible of making system calls to the Windows kernel). The image below is a good example of how EDR’s could work.

EDR Observations | RE & Sec Blog
how EDR’s can hook ntdll calls to prevent malware execution
source – http://christopher-vella.com/2020/08/21/EDR-Observations.html

There are two main methods an EDR uses to do its hooking, ironically, this is also how most rootkits operate: IAT hooking and Inline hooking (also known as splicing).

IAT stands for Import Address Table, you could compare the IAT with a phone book. Every executable file has this phone book where it can look up the numbers of his/her friends (functions it needs).
This phonebook can be tampered with, an EDR could change an entry in this dictionary to point to it. Below you can see a nice diagram of how IAT hooking could work.
In order for this diagram to make sense, you’ll have to think of the EDR as “malicious code”:

In this example, a program that wants to call a message box. The program will look up the message box’s number (address) in his phone book so they can call it.
Little does the program know, that someone actually replaced the phone number (address) so whenever they call message box, they actually call the EDR instead!
The EDR will pick up the phone, listen to the message (function call), and if the EDR likes the message, will tell the program the real phone number of message box so the program can call message box.

Inline hooking could be compared with an intruder holding a gun to the head of the friend our program wants to call.

splicing illustration courtesy of “Learning malware Analysis” by Monnapa K A

With inline hooking, the program has the correct number (address) of its friend (function). The program will call its friend and its friend will answer the call.
Little does the program know, that its friend has actually been taken hostage and the call is on speaker. The intruder tells the friend what to say a certain phrase (execute some instructions) and afterward resume the conversation as if nothing happened.

These two methods can cause serious issues for operators (in the case of defensive hooks) and defenders (in the case of offensive hooks).
From an offensive perspective, there are some bypasses you can leverage to get around these function hooks. Firewalker by MDSec comes to mind and Sharpblock by @CCob. Or, the ultimate bypass. use system calls directly.

Another interesting project is the Sharpsploit project, aimed to be used as a library to facilitate offensive C# tradecraft, much like PowerSploit was for PowerShell back in the day. The downside of Sharpsploit however, is that the compiled dll is considered malicious, so if you use sharpsploit as a dependency for your program, you’ll immediately be screwed by AV.
Part of Sharpsploit however, is dynamic invocation (also known as D/invoke). This is (in my opinion) the most interesting part of the entire Sharpsploit suite. It allows operators to invoke API’s leveraged by P/Invoke, but instead of static imports, it does it dynamically! This means IAT hooking is completely bypassed since dynamically invoking functions will not create an entry in the executables import table. As a result analysts and EDR’s alike will not be able to tell what your majestic program does, just by looking at your import table. TheWover wrote a very nice blog post about it, I highly recommend reading it.

Additionally, a NuGet package was released by TheWover and what is great about this NuGet package is that it can be directly used as a library and is NOT considered malicious. The cool thing about this package is that it contains structures and functions that would otherwise have to be manually defined by the programmer. If this does not make sense to you right now, allow me to illustrate with an example I created a few days ago:
https://gist.github.com/jfmaes/944991c40fb34625cf72fd33df1682c0#file-dinjectqueuerapc-cs

Then, I recreated the same PoC, with the NuGet

The codebase shrunk from 731 lines of code to just 38. That is what makes the D/Invoke NuGet the best invention ever for offensive .NET development.
The NuGet is still a work in process, but its ultimate goal is to be a full replacement for P/Invoke. If you want to help out, feel free to submit a pull request!

I’m confident that this library can become very big, through the power of open source!

Leveraging D/Invoke to bypass hooks and the revival of the invisible reg key.

Now that the concepts of hooking and dynamic invocation are clear, we can dive into bypassing hooks using D/Invoke.
For inspiration and to make this blog post useful, I’ve decided to create a proof of concept based on some old research from the folks over at Specterops.
In their research, they took even older research of Mark Russinovich and turned it into an offensive proof of concept. Mark released a tool called RegHide back in 2005.
He discovered that you could prepend a null byte when creating a new registry key using Ntcreatekey. When a null byte prepends the registry key, the interpreter sees this as a string termination (in C, strings get terminated with a null byte). This results in the registry accepting the new reg key, but unable to display it properly. This gives defenders already a nice indication something is definitely fishy.

Image for post
Regedit will show an error when trying to display a key value with a null character in its name.

In my proof of concept, I ported their PowerShell into C#, leveraging the power of D/invoke and its NuGet.
I’ve submitted a pull request for the D/invoke project, where I added all the necessary structures and delegates (along with some others, such as QueueUserAPC process injection).
However, as I wanted to create this blogpost already, I actually coded the necessary structs into my PoC as well. making it compatible with the current NuGet package of D/invoke.
The PoC can be found here:
https://github.com/NVISO-BE/DInvisibleRegistry

usage of the PoC – DinvisbleRegistry



There are three methods of invocation coded into the PoC, all the methods are fully implemented even though they could have been merged into one big function.
The reason why I took the time to write the code to its fullest, is because I wanted to show the concepts behind the different approaches an operator can take to leverage D/Invoke to bypass hooking.

Method 1: “classic” dynamic invoke.

When specifying the -n flag and all other required parameters the PoC will create a new (hidden, if you use the -h flag) registry key in the requested hive using the traditional D/Invoke methodology.
This method will bypass IAT hooking as the functions are being called dynamically, thus not showing up in the IAT.

D/Invoke works like this, you first need to create the signature of the API call you are trying to do (unless it’s already in the D/Invoke Nuget) and the corresponding Delegate function:

API signature

public static DInvoke.Data.Native.NTSTATUS NtOpenKey(
ref IntPtr keyHandle,
STRUCTS.ACCESS_MASK desiredAccess,
ref STRUCTS.OBJECT_ATTRIBUTES objectAttributes)
{
object[] funcargs =
{
keyHandle,desiredAccess,objectAttributes
};
DInvoke.Data.Native.NTSTATUS retvalue = (DInvoke.Data.Native.NTSTATUS)DInvoke.DynamicInvoke.Generic.DynamicAPIInvoke(@"ntdll.dll", @"NtOpenKey", typeof(DELEGATES.NtOpenKey), ref funcargs);
keyHandle = (IntPtr)funcargs[0];
return retvalue;
}

Corresponding delegate:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate DInvoke.Data.Native.NTSTATUS NtOpenKey(
ref IntPtr keyHandle,
STRUCTS.ACCESS_MASK desiredAccess,
ref STRUCTS.OBJECT_ATTRIBUTES objectAttributes);

As you can see in the API signature, you are calling the DynamicAPIInvoke function and passing it the delegate of the function.

Method 2: “Manual Mapping”

A trick some threat actors and malware strains use is the concept of manual mapping. TheWover explains manual mapping in his blog post as follows:

DInvoke supports manual mapping of PE modules, stored either on disk or in memory. This capability can be used either for bypassing API hooking or simply to load and execute payloads from memory without touching disk. The module may either be mapped into dynamically allocated memory or into memory backed by an arbitrary file on disk. When a module is manually mapped from disk, a fresh copy of it is used. That way, any hooks that AV/EDR would normally place within it will not be present. If the manually mapped module makes calls into other modules that are hooked, then AV/EDR may still trigger. But at least all calls into the manually mapped module itself will not be caught in any hooks. This is why malware often manually maps ntdll.dll. They use a fresh copy to bypass any hooks placed within the original copy of ntdll.dll loaded into the process when it was created, and force themselves to only use Nt* API calls located within that fresh copy of ntdll.dll. Since the Nt* API calls in ntdll.dll are merely wrappers for syscalls, any call into them will not inadvertently jump into other modules that may have hooks in place

Manual mapping is done in the PoC when you specify the -m flag and the code looks like this

First, map the library you are using, the lower you go, the less chance of hooks further down the call tree. Whenever you can use ntdll.dll.

DInvoke.Data.PE.PE_MANUAL_MAP mappedDLL = new DInvoke.Data.PE.PE_MANUAL_MAP();
mappedDLL = DInvoke.ManualMap.Map.MapModuleToMemory(@"C:\Windows\System32\ntdll.dll");

Next, create the delegate for the function you are trying to call, if it is not yet in D/Invoke, else you can just leverage the NuGet.

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate DInvoke.Data.Native.NTSTATUS NtOpenKey(
ref IntPtr keyHandle,
STRUCTS.ACCESS_MASK desiredAccess,
ref STRUCTS.OBJECT_ATTRIBUTES objectAttributes);

Next, create your function parameters and an array to store them in

IntPtr keyHandle = IntPtr.Zero;
STRUCTS.ACCESS_MASK desiredAccess = STRUCTS.ACCESS_MASK.KEY_ALL_ACCESS;
STRUCTS.OBJECT_ATTRIBUTES oa = new STRUCTS.OBJECT_ATTRIBUTES();
oa.Length = Marshal.SizeOf(oa);             
oa.Attributes = (uint)STRUCTS.OBJ_ATTRIBUTES.CASE_INSENSITIVE;             
oa.objectName = oaObjectName;             
oa.SecurityDescriptor = IntPtr.Zero;           
oa.SecurityQualityOfService = IntPtr.Zero;            
DInvoke.Data.Native.NTSTATUS retValue = new DInvoke.Data.Native.NTSTATUS();
object[] ntOpenKeyParams =
{
    keyHandle,desiredAccess,oa
};

Finally, call D/invokes CallMappedDLLModuleExport to call the function from the manually mapped DLL.

retValue = (DInvoke.Data.Native.NTSTATUS)DInvoke.DynamicInvoke.Generic.CallMappedDLLModuleExport(mappedDLL.PEINFO, mappedDLL.ModuleBase, "NtOpenKey", typeof(DELEGATES.NtOpenKey), ntOpenKeyParams, false);

In the case of ntdll, the last parameter of CalledMappedDLLModuleExport is false, this is because ntdll does not have a DllMain method. Setting it to true would cause panic as you are trying to access memory that does not exist, crashing the program.


Method 3: OverloadMapping (my personal favorite)

TheWover explains Overloadmapping as follows:

In addition to normal manual mapping, we also added support for Module Overloading. Module Overloading allows you to store a payload in memory (in a byte array) into memory backed by a legitimate file on disk. That way, when you execute code from it, the code will appear to execute from a legitimate, validly signed DLL on disk.
A word of caution: manual mapping is complex and we do not guarantee that our implementation covers every edge case. The version we have implemented now is serviceable for many common use cases and will be improved upon over time. Additionally, manual mapping and syscall stub generation do not currently work in WOW64 processes.

Method 2 and 3 are largely the same in implementation, the only variation is you call the overload manual map method, and you do not have to map to memory anymore

        DInvoke.Data.PE.PE_MANUAL_MAP mappedDLL = DInvoke.ManualMap.Overload.OverloadModule(@"C:\Windows\System32\ntdll.dll");

The rest of the implementation remains the same as in method 2.

If you want to see which process got used you can get it using the PE_MANUAL_MAP DecoyModule call:

Console.WriteLine("Decoy module is found!\n Using: {0} as a decoy", mappedDLL.DecoyModule);

Method 4: System calls

Disclaimer: This method is currently a bit “broken”, as a result, you might not experience the result you are looking for. This is also the reason why this method is currently NOT implemented in the PoC I would advise not using this method until a later release of D/Invoke.

D/Invoke has provided an API to dynamically get system calls as well. The steps to generate system calls are explained next.

Create your delegate (should it not already exist):

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate DInvoke.Data.Native.NTSTATUS NtOpenKey(
ref IntPtr keyHandle,
STRUCTS.ACCESS_MASK desiredAccess,
ref STRUCTS.OBJECT_ATTRIBUTES objectAttributes);

Create a IntPtr to store your syscall pointer and fill in the pointer using the GetSyscallStub function

IntPtr syscall = IntPtr.Zero;
syscall  = DInvoke.DynamicInvoke.Generic.GetSyscallStub("NtOpenKey");

Create a delegate of the call you want to make that uses the syscall through the use of our dear friend Marshal

DELEGATES.NtOpenKey syscallNtOpenKey = (DELEGATES.NtOpenKey)Marshal.GetDelegateForFunctionPointer(syscall, typeof(DELEGATES.NtOpenKey));

Finally, make the call 🙂

retValue = syscallNtOpenKey(ref keyHandle, desiredAccess, ref oa);

Conclusion

I hope this blogpost has shed some light on the different approaches an operator could take in order to bypass EDR hooks for both IAT and inline hooking.
Feel free to contribute to the D/Invoke project by submitting a pull request! We will greatly appreciate your efforts! The D/Invoke GitHub project can be found here:
https://github.com/TheWover/DInvoke
The proof of concept can be found here:
https://github.com/NVISO-BE/DInvisibleRegistry

About the author

Jean-François Maes is a red teaming and social engineering expert working in the NVISO Cyber Resilience team. 
When he is not working, you can probably find Jean-François in the Gym or conducting research.
Apart from his work with NVISO, he is also the creator of redteamer.tips, a website dedicated to help red teamers.
Jean-François is currently also in the process of becoming a SANS instructor for the SANS SEC699: Purple Team Tactics – Adversary Emulation for Breach Prevention & Detection course
He was also ranked #1 on the Belgian leaderboard of Hack The Box (a popular penetration testing platform).
You can find Jean-François on LinkedIn , Twitter , GitHub and on Hack The Box.

23 thoughts on “Dynamic Invocation in .NET to bypass hooks

  1. The masslogger (ba5942dbbe9ab4b896fd2d10025192784c595839c4d0a0a5a5f4f45b5a6f08ae) has a chain VBA-Powershell-C# with the same xor key

Leave a Reply