Deceiving Defender: Classic Bypass
A practical workflow for bypassing Windows Defender disk detection using ThreatCheck, Ghidra, and Cpp
Last updated
A practical workflow for bypassing Windows Defender disk detection using ThreatCheck, Ghidra, and Cpp
Last updated
This post was inspired by Sektor 7's Malware Development Essentials and Malware Development Intermediate Courses. This post is not going to cover advanced methodologies, rather we're going to use a ThreatCheck, Ghidra, and a classic payload injection mechanism to demonstrate a workflow for bypassing Windows Defender detection.
The classic payload injection mechanic we're going to be looking at is self-injection using this code:
If we compile and run this code in our development environment we have a working implant.exe that successfully calls calc.exe!
I recommend you place your malicious .exe in a folder excluded from Windows Defender scanning, otherwise ThreatCheck will throw errors when Defender deletes/quarantines the file before ThreatCheck can access it.
Tip: ThreatCheck uses C:\Temp by default so it's prudent to add C:\temp to your exclusions as well
Running ThreatCheck on implant.exe shows us the exact sequence of bytes that Windows Defender is detecting. We can now take this information and search for these bytes in Ghidra to see what part of our PE file is being detected!
Tip: If you're working with a larger or more complex .exe (or just having trouble identifying "bad" code), it might be helpful to generate a .pdb at compile time
I recommend searching for byte patterns that (approximately) bisect the detected bytes, so in this case I'm going search implant.exe's memory for 00 00 B0 21 01 40 01 00 in Ghidra.
It looks like the value that’s being flagged is a pointer, so let's follow that around and see if it gets us anywhere. If we follow the XREFs and pointers it looks like the first thing we hit is a library function. We know that the library function itself is not what's causing us to get caught by Defender. The problem is likely our specific use of the function.
We can tell from the distribution of this memory that likely something is going to be written here and it's that predictable behavior that is being detected by Windows Defender.
Because our payload is in the .text section, it's likely that our payload is what's causing the program to flag at this point in our code. We can test that by removing the payload and running ThreatCheck on our .exe again.
Tip: Another way to check this is by making the payload a global and therefore moving it into the .rodata section of your program. ThreatCheck will have any easier time finding your payload as the portion of the code that's getting flagged.
One of the easiest ways to bypass Windows Defender is to break its ability to analyze our program. There's a pretty standard set of checks we can implement.
[Intentionally Left Blank]
We implement the checks outlined in part four and compile our program.
Our executable can now survive on disk without any further obfuscation!
In this case we didn't have to go further than some pretty basic emulation checks. This is often not enough to bypass end-user security solutions. The addition of Ghidra to the ThreatCheck -> .cpp workflow (such that we use ThreatCheck -> Ghidra -> .cpp) allows us to tailor our response to the specific detection capabilities of Windows Defender and achieve malicious code execution.
Black Hat USA 2018 - Windows Offender Reverse Engineering Windows Defender's Antivirus Emulator