How to get around NGAV & EDR - 3/3

Last part in the series of how to establish a reverse shell on a client running EDR/XDR

Finally we get to the interesting parts. After evading Windows Defender in the last part I am now going to show how to get around EDR and why the same exploit that was able to evade Defender, will likely get blocked by most EDR-solutions.


Build msfvenom-payload, start metasploit-handler, disconnect victim from internet and run the exploit the same way we were able to evade Defender.

Well, color me surprised…not!

Back to prep

If you read my first blog-post, you’ll know that the main topic I want to talk about is APIs and API Unhooking. This is exactly what we are going to try next. If the AV & EDR is unable to see what APIs we’re calling it will hopefully not interfere. Combined with running from memory and without touching disk, that should hopefully give us a working shell, despite any EDR-solution. To do so we will need to implement a bit of code to actually be able to unhook those APIs. Fortunately we can use Pinvoke again and simply copy & paste any code we need.

Unhooking in detail

We know that dlls can get hooked by EDRs or Antivirus-solutions in general. What happens usually is that the EDR/AV will hook parts of the .text-section of a specific dll and inject code into it and/or change it, to be able to scan anything that goes through. One interesting library is called ntdll.dll and can be found in C:\Windows\system32. Ntdll.dll exports a lot of interesting functions that usually get abused by malware like working with files, threads or tokens. Because ntdll.dll is the closest you can get to the kernel (or the lowest), this library is always heavily monitored and any EDR worth its salt will scan anything that goes through.

The EDRs task is to replace parts of the code in memory with jmp to its own scanner-engine. The library is then hooked or at least parts of it or wherever the EDR changed parts of the exported functions. The original file is still on disk however (without any hooks) and what we will do now is simply replacing those parts with a fresh section of the original file. To replace those parts the file will be loaded into memory (into another region) which we will call ntdll2 for now and then parts of ntdll2 (.text-section) will be copied and will then replace the .text-section of ntdll.dll (the hooked version) in memory. Then protections of the memory region will be reapplied to the (overwritten) .text-section of the original ntdll.dll in memory. What also works and what we are going to do in this example is to rewrite the hooked functions and remove or overwrite the jmp-instruction with the original code and thereby “unhooking” the hooked API-call.

As mentioned, I am not the first to come up with this idea and I just write down and combine what I learned so far up to this point. One big pool of knowledge I used several times for different scenarios is unhook ntdll.dll in c++ which we simply need to translate into c# and then call it before we call our shellcode. In fact, makosec did exactly the same thing in his blogpost over here and here. Combining it all into one we should end up with a victim that is unable (blind) to scan code that goes through ntdll.dll.

Even better, with SharpUnhooker we are able to not only unhook several dlls but also patch AMSI and ETW in the process. Combine it all into one and your code should look like the following

While “Class1” is the actual xor-decoder with meterpreter and all the others SharpUnhooker. I took out unneeded code, changed vars and class and function names and then compiled it all into one. Then tried to run the same thing again…

This obviously did not work and 2 possible reasons here. First, meterpreter still stands out and is still a risky thing to run on any host. As soon as the AV/EDR finds meterpreter running in memory it will get blocked. The other is the weird connection here. Not many services connect to another host on port 9999. What I did now is change the Port to 53 and change the assembly information and change all vars and function and class names again. This obviously does not change meterpreter itself so in a real scenario you would propably be better off with a normal shell instead of meterpreter.
This time we end up with a working meterpreter-shell despite running AV/EDR.

Ok, cool

That worked but may fail with some EDRs as it is very uncommon for any application to patch/unhook dlls in general. Another problem is that to unhook we still need function-calls that may be monitored now or in the future. Even if not, it still produces noise that could potentially reveal an attacker. There are generally 3 other ways to get your malicious code to run. One, load a fresh copy of the library into memory, where for example the ntdll.dll from C:\Windows\system32\ntdll.dll will be loaded into another region and then simply used instead of the original loaded and hooked version.

Second approach is to run shellcode directly, which I am going to show next time which should be able to get around most AV/EDR-solutions. Downfall here is that different versions, different service packs and so on will force you to adapt your code. Fortunately someone did the hard work again and we can use SysWhispers2 to generate header-files that we can use to implement direct syscalls into our code.

Third approach is a bit different but very interesting. The idea here is that any hooked or rewritten function-call must point to the actual function in memory somewhere. Instead of patching it out or copying a fresh version from disk, why not follow the whole thing and see where we end up. This is (without detail) the idea of FireWalker written by MDSec.

With this technique we follow a function call until we end up at the injected hook or rather the jmp instruction. Instead of going through the jump code we “simply” point the execution to the original code or function. This can be done by using trap flags which means executing each instruction one by one and then analyzing the next about to be executed instruction. As with every other technique this one also comes with a few downfalls. First, its slow. Setting the Trap Flag and executing instruction one by one will slow down the execution of your code. Second, some EDRs copy the entire function into another memory region and therefore the “real” code is the one that EDR controls.

Next time I will try to do syscalls directly and then do a deep-dive into the shown ways of evading EDR…