Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Application control flips the default security posture of a Windows endpoint from *default-allow, deny known-bad* to *default-deny, allow known-good*. Microsoft ships two mechanisms for this: AppLocker, a user-mode rule engine descended from Software Restriction Policies, and Windows Defender Application Control (WDAC), a kernel-enforced Code Integrity policy that grew out of Device Guard. Both let an organization say “only these binaries, scripts, and installers may run” instead of trying to enumerate every possible bad one. Done well, application control neutralizes entire classes of attack — arbitrary EXE drops, unsigned tooling, most commodity malware — without needing a signature for any specific sample.
Done poorly, it is close to theatre: a policy left in audit mode logs violations without blocking anything, a path rule that includes a user-writable directory reopens the exact hole the policy was meant to close, and a Microsoft-signed LOLBin that the policy trusts by design can still be abused to run arbitrary code. This article covers how AppLocker and WDAC are structured, the concrete configuration mistakes that create bypasses, and how to design and monitor a policy that actually holds.
Attack Prerequisites
Bypassing application control does not require a memory-corruption exploit in most cases — it requires finding where the policy’s own design leaves a gap:
- Code execution as a standard user on a host where AppLocker/WDAC is enforced, plus the ability to write a file (for path-rule bypasses) or invoke an already-trusted binary (for signed-binary bypasses).
- A policy in Audit mode (
EnforcementMode="AuditOnly"for AppLocker,Enabled:Audit Modefor WDAC) that logs but does not block — common during rollout and sometimes never flipped to enforce. - An overly broad Path rule that includes a location a standard user can write to (
%OSDRIVE%\Users\*\AppData\...,%TEMP%, a shared build share) rather than only admin-writable paths. - A rule collection that is simply not enabled — AppLocker’s Script, MSI, DLL, and Packaged App collections are independent of the Exe collection and are frequently left unconfigured.
- A trusted but abusable binary already permitted by policy — WDAC in particular allows *any* Microsoft-signed binary unless explicitly denied, and many of those binaries can execute arbitrary code on your behalf.
How It Works
AppLocker evaluates rules per rule collection — Executable, Windows Installer, Script, DLL, and Packaged app are each independent. Within a collection, rules are one of three condition types: Path (a file or folder location, weakest — controls nothing about the file’s identity), Publisher (Authenticode signer plus optional product/file name and version range, strong when pinned tightly), and Hash (a specific file’s SHA-256, strongest but breaks on every update). Once any Allow rule exists in a collection, that collection becomes default-deny for everything else — but only for that collection; if the DLL collection has no rules, DLL loading is unrestricted regardless of how tight the Exe collection is. AppLocker itself runs in user mode as part of the Application Identity service (AppIDSvc), which must be running for enforcement to apply.
WDAC is fundamentally different: it is enforced by the kernel’s Code Integrity component (CI.dll/ci.dll in the kernel), so it governs user-mode binaries, DLLs, drivers, and — depending on policy options — scripts and MSIX, in a way that cannot be bypassed by simply renaming or relocating a file the way weak AppLocker path rules can. A WDAC policy is an XML document (SiPolicy schema) compiled to a binary .cip and, on current Windows, deployed to %windir%\System32\CodeIntegrity\CiPolicies\Active\, refreshed with CiTool.exe --refresh or applied at boot. Policies combine a Base policy with optional Supplemental policies, and rules are built from Signers (certificate chain trust, optionally scoped by publisher/product/version — the WDAC analogue of AppLocker’s Publisher rule) and FileRules (explicit hash allow/deny). Two policy options shape real-world exposure: Managed Installer, which trusts anything written by a designated installer process, and the Intelligent Security Graph (ISG) heuristic, which trusts files with sufficient reputation — both convenience features that widen the trust boundary beyond what the explicit rules say.
Because WDAC’s default posture trusts the entire Windows-signed binary set unless a policy explicitly narrows it, its real attack surface is the living-off-the-land binary (LOLBin) problem: msbuild.exe, installutil.exe, mshta.exe, regsvr32.exe, rundll32.exe, and dozens of other Microsoft-signed tools can execute attacker-supplied code or scripts, and a policy that merely says “allow Microsoft-signed” allows all of them by construction. Microsoft publishes a recommended block list (Microsoft Recommended Block Rules) specifically to close this gap, and any policy that does not incorporate it is trusting a much larger set of executables than the designer likely intended.
Vulnerable Code / Configuration
The single most common AppLocker gap is a Path rule that reaches into a user-writable directory. This looks reasonable at a glance — it allows “everything under Program Files” plus a folder for a legacy app — but the second rule below is the bug:
<AppLockerPolicy Version="1">
<RuleCollection Type="Exe" EnforcementMode="Enabled">
<FilePathRule Id="a1f1b6b0-0000-0000-0000-000000000001"
Name="Allow Program Files" Action="Allow">
<Conditions>
<FilePathCondition Path="%PROGRAMFILES%\*" />
</Conditions>
</FilePathRule>
<!-- GAP: %TEMP% under a user profile is user-writable. -->
<FilePathRule Id="a1f1b6b0-0000-0000-0000-000000000002"
Name="Legacy app temp launcher" Action="Allow">
<Conditions>
<FilePathCondition Path="%OSDRIVE%\Users\*\AppData\Local\Temp\*" />
</Conditions>
</FilePathRule>
</RuleCollection>
</AppLockerPolicy>
XMLAny standard user can drop an arbitrary binary into their own %TEMP% and run it — the rule was written to solve one legacy-app problem and accidentally allow-listed every user-writable temp folder on the estate. The WDAC equivalent gap is subtler: a policy shipped for pilot testing that never had its enforcement flag flipped, so it audits forever instead of blocking:
<SiPolicy xmlns="urn:schemas-microsoft-com:sipolicy">
<Rules>
<!-- GAP: still in audit mode; nothing is actually blocked. -->
<Rule>
<Option>Enabled:Audit Mode</Option>
</Rule>
<Rule>
<Option>Enabled:Unsigned System Integrity Policy</Option>
</Rule>
</Rules>
<!-- ...Signers / FileRules / SigningScenarios omitted... -->
</SiPolicy>
XMLEnabled:Audit Mode is essential during rollout, but a policy that is still auditing months after deployment provides visibility with zero enforcement — the CodeIntegrity operational log fills with block candidates that a real attacker simply runs through unimpeded. The second option, Enabled:Unsigned System Integrity Policy, is a legitimate way to avoid the enterprise-CA signing workflow, but it also means the policy itself has no cryptographic protection against tampering by anyone with admin rights on the box.
Walkthrough / Exploitation
Reconnaissance starts by reading the effective policy rather than guessing at it — both engines expose their active rule set to a standard user:
# AppLocker: dump the effective policy (local + GPO-merged) as XML.
Get-AppLockerPolicy -Effective -Xml | Out-File effective.xml
# WDAC: list active policies and confirm enforcement vs audit.
CiTool.exe -lp -json
PowerShellFor an AppLocker path-rule gap, the operator confirms the rule includes a writable location, then simply writes and runs the payload there — no renaming or signing tricks required because the rule matches on location alone:
:: Confirm a benign binary is blocked outside the allowed path (baseline).
copy calc.exe C:\Windows\Temp\test.exe
:: Now abuse the allowed writable location from the policy above.
copy payload.exe "%TEMP%\update.exe"
"%TEMP%\update.exe"
TEXTWhere policy is genuinely tight on Path/Publisher/Hash for the Exe collection, the reliable bypass is to stop trying to run an unapproved binary at all and instead abuse a binary the policy already trusts. WDAC’s default trust of Microsoft-signed code makes this systematic — msbuild compiles and executes inline C# from an XML project file, and it is signed by Microsoft, so a policy relying purely on publisher trust admits it by default:
:: MSBuild executes attacker-supplied inline tasks; Microsoft-signed so
:: it passes a policy that trusts the Microsoft publisher without the
:: recommended LOLBin block rules layered on top.
msbuild.exe payload.csproj
TEXTVerification, for both attacker and auditor, is the same enumeration step run after execution: check whether the event log recorded an audit hit (policy would have blocked in enforce mode) or an actual block, which tells you precisely which control needs tightening.
Note: AppLocker rule collections fail closed independently — enabling strict Exe rules while leaving the Script collection unconfigured means
.ps1/.vbs/.jsexecution is entirely unrestricted, a gap red teams look for immediately. Likewise,Get-AppLockerPolicy -Effectiveonly reflects what actually applies to the box; a GPO with correct rules that never linked to the right OU is functionally no policy at all.
Opsec: WDAC audit-mode events (Event ID 3076) are extremely detection-rich but generate no alert on their own — they must be actively collected and reviewed, otherwise a red team (or a real attacker) can operate indefinitely against a policy that looks enforced in documentation but is not enforced on disk. Always confirm enforcement state with
CiTool.exe -lprather than trusting policy documentation.
Detection and Defense
A durable application-control design assumes rule collections and trust anchors will be probed, and closes the gaps rather than only reacting to individual bypasses:
- Move from Audit to Enforced deliberately — track the audit log to zero false positives before flipping
EnforcementMode/ removingEnabled:Audit Mode, and set a hard deadline so audit mode does not become permanent. - Prefer Publisher/Hash over Path rules, and if Path rules are unavoidable, restrict them to admin-writable locations only — never
%TEMP%,%APPDATA%, or any per-user writable folder. - Enable every relevant rule collection — Exe, DLL, Script, and MSI for AppLocker; confirm WDAC options cover scripts and MSIX if those are in scope, since a strong Exe-only policy leaves scripting engines open.
- Layer Microsoft’s Recommended Block Rules into WDAC/AppLocker policies so trusted-publisher trust does not implicitly allow known LOLBins.
- Monitor the CodeIntegrity and AppLocker logs — Microsoft-Windows-CodeIntegrity/Operational Event ID 3076 (would have been blocked, audit) and 3077 (blocked, enforced); Microsoft-Windows-AppLocker/EXE and DLL Event ID 8003 (audit hit) and 8004 (blocked). Alert on new audit hits from binaries in user-writable paths.
- Protect the policy itself — a signed WDAC policy resists tampering even by local admins; combine with Credential Guard/HVCI so the enforcement mechanism cannot be trivially disabled from user mode.
- Restrict Managed Installer / ISG usage to processes and reputation sources you actually trust, and periodically audit what those trust paths have allowed to run.
Real-World Impact
Application-control bypass is a well-documented category in offensive tradecraft: the LOLBAS project exists largely to catalogue Microsoft-signed binaries — msbuild.exe, installutil.exe, regsvr32.exe, mshta.exe, rundll32.exe, and others — that execute attacker logic while remaining trusted by publisher-based AppLocker and WDAC policies. MITRE ATT&CK catalogues the general technique as T1218 (System Binary Proxy Execution), and application-control bypass via writable path rules or unenforced policy is a routine finding in enterprise assessments, often because organizations deploy application control once, in audit mode, during a pilot, and never complete the rollout to enforced. Microsoft’s continued investment in WDAC (kernel enforcement, Managed Installer, ISG, and the block-rules list) reflects that pure allow-listing by publisher trust alone is not sufficient without curating what that trust actually admits.
Conclusion
AppLocker and WDAC turn “what can run on this endpoint” into an explicit, auditable policy instead of an implicit default-allow — a genuine security win when the policy is enforced, its rule collections are complete, and its path rules stay out of user-writable territory. The gaps that defeat it in practice are rarely exotic: audit mode left on indefinitely, a Path rule that reaches into %TEMP%, an unconfigured Script or DLL collection, or trust in “Microsoft-signed” without curating the LOLBins that signature covers. Designing for those failure modes up front — enforced mode, narrow Publisher/Hash rules, complete rule collections, and Microsoft’s recommended block list — is what separates application control that actually stops an intrusion from application control that only logs one.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments