An Introduction to Bypassing Consumer Mode EDR Hooks

Lately I bought again into malware analysis and was going by means of a few of my outdated notes for an article I’m writing.
Whereas cross-referencing notes towards outdated weblog posts, I spotted that I by no means really revealed nearly all of my work on system calls and consumer mode hooking.
Since my subsequent article would require that readers be conversant in each ideas, I made a decision to take the time to shine up and publish the remainder of my analysis.
And hey, who’s to show down a free additional weblog publish?

While this text is designed to face by itself, in case you’re , yow will discover my earlier articles on these matters right here, right here, right here and right here.
Surprisingly, regardless of all this analysis being over a decade outdated, it’s nonetheless fully related as we speak. The extra issues change, the extra they keep the identical, I assume?

System calls are the usual option to transition from consumer mode to kernel mode.
They’re the fashionable, sooner, model of software program interrupts.

The system name interface is extraordinarily complicated, however since most of it isn’t related to what we’re doing I’m simply going to offer the next degree abstract.
For probably the most half, you received’t actually need an in-depth understanding of the way it works to make the most of these methods, however it may be useful to know.

On Home windows, the kernel has a desk of features which can be allowed to be known as from consumer mode.
These features are generally known as System Providers, Native operate, or Nt Features.
They’re the features that start with Nt or Zw and are positioned in ntoskrnl.exe.
The desk of system companies is called the System Service Descriptor Desk, or SSDT for brief.

To name a system service from consumer mode a system name should be carried out, which is completed through the syscall instruction.
The appliance tells the kernel which system service it needs to name by storing its ID into the eax register.
The System Service ID (usually known as a System Service Quantity, System Name Quantity, or just SSN), is the index of the operate’s entry inside the SSDT.
So, setting eax to 0 will name the primary operate within the SSDT, 1 will name the second, 2 will name the third, and so forth…

the lookup seems to be one thing like this entry = nt!KiServiceTable+(SSN * 4).

The syscall instruction causes the CPU to change into kernel mode and invoke the system name handler, which takes the SSN from the eax register and calls the corresponding SSDT operate.

Let’s say an software calls the OpenProcess() operate from kernel32.dll to open a deal with to a course of.


A disassembly of kernel32!OpenProcess().

As you possibly can see, all of the operate actually does is ready up a name to NtOpenProcess(), which is positioned in ntdll.dll.

Now, let’s check out the NtOpenProcess() logic.


A disassembly of ntdll!OpenProcess().

Inside NtOpenProcess(), there’s hardly any code in any respect.
It is because like all features starting with Nt or Zw, NtOpenProcess() is definitely positioned within the kernel.
The ntdll (consumer mode) variations of those features merely carry out syscalls to name their kernel mode counterparts, which is why they’re sometimes called system name stubs.

In our case, the SSN for NtOpenProcess is 0x26, however this quantity adjustments throughout Home windows model so don’t anticipate it to be the identical for you.

From a simplified high-level view, the decision stream seems to be considerably like this:


A simplified x64 system name stream.

Here’s a extra detailed overview of an x86 system name stream from a earlier article.


A extra detailed x86 system name stream.

Observe: from consumer mode, each the Nt and Zw model of a operate are an identical. From kernel mode the Zw operate takes a barely totally different path. This is because of the truth that Nt features are designed to be known as from consumer mode, due to this fact do extra intensive validation of operate parameters.

Since Microsoft launched Kernel Patch Safety (aka PatchGuard) in 2005, many modifications to the kernel at the moment are prevented.
Beforehand, safety merchandise monitored consumer mode calls from contained in the kernel by hooking the SSDT.
Since all Nt/Zw features are applied within the kernel, all consumer mode calls should undergo the SSDT, and are due to this fact topic to SSDT hooks.
Patch guard makes SSDT hooking off-limits, so many EDRs resorted to hooking ntdll.


A have a look at the place safety merchandise place hooks earlier than and after patch guard.

For the reason that SSDT exists within the kernel, consumer mode purposes weren’t capable of intervene with these hooks with out loading a kernel driver. Now, the hooks are positioned in consumer mode, alongside the applying.

So, what does a consumer mode hook appear like?


An instance of a ntdll operate earlier than and after hooking.

To hook a operate in ntdll.dll, most EDRs simply overwrite the primary 5 bytes of the operate’s code with a jmp instruction.
The jmp instruction will redirect code execution to some code inside the EDR’s personal DLL (which is robotically loaded into each course of).
After the CPU has been redirected to the EDR’s DLL, the EDR can carry out safety checks by inspecting the operate parameters and return tackle.
As soon as the EDR is completed, it might probably resume the ntdll name by executing the overwritten directions, then leaping to the placement in ntdll proper after the hook (jmp instruction).


Management stream instance for hooked ntdll operate.

Within the above instance, NtWriteFile is hooked. The inexperienced directions are the unique directions from NtWriteFile.
The primary 3 directions of NtWriteFile have been overwritten by the EDR’s hook (a jmp that redirects execution to a operate named NtWriteFile in edr.dll).
Every time the EDR needs to name the actual NtWriteFile, it executes the three overwritten directions, then jumps to the 4th instruction of the hooked operate to finish the syscall.

While EDR hooks could fluctuate barely from vendor to vendor, the principal remains to be the identical, and all share the identical weak spot: they’re positioned in consumer mode.
Since each the hooks and the EDR’s DLL must be positioned inside each course of’s tackle area, a malicious course of can tamper with them.

There are a large number of the way to bypass EDR hooks, so I’ll cowl simply the primary ones.

EDR Unhooking

For the reason that hooked ntdll is positioned in our personal course of’s reminiscence, we will use VirtualProtect() to make the reminiscence writable, then overwrite the EDR’s jmp instruction with the unique operate code.
With the intention to change the hooks, we’ll in fact have to know what the unique meeting directions have been.
The commonest approach to do that is by studying the ntdll.dll file from disk, then evaluating the in-memory model towards the disk model.
This assumes the EDR doesn’t detect or forestall manually studying ntdll.dll from disk.

The principle downside of this technique is that the EDR may simply periodically examine the reminiscence of ntdll to see if its hooks have been eliminated.
If the EDR detects its hooks have been eliminated, it could write them again, or worse, terminate the method and set off a detection occasion.
Whereas the hooks could have to be positioned in consumer mode, checking them could be completed from kernel mode, so there’s not a lot we may do to stop it.

Manually Mapping DLLs

As a substitute of studying a clear copy of ntdll from disk to allow us to unhook the unique ntdll, we may simply load the clear copy into our proccess’s reminiscence and use that as an alternative of the unique.
Since features like LoadLibrary() and LdrLoadDll() don’t permit the system to load the identical DLL twice, now we have to load it manually.
The code for manually mapping DLLs could be intensive and in addition vulnerable to errors or detection.

DLLs usually additionally carry out calls to different DLLs, so we’ll both be restricted to solely utilizing features from our manually loaded ntdll, or loading a second copy of each DLL we want and patching them to solely make use of different manually loaded DLLs, which might get fairly messy.
There’s additionally an excellent probability of detection if an antivirus does a reminiscence scan and sees a number of copies of each DLL loaded into reminiscence.

Direct Syscalls

As mentioned earlier, consumer mode Nt/Zw features don’t really do something apart from execute syscalls.
So we don’t actually need to map a complete new copy of ntdll simply to do some syscalls.
As a substitute, we will implement the syscall logic straight into our personal code.
All we have to do is transfer the SSN for the operate we wish to name into the eax register, then execute the syscall instruction.

It’s so simple as

__asm {
  mov r10, rcx
  mov eax, 0x123
  syscall
  ret
}

Sadly, as a result of the EDR’s hook sometimes overwrites the instruction that units the eax register, we will’t merely simply extract it from the hooked operate.
However…there’s a number of methods we will discover out what it’s.

Studying a clear copy of ntdll

You’re most likely uninterested in this concept by now, however we may simply learn a clear copy of ntdll from disk and extract the SSNs from there.
For the reason that SSN is all the time put into the eax register, all we have to do is scan the operate we wish to name for the mov eax, imm32 instruction.
However, what if we wish a way that isn’t just a few variation of studying ntdll from disk? Properly, don’t worry!

Calculating the system name quantity based mostly on operate order

System name ids are indexes, and due to this fact sequential.
If the SSN for the operate we wish to name is 0x18, then the one straight earlier than it’ll probably be 0x17 and the one straight after, 0x19.
For the reason that EDR doesn’t hook each Nt operate, we will merely seize the SSN from the closest non-hooked operate, then calculate the one we wish by including or subtracting what number of features are between it and our goal operate.


NtAllocateVirtualMemory is hooked by the EDR, however the operate earlier than and after it aren’t. The operate earlier than it’s system name quantity 0x17, and the operate after it’s 0x19. We are able to simply assume that the SSN we wish is 0x18.

This technique does have one flaw although: we will’t 100% assure system name numbers will stay sequential without end, or the DLL received’t skip a number of.

Hardcoding

The only technique of all, is to simply laborious code the system name numbers.
While they do change from model to model, they haven’t modified an enormous quantity previously. It’s not an excessive amount of work to detect the OS model and cargo the right SSN set.
The truth is, j00ru has kindly revealed an inventory of each system name quantity for each Home windows model.
The one downside of this technique is the code many not robotically work on new Home windows model if the system name numbers change.

The issue with direct syscalls

Direct syscalls have been the go to for bypassing consumer mode hooks for over a decade. I really first experimented with this technique myself approach again in 2012. Sadly, a lot work has been completed to attempt to forestall this sort of bypass.
The commonest detection is by having the EDR’s kernel mode driver examine the callstack.

Though the EDR can now not hook loads of locations within the kernel, it might probably use monitoring performance offered by the working system, resembling:

  • ETW occasions
  • Kernel Callbacks
  • Filter Drivers

If we carry out a handbook syscall, and someplace alongside the best way the kernel operate we name hits any of the above, the EDR may take the chance to examine the callstack of our thread.
By unwinding the decision stack and inspecting return addresses, the EDR can see all the chain of operate calls that led to this syscall.

If we have been to carry out a traditional name to, say, kernel32!VirtualAlloc(), the callstack could appear like so:


The callstack of a name to VirtualAlloc().

On this case the decision to VirtualAlloc() is initiated by ManualSyscall!essential+0x53.
The related components of the callstack so as of name are:

  1. ManualSyscall!essential+0x53
  2. KERNELBASE!VirtualAlloc+0x48
  3. ntdll!NtAllocateVirtualMemory+0x14
  4. nt!KiSystemServiceCopyEnd+0x25

This tells us (or the EDR) that the executable (ManualSyscall.exe) known as VirtualAlloc(), which known as NtAllocateVirtualMemory(), which then carried out a system name to transition into kernel mode.

Now let’s have a look at the decision stack after we do a direct syscall:


the callstack for a direct syscall to NtAllocateVirtualMemory().

The related components of this callstack so as are:

  1. ManualSyscall!direct_syscall+0xa
  2. nt!KiSystemServiceCopyEnd+0x25

Right here it’s clear the kernel transition was triggered by code inside ManualSyscall.exe and never ntdll. However, what’s the issue with this?

Properly, on programs like linux it’s fully regular for software to provoke system calls straight. However do not forget that I discussed system name ids change between Home windows variations?
Because of this it’s extremely impractical to write down Home windows software program that depends on direct system calls.
Because of the truth ntdll already implements each system name for you, there’s virtually no motive to do a handbook syscall. Except, in fact, you’re writing malware to bypass EDR hooks. Are you writing malware to bypass EDR hooks?

As a result of direct system calls are such a robust indicator of malicious exercise, extra subtle EDRs will log detections for system name that originated outdoors ntdll.
Reality be informed, you possibly can nonetheless get away with it loads of the time, however the place’s the enjoyable in that?

Oblique syscalls

Most EDRs write their hooks initially of the Nt operate, overwriting the SSN however leaving the syscall instruction intact.
This permits us to make the most of the syscall directions already offered by ntdll as an alternative of bringing our personal.
We are able to simply arrange the r10 and eax registers ourselves, then leap to the syscall instruction contained in the hooked ntdll operate (which comes after the EDR’s hook).


Code stream for an oblique syscall.

Observe: we don’t strictly want the check or jnz instruction, these are simply there for backwards compatibility.
Some historic CPUs don’t help the syscall instruction and use int 0x2e as an alternative.
The check instruction checks if syscalls are enabled, and if not, falls again to software program interrupts.
If we want to help these programs, we may simply carry out the examine ourselves, then leap to the int 0x2e instruction (which can be positioned inside Nt operate) if wanted.

Identical to with direct syscalls, we nonetheless want the system name quantity to place into eax, however we will use all the identical methods beforehand detailed within the direct syscalls part.

Establishing a system name this fashion will give us a name stack that appear like the next:


The decision stack for an oblique system name.

As you possibly can see, the decision stack now look as if the decision got here from the ntdll!NtAllocateVirtualMemory() insted of our executable, as a result of technically it did.

One subject we may run into is that if the EDR hooks or overwrites the syscall instruction a part of the Nt name.
I’ve by no means seen this occur, however it may in principle.
On this case, we may leap to a syscall instruction inside a unique, non-hooked, Nt operate.
This may nonetheless bypass EDRs which solely validate that the decision title from ntdll, however would fail any checks verifying that the kernel operate known as matches the ntdll operate the syscall got here from.

The bigger drawback is, what if the EDR checks extra than simply the primary return tackle.
Not simply the place the syscall got here from, however who known as the operate that executed the syscall.
If we’re doing oblique syscalls from some shellcode positioned in dynamically allotted reminiscence, then the EDR goes to see that.
Calls coming from outdoors a legitimate PE part (exe or DLL reminiscence) are pretty suspicious.

Moreover, for the reason that operate is hooked by the EDR, the EDR’s hook could be anticipated to look within the name stack.
I’m really undecided which EDRs, if any, examine this. However, as you possibly can see right here it’s clear from the decision stack
that we bypassed the EDR hook.


The EDR’s hook is seen within the name stack from a daily name however not from an oblique syscall.

Ideally, we wish to faux extra than simply the system name return tackle.
An fascinating resolution to that is name stack spoofing, which I’m most likely going to cowl in a separate article.
With name stack spoofing it’s doable to faux all the name stack, however it may be difficult to implement with out crashing the calling thread.

You’ll be able to learn extra about name stack spoofing right here:
Spoofing Name Stacks To Confuse EDRs – WithSecure
Lengthy Stay Customized Name Stacks – DarkVortex

so there you may have it, you now perceive the fundamentals of consumer mode EDR hooks and a few frequent methods to bypass them.
This information can be essential for my subsequent article, which can go deeper into how EDR hooks work and element some different strategies of bypassing them.

Half 2: https://malwaretech.com/2023/12/silly-edr-bypasses-and-where-to-find-them.html

Leave a Reply

Your email address will not be published. Required fields are marked *