RSS Security

🔒
❌ About FreshRSS
There are new articles available, click to refresh the page.
Before yesterdayNVISO Labs

Tap tap… is this thing on? Creating a notification-service for Cobalt-Strike

5 March 2021 at 15:07

Ever needed a notifier when a new beacon checks in? Don’t want to keep checking your Cobalt-Strike server every 5 minutes in the hopes of a new callback? We got you covered! Introducing the notification-service aggressor script available at
https://github.com/NVISOsecurity/blogposts/tree/master/cobalt-strike-notifier

If the above image resonates with you, you’ll know that the point between sending out your phish and receiving your first callback is a very stressful time. All kinds of doom scenarios pop into your head… “Did I test my payload sufficiently?”, “Did my email get blocked somewhere along the chain?”, “Did my target pay attention and reported it as a phish?” You can solve some of these issues by introducing “canaries” in your payloads, for example, an image that phones home when the email is opened, an arbitrary HTTP (or DNS) request to a server you control.

There is however one thing you cannot control, even if you really wanted to: WHEN a user will click on your payload. If you are using Cobalt-Strike out of the box, this will result in you having to check your GUI every x minutes/hours/days to see if a beacon dialed in or not, and by the time you see your beacon connecting, it might already be several minutes or even hours that your beacon is active, this is of course less than ideal.

Thankfully Cobalt-Strike allows us to modify or expand its default behavior through the usage of “Aggressor Scripts”. These scripts are developed in “sleep”. Sleep is a java based scripting language developed and invented by Raphael Mudge (the creator of Cobalt-Strike). There are already a ton of aggressor scripts out there and there even was one that closely resembled our use case developed by FortyNorthSecurity:
https://fortynorthsecurity.com/blog/aggressor-get-text-messages-for-your-incoming-beacons/

Albeit close to what we wanted to implement, it was not ticking all boxes for us. The aforementioned aggressor relied on older python code and was using an email-to-text service only available in the US. This aggressor also didn’t provide an “opt-out” which means you’ll have to kill the aggressor on the team server if you wanted to quit having notifications. So we decided to put on our coding hats and started exploring the world of aggressor coding ourselves.

Tackling two problems for the price of one.

Aggressor scripts are profile specific. This means that they get loaded up when you establish your user session and unloaded when you disconnect from the team server.
This is not a problem for normal operations, but for a notification server this might not be what we want. Luckily Raffi (that’s how Raphael Mudge often refers to himself) thought of this and introduced a binary packaged with Cobalt-Strike called “agscript”. This allows to run Cobalt-Strike in headless mode.

Both approaches have their advantages and disadvantages however:

  • Headless mode will need you to hardcode all your values in your aggressor script or figure out a way to parse them from your GUI event log window.
    This is a bit cumbersome and reduces flexibility in case you want to turn notifications on or off, (assuming you don’t want to get signal/text/mail spammed every time you lateral move).
  • “Graphical” mode (for the lack of a better term) will unload if you disconnect from your Cobalt-Strike session. You’ll therefore not receive any notifications if beacons check in while you are disconnected.

We decided we wanted full flexibility so we created both a headless and a graphical aggressor.

Meet our notification GUI

Let’s take a look at our graphical aggressor script first:

As you can see here the power of a graphic aggressor really shines, as you can toggle your notifications on and off and fine grain them however you’d like. Let’s take a look under the hood on how this actually works I’ll explain as we go.. 🙂

First of all we define global variables, these can then be used from any function we want.
Then we set debug level to 57, for more information about debug levels check the Sleep manual.

global ('$emailaddress');
global ('$email2textaddress');
global ('$signalphonenumber');
global ('$receivesignalmessages');
global ('$receivemails');
global ('$receivetexts');
global ('$scriptlocation');

debug(57);

Then we define a callback function. This callback function is called when the set preferences button is clicked and basically takes care of parsing the GUI and setting all global variables to their respective values. This also makes sure they persist when you close and reopen the window. The callback function also takes care of error handling in the GUI. For example if you set signal messaging to on but you are not providing a signal number.

sub callback {
	$receivemails = $3["emailchkbox"];
	$emailaddress = $3["email"];
	$email2textaddress = $3["txt2email"];
	$receivetexts = $3["textschkbox"];
	$signalphonenumber = $3["signalnumber"];
	$receivesignalmessages = $3["signalchkbox"];
	$scriptlocation = $3["script_location"];
	if(($receivemails eq 'true') && (strlen($emailaddress) == 0))
	{
		show_message("You won't receive emails because you did not input an email address!");
	}	
	else if(($receivetexts eq 'true') && (strlen($email2textaddress) == 0))
	{
		show_message("mail to text field is empty, you will not receive text messages");
	}
	
	else if (($receivesignalmessages eq 'true') && (strlen($signalphonenumber) == 0))
	{
		show_message("You won't receive signal messages because you did not input a phone number");
	}
	
	else
	{
		show_message("preferences saved successfully!");
	}
	
	if (checkError($error)) 
	{
		warn("$error");
	}	

}



The shownotificationdialog function is responsible of drawing our GUI and setting up some default values:

sub shownotificationdialog{
	$dialog = dialog("notification preferences",%(email => $emailaddress, txt2email => $txt2email,signalnumber => $signalphonenumber,script_location => "/home/kali/aggressors/mailer.py", emailchkbox => $receivemails,textschkbox => $receivetexts, signalchkbox => $receivesignalmessages),&callback);
	dialog_description($dialog, "Get notified when a new beacon calls home.");
	drow_text($dialog,"email","Your email address:");
	drow_text($dialog,"txt2email","Email address of the mail-to-text provider:");
	drow_text($dialog,"signalnumber","Your signal phone number in internation notation(+countrycode):");
	drow_text($dialog,"script_location","The location of the mail script on YOUR LOCAL HOST:");
	drow_checkbox($dialog,"emailchkbox","Do you want email notifications?");
	drow_checkbox($dialog,"textschkbox","Do you want text messages?");
	drow_checkbox($dialog,"signalchkbox","Do you want signal messages?");
	dbutton_action($dialog,"set preferences");
	dialog_show($dialog);
}

The popup aggressor hooks onto the Cobalt-Strike menu button in the Cobalt-Strike GUI. A list of hooks can be found here. Basically this function triggers the shownotificationdialog function whenever the button is pressed:

popup aggressor {
item "Notification preferences" {shownotificationdialog();}
}

The real “magic” however is in the on beacon_initial callback, this method will parse the hostname and the internal ip address from the beacon and will invoke the python script using Sleeps built-in exec function.

on beacon_initial {
local('$computer');
local('$internal');
$computer = beacon_info($1, "computer");
$internal = beacon_info($1, "internal");
if(($receivemails eq 'true') && (strlen($emailaddress) != 0)){
		 println("executing python $scriptlocation --ip $internal --computer $computer --receive-emails $emailaddress");
		 $handle = exec("python $scriptlocation --ip $internal --computer $computer --receive-emails --email-address $emailaddress");
	}
	
if(($receivetexts eq 'true') && (strlen($email2textaddress) != 0))
{
		println("executing python $scriptlocation --ip $internal --computer $computer --receive-texts --mail_totext $email2textaddress ");
		$handle = exec("python $scriptlocation --ip $internal --computer $computer --receive-texts --mail_totext $email2textaddress");
}
	
if (($receivesignalmessages eq 'true') && (strlen($signalphonenumber) != 0))
{
	println("executing python $scriptlocation --ip $internal --computer $computer --receive-signalmessage --signal-number $signalphonenumber");
	 $handle = exec("python $scriptlocation --ip $internal --computer $computer --receive-signalmessage --signal-number $signalphonenumber");
}
	

if (checkError($error)) 
	{
		warn("$error");
	}	

};

An important gotcha! As I already mentioned, your aggressor script is bound to your profile, it is not bound to the team server. As a result, “exec” will execute the command on YOUR machine, NOT THE TEAMSERVER. The notifier script has some dependencies the primary one is obviously python3 as it’s a python script. For signal integrating, it’s relying on signal-cli.

Distracted Boyfriend Meme - Imgflip

Meet our Notification User!

Our headless-mailer aggressor script shares a lot of similarities to our graphic aggressor script:

global ('$emailaddress');
global ('$email2textaddress');
global ('$signalphonenumber');
global ('$scriptlocation');
global ('$receivemails');
global ('$receivetexts');
global ('$receivesignalmessages');


$emailaddress = "";
$txt2emailaddress ="";
$signalphonenumber ="+countrycode";
$scriptlocation = "/some/dir/notifier.py";
$receivemails = "true";
$receivetexts = "false";
$receivesignalmessages = "true";


on beacon_initial {
local('$computer');
local('$internal');
$computer = beacon_info($1, "computer");
$internal = beacon_info($1, "internal");
if(($receivemails eq 'true') && (strlen($emailaddress) != 0)){
		 say("new beacon detected! Emailing $emailaddress");
		 println("executing python $scriptlocation --ip $internal --computer $computer --receive-emails $emailaddress");
		 $handle = exec("python $scriptlocation --ip $internal --computer $computer --receive-emails --email-address $emailaddress");
	}
	
if(($receivetexts eq 'true') && (strlen($email2textaddress) != 0))
{
		say("new beacon detected! sending an email to the email to text service!");
		println("executing python $scriptlocation --ip $internal --computer $computer --receive-texts --mail_totext $email2textaddress ");
		$handle = exec("python $scriptlocation --ip $internal --computer $computer --receive-texts --mail_totext $email2textaddress");
}
	
if (($receivesignalmessages eq 'true') && (strlen($signalphonenumber) != 0))
{
	say("new beacon detected! sending a signal message to $signalphonenumber");
	println("executing python $scriptlocation --ip $internal --computer $computer --receive-signalmessage --signal-number $signalphonenumber");
	 $handle = exec("python $scriptlocation --ip $internal --computer $computer --receive-signalmessage --signal-number $signalphonenumber");
}
}

As already mentioned, headless means that you’ll need to hardcode your variables instead of having the options in the GUI. Once you filled in the variables, you can launch the agscript for a headless connection to your Cobalt-Strike server:

./agscript 127.0.0.1 50050 notification-service demo  /home/jean/Documents/Tools/Agressors/headless-notifier.cna     

This can be run from anywhere you want, but your session needs to remain open so I recommend running it directly from your teamserver. The syntax is agscript <host> <port> <username> <password> </path/to/cna>. When done successfully a new user will have entered your server:

Now your notification service is ready for action! When a new beacon spawned the notification service will announce it in the event log window + will take the appropriate action.

Now check your email and/or phone, a new message will be waiting for you!

The real magic lies in the python script!?

Not really though, the python script is fairly trivial:

import argparse
import os
import smtplib
from email.mime.multipart import MIMEMultipart 
from email.mime.text import MIMEText  

#change your smtp login details here.
fromaddr = ""
smtp_password=""
smtp_server =""
smtp_port = 587

#change your signal REGISTRATION number here:
signal_registration_number =""

#leave these blank,will be dynamically filled through the aggressor.
smsaddr = ""
mailaddr = ""


parser = argparse.ArgumentParser(description='beacon info')
parser.add_argument('--computer')
parser.add_argument('--ip')
parser.add_argument('--receive-texts', action="store_true")
parser.add_argument('--receive-emails', action="store_true")
parser.add_argument('--receive-signalmessage', action="store_true")
parser.add_argument('--email-address')
parser.add_argument('--mail_totext')
parser.add_argument('--signal-number')

args = parser.parse_args()
toaddr = []

#take care off email and email2text:
if args.receive_texts and args.mail_totext:
    toaddr.append(smsaddr)
if args.receive_emails and args.email_address:
    toaddr.append(args.email_address)


#message contents:
hostname = args.computer
internal_ip = args.ip
body = "Check your teamserver! \nHostname - " + str(hostname) + "\nInternal IP - " + str(internal_ip)

#email logic
if toaddr:
	print("debug")
	msg = MIMEMultipart()
	msg['From'] = fromaddr
	msg['To'] = ", ".join(toaddr)
	msg['Subject'] = "INCOMING BEACON"
	msg.attach(MIMEText(body, 'plain'))
	server = smtplib.SMTP(smtp_server, smtp_port)
	server.starttls()
	server.login(fromaddr,smtp_password)
	text = msg.as_string()
	server.sendmail(fromaddr, toaddr, text)
	server.quit()

#signal-cli
if args.signal_number and args.receive_signalmessage:
	#take care of signal
	print(f"{args.signal_number}")
	os.system(f"signal-cli -u {signal_registration_number} send -m " + "\"" + str(body) + "\"" +  f" {args.signal_number}")

As you can see it’s nothing more than a simple email script with 1 OS command executor for the signal-cli.

This means that, whether you are execution graphical or headless you’ll need to have python3 installed (and available as your default “python”) and signal-cli installed as well in your global path.

If signal-cli is not in your global path you can adapt the python script to take this into account, it only requires a small change. The same would go for python3 not being your default “python” command.

Conclusions

We hope this “deep” dive into the world of Cobalt-Strike aggressors will open the gates for even more awesome aggressor scripts being developed!
Enjoy your beacon notification services, and good luck, have fun in your next engagements!

The code corresponding to this blog post can be found here:

https://github.com/NVISOsecurity/blogposts/tree/master/cobalt-strike-notifier

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.

Dynamic Invocation in .NET to bypass hooks

20 November 2020 at 08:45


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.

❌