How to get around NGAV & EDR
Working as part of a Red Team or as Penetration tester, you surely know how awesome it feels to finally get a SYSTEM-Shell on your system of choice. Owning the system despite all the efforts of the defenders is always satisfying.
Even better if you were able to get around AV with a 0day or an obfuscated tool. However, too many times the AV is in the way, and you are lacking permissions to kill or suspend the service.
What you need here is a FUD (fully undetectable) exploit or simply something that does not trigger the AV at all. While you can find several of those on GitHub, you would be better off if you could create your own shell/exploit, perfectly suited for the domain in question.
In this blog-series I am trying to take a look at NGAV (Next-Gen-Anti-Virus) and the techniques used to stop exploits from running and most interesting, how to get around NGAVs.
The final goal is to launch a shell and connect back to my attacker machine on a fully patched system with and without a NGAV (Windows Defender / NGAV)
The techniques shown here are not new nor are the code snippets used here. I am simply combining what I learned so far.
NGAV vs AV
I hate all those buzzwords and if you ever had to deal with an AV-Vendor, you’ll know that they like to drown you in buzzwords that sound cool and fancy (“Intercept X Advanced EDR, XDR, MTR”) and will surely confuse and hopefully convince the customer into buying their AV-Solution.
Drained of all the Sales-talk it boils down to added scanning engines on top. NGAVs will in addition to signature detection check heuristics and behavior. While traditional AVs only check the patterns of a file and then decide if it is malicious, NGAVs will also check the timestamp, size, imports, exports and so on.
Everything was better/easier in the past
What does this mean for malware authors? A few years ago, it was mostly enough to take a known exploit, rewrite it a few parts and voila…you got yourself a working exploit. Nowadays it got increasingly harder and an exploit that is undetected today may be a red flag tomorrow.
However, that does not mean it is impossible it simply means you need to work harder to hide your payload. There are several ways to do so with new vectors emerging all the time. For example, the relatively new programming language NIM was not on the radar of most AV-Vendors and it was pretty simple to generate FUDs for quite a while (byt3bl33d3r/OffensiveNim)
In this series I am going take a look at 2 techniques and how they work under the hood. There are more out there which I may cover later on.
- API unhooking which is basically unhooking API Hooks of the library you need for your exploit
- Direct syscalls to avoid using hooked functions such NtCreateFile and so on
To understand API unhooking we first need to understand API Hooks. Windows (other OS as well of course) offer the system and running programs several APIs to do different tasks. For example, creating, reading or deleting files or opening up a socket or querying the name of the current process will be done through APIs stored in libraries such as user32.dll or ntdll.dll (there are more).
There are certain API-Calls that are common with malware such as VirtualAlloc to allocate memory or VirtualProtect to change the protection of a certain memory region or NtCreateThread to create a thread.
Those API-Calls are monitored by AVs and if triggered will flag the file as potentially malicious or block it right away. The AV itself is able to do so because it injected itself into the code and will scan anything that is supposed to go through that API-Call beforehand.
If our malicious example would call NtCreateThread via ntdll.dll it would first go through the AV (if hooked) and be checked for malicious code and then either get blocked or declared as clear and then routed back to the original API. It should be noted, AVs are also able to intercept kernel related calls and but this will not be part of this series.
What does that mean though? Let me show you on a new W10 machine. One with AV and one with only Windows Defender.
Without going too much into detail we can differentiate hooked or not hooked functions by looking at the disassembled code of Windows-functions. Unaltered (hooked) functions are missing a MOV-instruction on top and another another JMP-Instruction before or afterwards.
What happens here is that instead of working on the API-Call, the CPU will jump to the specific memory region, go through the scanning-routine and then return afterwards and continue on working on the API-Call.
Any malicious activity will be easy to spot that way. Another indicator is the handles on any running program. Open up powershell.exe and you can see that the AV injected itself into the powershell-process and has several handles open.
AVs are hooking potentially malicious API-Calls and inject their own code into it. No matter the encryption or obfuscation, when code is in memory and about to run it will be scanned by the AV before it goes through the API-Calls. So…how do we get around it?
There are several different techniques, but it boils down to either avoid the hooked functions somehow or patch them out. It is also possible to directly do syscalls without going through the API which I will cover later on. Another approach is to map a fresh section of ntdl.dll (or another library) and therefore not go through the hooked version of the library.
This means, if your malicious file is able to get around heuristic and signature detection and will NOT go through the runtime detection the AV is basically blind and not looking your way. Therefore, the AV cannot flag anything because it is not going through the scanning-routine.
Instead of going through the heavily monitored APIs we can also call those functions directly. If we were able to directly call NtCreateProcess for example instead of going through the monitored ntdll.dll we would avoid the AV-scanner. An interesting example of this is Dumpert were direct syscalls and API unhooking will allow the attacker to run mimikatz on the victim’s system despite EDR and AV installed.
Instead of going through the API itself we do those syscalls ourselves (kernel, 0-ring). This works because we can see what the API is actually doing (example, ZwCreateProcess) and by simply replicating it we could avoid hooked and monitored APIs.
What this means in practice is to dissasemble the actual code (ZwCreateProcess in this case) and taking those instructions (mov eax,B9) and integrate them into our own code. Downside is here, as already mentioned the code itself is not consistent through out each Windows OS and even service packs.
First of all, let’s see what happens if we simply run a simple reverse-shell on a fully patched W10 with Defender enabled. I started off with something that should be easily spotted. A simple executable connecting back to the attacker, created with msfvenom.
After copying it to /var/www/html on my attacker machine and trying to download the file via Microsoft Edge, I immediately get blocked by Windows Defender….as expected.
Next thing to try is Powershell. We can try to download the file to disk and run it. In this case I am simply downloading it to appdata as test.exe and run it from there…or at least I tried. As expected Defender is blocking the attempt right away.
We could now go ahead and try a few more things and fail each step like run-from-memory, lolbins, process injection and propably a few more. However, each technique alone will not be enough to get around even Defender. That is why we skip ahead and combine a few different techniques.
All in one
First thing to do is create a msfvenom-payload in c# (or any other language).
Take this code, encode it again with your own key (Powershell) and then take that payload and safe it somewhere. Now we will use Pinvoke to copy paste code into our project to be able to use Windows-APIs. This will be used to not only create a thread and inject our payload into it but also to distract AVs by fooling heuristics.
With VirtualAllocExNuma and the Sleep-function we will potentially fool heuristics. The idea behind it, allocate memory, if it works simply continue, if not then wait for 5 seconds and then end the program.
The rest of the code (Source: purpl3f0x) will consist of allocating memory, copying the shellcode into the memory-region and executing it. Compiling it all should leave you with a dll you can then use to load and execute from memory via powershell. The whole thing will be done through 2 powershell-scripts.
The result? With only a few lines of code we were able to get a shell on a fully patched W10 without even touching on the initial subject of API-Unhooking. While this works for Windows Defender, you have to put in a bit more effort for most EDR solutions.
Finally we get to the interesting parts. After evading Windows Defender we now want to get around EDR.
Build msfvenom-payload, start metasploit-handler, disconnect victim from internet and run the exploit the same way we were able to evade Defender. While this was not enough for Windows Defender it will likely not work with an EDR. Well, color me surprised…not!
Back to prep
If you read the first part of this 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.
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.
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.
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.