x64 Windows kernel-driver emulation, behavior analysis, and coverage-guided research harness — powered by Unicorn.
Overview • Architecture • Capabilities • Build • Usage • Fuzzing • Analysis • MCP • Compatibility • Layout • Limitations
KEVLAR maps a 64-bit Windows kernel driver into a synthetic kernel address space, resolves its imports into host implementations or controlled stubs, constructs the minimum kernel environment needed, and executes DriverEntry inside Unicorn — without loading anything into the live Windows kernel.
It is designed for driver behavior research, anti-cheat analysis, execution tracing, environment-probe reconstruction, and coverage-guided fuzzing of kernel-mode code.
Important
KEVLAR is a specialized research harness, not a complete Windows virtual machine. A driver reaching the end of DriverEntry does not prove correct initialization; many real-world side effects are approximated.
flowchart LR
A[Target x64 .sys] --> B[PE mapper]
B --> C[Relocations + imports]
C --> D[Synthetic kernel environment]
D --> E[DRIVER_OBJECT / KPCR / EPROCESS]
D --> F[PsLoadedModuleList + exports]
D --> G[Virtual FS + registry]
D --> H[Hardware + CPUID profile]
E --> I[Unicorn x64 engine]
F --> I
G --> I
H --> I
I --> J[Instruction + exception hooks]
I --> K[Kernel API implementations]
I --> L[Synthetic threads + IRPs]
J --> M[Log / trace / coverage]
K --> M
L --> M
M --> N[AFL++ bitmap / JSON report]
| Area | Detail |
|---|---|
| Image loading | x64 PE mapping, full relocation processing, IAT resolution, synthetic kernel base addresses |
| Kernel environment | DRIVER_OBJECT, DRIVER_EXTENSION, KPCR/KPRCB, ETHREAD, EPROCESS, PsLoadedModuleList, loader entries |
| System state | Real or stubbed system modules, export resolution, KUSER_SHARED_DATA, Hyper-V shared page |
| CPU hooks | CPUID (coherent Intel bare-metal profile), RDTSC, RDMSR/WRMSR, syscall, interrupts, port I/O |
| Instruction emulation | AVX/AVX2 YMM patch-and-dispatch, AES-NI, SHA, CRC32c, RDRAND, RDSEED, XGETBV, PINSR/PEXTR family |
| Memory | Pool allocator, variable allocations, MDL/mapped images, user-mode region mapping, physical memory primitives |
| I/O | Create/close/cleanup/read/write/IOCTL dispatch; IRP allocation, cancellation, completion routing |
| Concurrency | Synthetic system threads with independent Unicorn engines and stacks; APC, DPC, timer delivery |
| SEH / unwind | PDATA-guided O(1) frame-chain fast path, fallback linear stack scan, synthetic exception dispatch |
| State isolation | Per-driver virtual filesystem and registry trees; named-object registry for AC IPC objects |
| Target | Support |
|---|---|
| EAC | Primary target; service-path IPC objects, EOSBin section, event handles (--eac-service-emu) |
| EOS pipe | NtCreateFile stub intercepts EOS_AntiCheat_Server named-pipe paths (--eos-pipe) |
| Vanguard | Configurable NTSTATUS override map, including all VGK rejection codes (--override-status) |
| BattlEye | Reported working on tested initialization paths |
| FACEIT | Reaches entry point; coherent CPUID bare-metal profile (hypervisor bit clear) |
| Feature | Detail |
|---|---|
| Coverage tracking | Edge-level coverage with hit counts; per-run snapshots; AFL++ 65536-byte bitmap output |
| Structured JSON output | Machine-readable run report: SEH events, memory probes, CPUID, unmapped reads, consistency |
| Execution tracing | Deterministic --trace record + --check replay with divergence detection |
| Provenance tracing | --provenance records branch decisions and API return values at rejection paths |
| VTIL devirtualization | Bounded AMD64→VTIL lift via kevlar-vtil-host.ps1 (CreateProcessW, 5-min watchdog) |
| Snapshot / restore | Full emulator memory checkpoint for zero-overhead fuzz-loop restarts |
| IRQL enforcement | Synthetic IRQL tracker; logs calls at wrong IRQL level |
| ETW provider stubs | EtwRegister/EtwWrite/EtwUnregister with provider GUID logging |
| ObReference tracking | Reference-count lifecycle tracking for fake EPROCESS/ETHREAD objects |
| PID→name map | --pid-map <pid>=<name> populates realistic ImageFileName in fake EPROCESS structs |
- Windows x64 (10 / 11)
- Visual Studio 2022 (or Build Tools) with Desktop development with C++ workload
- Windows 10/11 SDK
- PowerShell 5.1+
- Python 3.11+ (MCP server only)
Dependencies managed via vcpkg.json:
| Library | Version |
|---|---|
| Unicorn | 2.1.4 |
| Zydis | 4.1.1 |
| Zycore | 1.5.2 |
Note on Unicorn symlinks — The upstream static CMake build creates a symbolic link during packaging, which requires Developer Mode or elevation on Windows.
vcpkg-ports/unicorn/no-symlink.patchreplaces it with a plain file copy. No emulator behavior is changed.
Set-ExecutionPolicy -Scope Process Bypass
.\build.ps1 Release.\build.ps1 Debug # includes symbols and diagnostic assertionsOutputs land in separate directories so configurations never overwrite each other:
builds\Release\KEVLAR.exe
builds\Debug\KEVLAR.exe
A CMakeLists.txt is provided as an alternative for non-MSBuild environments:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
cmake --build build --config ReleaseA selftest CMake target is available:
cmake --build build --target selftestGitHub Actions runs a Release build and self-test on every push to main / master / ultimate and on all pull requests. See .github/workflows/build.yml.
.\builds\Release\KEVLAR.exe C:\path\to\driver.sysWith full diagnostics and JSON output:
.\builds\Release\KEVLAR.exe driver.sys --diag --modreads --json-out report.jsonCoverage-guided research run:
.\builds\Release\KEVLAR.exe driver.sys --afl-bitmap cov.bin --coverage-out run1.covCompare against a prior run:
.\builds\Release\KEVLAR.exe driver.sys --coverage-base run1.cov --coverage-out run2.cov| Option | Purpose |
|---|---|
--max-insns <n> |
Hard instruction budget; disassembles the stopped RIP on limit |
--seed <n> |
Deterministic seed for TSC jitter |
--no-pause |
Skip final pause; exit ~5 s after a no-thread run (automation) |
--workers-deep |
Extended emulation loop for synthetic worker threads |
--selftest |
Run IRQL/APC/DPC/timer self-test and exit (no driver required) |
| Option | Purpose |
|---|---|
--profile <name|build> |
Select a built-in Windows build profile |
--profile-json <file> |
Load a validated build-profile override from JSON |
--module <path> |
Map an additional companion kernel module |
--pid-map <pid>=<name> |
Set ImageFileName in the fake EPROCESS for a given PID (repeatable) |
--inject-hypervideo |
Expose synthetic hypervideo.sys in the module list |
--target-compat / --no-target-compat |
Control exact-version status normalization |
| Option | Purpose |
|---|---|
--eac-service-emu |
Create EAC service IPC objects (EOSBin section, event handles) before DriverEntry |
--eos-pipe |
Intercept EOS_AntiCheat_Server NtCreateFile paths and serve a synthetic named pipe |
--vgk-override |
Alias: adds 0xC0000022→0 and 0xC000007A→0 to the status override map |
--override-status <from>=<to> |
Map any NTSTATUS return from DriverEntry to another (repeatable, hex values) |
| Option | Purpose |
|---|---|
--diag |
Enable structured diagnostic hooks (significantly slower) |
--modreads |
Trace reads from mapped system modules |
--provenance |
Record branch decisions and API return values at rejection paths |
--trace <file> |
Record a deterministic execution trace |
--check <file> |
Replay a prior trace; report the first divergence |
--json-out <file> |
Write a structured JSON analysis report after the run |
--no-seh |
Disable synthetic SEH dispatch |
--strict-exports |
Unresolved exports return STATUS_NOT_IMPLEMENTED instead of 0 |
| Option | Purpose |
|---|---|
--afl-bitmap <file> |
Write a 65536-byte AFL++/libFuzzer-compatible edge-coverage bitmap |
--coverage-out <file> |
Save an edge-coverage snapshot for cross-run comparison |
--coverage-base <file> |
Load a prior snapshot; log newly-covered edges in this run |
| Option | Purpose |
|---|---|
--devirt |
Enable devirtualization-testing mode |
--vtil-rva <rva> |
Lift a bounded region from the driver image into VTIL |
--vtil-size <bytes> |
Byte count available to the lifter (default 0x5000) |
--vtil-out <file> |
Output path for the serialized VTIL (default: exe directory) |
| Option | Purpose |
|---|---|
--client <script> |
Run a usermode IOCTL client script against the emulated driver |
--client-delay <ms> |
Delay before launching the client (default 120 ms) |
On first launch, KEVLAR downloads PDB symbols for ntdll.dll and ntoskrnl.exe into pdb_cache. Real system modules can be placed beside the executable when a target requires the actual image; otherwise KEVLAR creates bounded stubs.
builds/Release/
├── KEVLAR.exe
├── pdb_cache/ # downloaded PDB files
├── kevlar.log # full run log
├── easyanticheat_threads/ # per-thread logs (one file per synthetic thread)
└── <driver-name>/
├── vfs/ # virtual filesystem root
└── vreg/ # virtual registry root
KEVLAR integrates coverage-guided research workflows without modifying the target driver.
After a run, --afl-bitmap <file> writes a 65536-byte file where each byte represents a unique control-flow edge:
byte[StableEdgeId(edge) % 65536] = min(hit_count, 255)
This is the standard AFL shared-memory layout. Point AFL++ or a libFuzzer harness at the file to drive input mutation.
# Baseline run
.\KEVLAR.exe driver.sys --coverage-out baseline.cov
# Subsequent run — shows how many new edges were discovered
.\KEVLAR.exe driver.sys --coverage-base baseline.cov --coverage-out run2.covLog output example:
[CYN] Coverage: 142 new edges vs baseline baseline.cov
The EmulatorSnapshot subsystem (KEVLAR/core/snapshot/) provides full-state checkpointing. After import resolution and kernel-struct setup, a checkpoint can be taken to restart fuzz iterations from a clean pre-DriverEntry state in microseconds, without re-mapping the PE or re-initializing pool memory.
--json-out <file> writes a machine-readable report after the run. Only non-empty event categories are included.
{
"seh": [
{
"seq": 1,
"rip": "0xFFFFF80100001234",
"exception_code": "0xC0000005",
"fault_address": "0x0000000000000000"
}
],
"mem_probes": [...],
"cpuid": [
{
"seq": 4,
"leaf": "0x1",
"leaf_name": "FeatureBits",
"post_eax": "0x000906ED",
"post_ecx": "0x7FFAB7FF"
}
],
"unmapped_reads": [...],
"consistency": [...]
}All addresses are 0x-prefixed hex strings. Useful for automated diffing between driver versions or builds.
--override-status allows any DriverEntry return code to be remapped at the host level without patching the driver binary:
# Remap VGK access-denied codes to success
.\KEVLAR.exe vgk.sys --override-status 0xC0000022=0x0 --override-status 0xC000007A=0x0
# Equivalent shorthand
.\KEVLAR.exe vgk.sys --vgk-override
# Remap a custom vendor code
.\KEVLAR.exe driver.sys --override-status 0xC0EB0001=0x0Overrides are applied post-emulation: RAX is read from the engine, the map is checked, and if a match exists the new value is written back and the result is promoted to success.
mcp/kevlar_mcp is a local stdio Model Context Protocol server. Every long operation becomes a persistent run identified by a run ID, logged under builds/mcp-runs/, and readable/cancellable after server restarts.
python -m pip install -e .\mcpclaude mcp add kevlar -- python -m kevlar_mcpOr add to .mcp.json:
{
"mcpServers": {
"kevlar": {
"command": "python",
"args": ["-m", "kevlar_mcp"],
"cwd": "C:\\path\\to\\Kevlar-Ultimate"
}
}
}| Tool | Purpose |
|---|---|
inspect_driver |
PE header/section inspection; SHA-256, machine type, entry point, sections |
build_kevlar |
Start a bounded Debug or Release build via build.ps1; returns a run record |
run_driver |
Execute a staged driver with the full option surface and bounded time/instruction limits |
run_selftest |
Run KEVLAR's built-in host-semantics self-test (IRQL, APC, DPC, timers) |
generate_test_driver |
Emit a minimal test .sys via the repository generator |
read_run_log |
Read a bounded UTF-8 chunk of a run log at an offset |
list_runs |
List recent runs, optionally filtered by status |
cancel_run |
Kill an active process tree by run ID |
- Path confinement — inputs resolve only under the Kevlar project root,
C:\Program Files\Alea, orC:\Program Files\FACEIT AC; opened handles are re-verified viaGetFinalPathNameByHandleto defeat junction/symlink escapes. - Bounded execution — per-run timeout (≤1 h), output cap (≤4 MiB), instruction cap (≤1 × 10⁹), ≤4 concurrent subprocesses; overruns killed via
taskkill /T /F. - Strict schemas — all requests/responses are Pydantic models with
extra="forbid"; unknown arguments are rejected at the protocol envelope. - Crash hygiene — a server restart marks in-flight runs as
interruptedrather than leaving them as phantomrunningrecords.
.\tests\smoke.ps1 # build, generate minimal .sys, run, assert STATUS_SUCCESS
.\tests\smoke.ps1 -SkipBuild # reuse an existing buildtests\make_test_driver.py emits a minimal x64 native driver that:
- Performs a KPCR/GS read and a conditional branch (exercises the core emulation path)
- Runs a FACEIT-style CPUID probe (leaf
0x1+0x40000000) so--diagruns expose the coherent bare-metal profile - Optionally imports
KeInitializeSpinLockvia--with-importto exercise the fullParseExport→ResolveImport→BuildSentinelIatpath
Kernel structure offsets for the actual target ntoskrnl PDB are generated with the DIA SDK:
.\tools\pdb_layout.ps1 # -> generated\kernel_layout.hThe harness consumes these through KEVLAR\include\kernel_layout_consume.h. Fixed-offset accesses (KPCR→KPRCB→CurrentThread, ETHREAD→KTHREAD back-pointers, APC-disable) use GEN_* macros. Compile-time static_asserts fail the build if a hardcoded struct drifts from generated offsets.
Regenerate generated\kernel_layout.h after refreshing the target ntoskrnl PDB, then rebuild.
KEVLAR/
├── api/ # Emulated kernel API implementations
│ ├── ex/ # Executive (pool, pushlock, synchronization)
│ ├── io/ # I/O manager, device, filter manager
│ ├── ke/ # Kernel (sync, events, timers, ETW, IRQL)
│ ├── mm/ # Memory manager (pool, virtual, MDL)
│ ├── nt/ # Native API (file, memory, query, registry)
│ ├── ob/ # Object manager, callbacks
│ ├── ps/ # Process/thread manager, token
│ └── rtl/ # Runtime library (strings, hash, registry)
├── core/
│ ├── coverage/ # Edge coverage tracker + AFL bitmap
│ ├── debug/ # Logging, bugcheck rendering
│ ├── devirt/ # VTIL lift integration
│ ├── diagnostics/ # Structured event ring buffers + JSON output
│ ├── exception/ # SEH dispatch + PDATA unwind
│ ├── exec/ # Unicorn engine, hooks, instruction emulation
│ ├── fuzz/ # I/O fuzzer
│ ├── hardware/ # PCI bus, IOMMU model
│ ├── io/ # IRP lifecycle, I/O manager
│ ├── loader/ # Module loader, kernel structs
│ ├── memory/ # Guest/host memory management
│ ├── process/ # Synthetic thread management
│ ├── profile/ # Windows build profile system
│ ├── scheduler/ # Virtual thread scheduler
│ └── snapshot/ # Full-state emulator snapshots
├── host/
│ ├── frameworks/ # NDIS, KMDF, storport, fltmgr providers
│ ├── main/ # CLI entry point (kevlar.cpp)
│ └── providers/ # Export contract runtime, ntoskrnl/usermode providers
└── include/ # Windows structure definitions, layout macros
libs/ # Logger, PE mapper, symbol parser
extern/VTIL-NativeLifters/ # Vendored VTIL lifter
mcp/kevlar_mcp/ # Local stdio MCP server (Python)
vcpkg-ports/ # Reproducible dependency overlay
tests/ # Smoke test driver generator + runner
tools/pdb_layout/ # DIA-based kernel layout generator
generated/ # Generated layout headers
CMakeLists.txt # CMake build (alternative to build.ps1)
.github/workflows/build.yml # CI pipeline
FACEIT_AC.sys and FACEIT_IOMMU.sys both reach their entry points and return 0xC0EB0001 after early platform checks: InitSafeBootMode read (0), then CPUID leaf 0x1 and hypervisor leaf 0x40000000. The coherent CPU profile presents a bare-metal surface for exactly those reads — ECX=0x7FFAB7FF (hypervisor bit clear), 0x40000000 empty. No FACEIT compatibility claim is made yet.
- Kernel structure definitions are based on Windows 10 21H2 x64. The
_KPROCESS/_EPROCESSbodies predate build 26100; fields not accessed throughGEN_*macros are not PDB-accurate. Full accuracy requirespdb_layoutto emit typed struct definitions, not just offsets. - Many kernel exports are simplified or intentionally stubbed; unknown return values can alter downstream driver control flow.
- Host threads do not perfectly reproduce Windows scheduling, IRQL behavior, or quantum accounting.
- ACPI/PCI/IOMMU model is a bounded baseline: synthetic PCI config, VT-d
DMARtable,0xFED90000register block, and CPUID/MSR pre-conditions. The register set is still minimal until a real IOMMU driver trace validates it. - PnP, power, DMA, and filter stacks are present but simplified; they are extended only when a specific target driver exercises a given path.
- Diagnostic mode (
--diag) produces large traces and runs substantially slower. KeWaitForMultipleObjectsWaitAll/WaitAnypaths are implemented; complex cross-object wait chains with arbitrary timeout types may still fall back to simplified behavior.- APC delivery runs inline on the target thread's own engine and stack. Cross-thread APC signals a per-thread wake event so a wait-blocked thread is interrupted and delivers in its wait loop.
- TheRealWaryas — KACE, which inspired the project's early design
- Unicorn Engine — CPU emulation core
- Zydis / Zycore — x86/x64 instruction decoding
No license file was included in the source archive. Publication of this repository does not add or imply a new license; original authors retain their applicable rights.
