Skip to content

durck/applocker-viewer

Repository files navigation

AppLocker Viewer

tests python license

Red-team-friendly inspector for Get-AppLockerPolicy -Effective -Xml output.

  • CLI with table / JSON / CSV output and arbitrary filters
  • Standalone HTML viewer (drag-n-drop, facets, AND/OR filter groups, presets)
  • Risk analyzer with atomic flags — combinations done by filtering, not baked-in
  • Diff between two policies (added / removed / modified, identity-aware)
  • PowerShell wrapper for capturing live policy
  • stdlib-only Python, no external dependencies

Disclaimer. This tool is intended for authorized security assessments, red-team engagements, defensive auditing of your own systems, and CTF / educational use. Do not use it against systems you do not own or have explicit permission to test.

Install

git clone https://github.com/durck/applocker-viewer.git
cd applocker-viewer

That's it — no pip install needed. Python 3.10+ is the only requirement (uses from __future__ import annotations, PEP 604 unions, etc., available on 3.10+). Run tests with python -m pytest -q (pytest is the only dev dependency).

Layout

applocker.py              core parser, filter, analyzer, diff (CLI)
viewer.html               standalone single-file viewer (no server, no deps)
Get-AppLockerPolicy.ps1   thin PS wrapper -> applocker.py
tests/                    pytest suite + XML fixtures

Quick start

Capture and analyze on a live Windows host

.\Get-AppLockerPolicy.ps1 analyze

Inspect a saved XML

python applocker.py parse policy.xml --pretty
python applocker.py filter policy.xml --action allow --collection exe
python applocker.py filter policy.xml --risky --format json
python applocker.py analyze policy.xml
python applocker.py diff old.xml new.xml

Open in the browser

Open viewer.html in any browser (no server needed). Drag the XML or the JSON output of applocker.py parse into the page, or click Open file.

The viewer supports:

  • Search at the top of the sidebar — case-insensitive substring match across rule name, description, summary, SID/principal name and serialized conditions/exceptions (so mshta, \appdata\, S-1-5-32-544 etc. all work).
  • Filter groups with AND/OR:
    • Multi-select inside one facet (e.g. risk flag) is OR.
    • Between facets in the same group is AND.
    • Between groups is OR (use + Add group (OR)).
    • Tri-state per value: left click = include, right click = exclude.
  • Built-in presets for common red-team views (broad path allows, LOLBin allows, user-writable allows, AuditOnly rules, wildcard publishers, …) plus user-saved presets in localStorage. Applying a preset replaces the current groups; one-step Undo restores the previous state.
  • Resizable columns with three gestures, all persisted to localStorage:
    • drag right ⇒ pool-compresses everything on the right (widest first), so the right side of the table flattens into a uniform profile and the table stays glued to the section edge;
    • drag left ⇒ pool-expands the right side (narrowest first), the symmetric inverse;
    • Shift + drag ⇒ free grow (only the dragged column changes; horizontal scroll appears if it overflows);
    • double-click the handle ⇒ auto-size the column to its content.
  • Resizable filters panel — drag the vertical handle between the sidebar and the table. Width persists; the panel auto-collapses on narrow viewports (< 900px) into a ☰ Filters button.
  • Diff two policies — click + Compare, drop a second XML/JSON. The table shows a Δ column with + / / ~ / = markers, rows are tinted, and modified rules expand to an old → new diff per changed field. A Change types panel in the sidebar (added / removed / modified / unchanged) is multi-select. Esc or Exit diff returns to single mode.
  • Esc closes the mobile filters drawer first, then exits diff mode.
  • Filter, sort and detail state survive across rerenders (scroll positions and search caret are preserved when you toggle facets).

CLI commands

parse

Emit the normalized JSON model.

applocker.py parse <xml|->  [--pretty]

filter

Filter rules and emit table / JSON / CSV.

applocker.py filter <xml|->
    [--action allow|deny]
    [--collection exe|msi|script|dll|appx]
    [--type path|publisher|hash]
    [--sid <SID>]
    [--search <substring>]
    [--has-exceptions | --no-exceptions]
    [--risky]
    [--flag <risk_flag>]
    [--format table|json|csv]

analyze

Print a risk-grouped report.

applocker.py analyze <xml|->

diff

Compare two policies. The identity key for pairing rules is the semantic anchor of the rule — (collection, rule_type, UserOrGroupSid, primary condition value) — so the following are reported as modified (with the specific changed field listed under changes) instead of as a removed + added pair:

  • a renamed or re-described rule;
  • the same rule with Action flipped between Allow and Deny on the same target;
  • conditions tweaked (publisher version range narrowed, exception added).

The CLI diff is also available as an interactive mode in viewer.html (click + Compare).

applocker.py diff <old.xml> <new.xml> [--format text|json]

Risk flags (atomic)

Every flag is a single observable property, never a combination. Combine them with filters when you need to express a hypothesis like "broad path AND allow AND not Everyone" — that's --collection exe --action allow --flag broad_wildcard_path plus filtering by SID, not a single canned flag.

Flag Meaning
audit_only Rule lives in a collection with EnforcementMode=AuditOnly
user_writable_path Path covers a known user-writable directory (%TEMP%, %LOCALAPPDATA%, \Windows\Tasks, …)
broad_wildcard_path Path starts with * or contains \*\ (subtree-spanning wildcard, not anchored to a fixed root)
wildcard_path Path contains any * (weaker than broad_wildcard_path; covers C:\App\* and similar trailing wildcards too)
wildcard_publisher Publisher field of a FilePublisher rule is * or empty
lolbin_path Basename matches a curated LOLBin (mshta, regsvr32, msbuild, …)
has_exceptions The rule defines at least one Exceptions entry
non_everyone_sid UserOrGroupSid is not S-1-1-0 (Everyone)

Plus one policy-level flag, surfaced under metadata.policy_flags:

Flag Meaning
missing_dll_collection The policy contains no Dll rules → DLL hijacking is unconstrained

Atomicity rationale: a rule may have a broad path that is only granted to a specific high-privilege SID. Whether that's exploitable depends on which user context you currently have. Don't pre-bake that decision into the flags — filter for it.

Curated lists

Edit at the top of applocker.py:

  • USER_WRITABLE_SUBSTRINGS — extend if you have site-specific writable paths
  • LOLBIN_BASENAMES — sourced from the GTFOBins-style LOLBAS catalog, trim or expand as needed for your engagement
  • WELL_KNOWN_SIDS — built-in SID -> name table for nicer output

Normalized JSON model

{
  "metadata": {
    "schema_version": 1,
    "source_file": "policy.xml",
    "parsed_at": "2026-05-04T09:55:20+00:00",
    "policy_flags": ["missing_dll_collection"]
  },
  "collections": [
    {"type": "Exe", "enforcement_mode": "Enabled", "rule_count": 9}
  ],
  "rules": [
    {
      "id": "e0771f6b5e2eb8f5",
      "collection": "Exe",
      "rule_type": "FilePath",
      "action": "Allow",
      "name": "(Default Rule) All files",
      "description": "",
      "user_or_group_sid": "S-1-1-0",
      "user_or_group_name": "Everyone",
      "conditions": [{"type": "path", "path": "*"}],
      "exceptions": [],
      "summary": "*",
      "risk_flags": ["broad_wildcard_path"]
    }
  ]
}

risk_flags is added by analyze / filter. parse returns the model without it (so the parsing layer stays content-only).

Security notes

  • The parser pre-rejects XML containing DOCTYPE or ENTITY declarations to prevent XXE through hostile policies.
  • The HTML viewer mirrors the same XXE check before handing the document to the browser's DOMParser.
  • Both run fully offline; nothing is uploaded.

Tests

python -m pytest -q

89 tests covering parser, filters, emitters, analyzer atomicity, and diff identity behaviour (including the action-flip-as-modified contract). CI runs on Linux + Windows across Python 3.10–3.13.

Contributing

PRs welcome. The code is intentionally compact and stdlib-only — please keep new functionality in the same spirit:

  • No external runtime dependencies for applocker.py or viewer.html. Tests may use pytest.
  • Risk flags must stay atomic — one observable property per flag, no combinations. Combinations belong in filter groups in the viewer / CLI.
  • Add or extend a test fixture in tests/fixtures/ when introducing new parsing or analysis behaviour.
  • Run python -m pytest -q before pushing.

Known limitations

  • Curated lists (LOLBins, user-writable paths) are deliberately small and conservative — extend them for your environment.
  • DLL collection rules are parsed but the analyzer treats absence/0 rules as missing_dll_collection. Per-rule DLL analysis follows the same shape as Exe rules.
  • Appx packaged-app conditions are normalized through the same publisher shape; packaged-only attributes that AppLocker may emit are not preserved.

About

Red-team-friendly inspector for Get-AppLockerPolicy -Effective -Xml output: stdlib-only Python CLI (parse/filter/analyze/diff) + standalone HTML viewer with AND/OR filter groups, presets, and a side-by-side diff mode

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors