An unfinished Windows kernel research prototype for identifying executable code present in physical memory but outside the address ranges of loaded kernel modules.
The experiment walks physical memory, looks for FF 25 indirect jumps, resolves their targets, and compares source and destination addresses with SystemModuleInformation. A source outside every known module that resolves into the kernel can be a useful triage lead for manually mapped code or another unusual executable allocation.
Warning
The detector path is disabled in the checked-in source. The current driver initializes its debugger logger and returns success. This repository documents an approach under investigation, not a working detector.
- Research question
- Current behavior
- Detection model
- Build
- Observed output
- Source map
- Research roadmap
Windows reports loaded kernel images through SystemModuleInformation. The prototype asks whether physical-memory inspection can surface code that does not belong to one of those reported ranges but still transfers control into ntoskrnl.
| Signal | Prototype check | Interpretation |
|---|---|---|
| Candidate source | FF 25 bytes in a copied physical page |
Possible RIP-relative indirect jump |
| Resolved destination | Address falls inside the first reported module | Possible transfer into ntoskrnl |
| Source ownership | Address falls outside every reported module | Candidate unbacked or manually mapped code |
This is a triage heuristic. Raw opcode matching is not instruction decoding, module membership does not prove page executability, and an address outside a loaded module is not automatically malicious.
| Component | Checked-in state | Runtime effect |
|---|---|---|
| Kernel logger | Active | Writes a DriverEntry message to the kernel debugger |
| Module enumeration | Commented out | SystemModules remains null |
| Physical-range walk | Implemented in MemoryScan() |
Not reached because the call is commented out |
FF 25 candidate scan |
Commented out | No instructions are classified |
| Result transport | Not implemented | No findings leave the driver |
| Unload and cancellation | Not implemented | The active entry path simply returns |
Re-enabling only the inner scan is unsafe and incomplete. Detector::IsValidKernel() and Detector::IsValidAny() require a populated SystemModules buffer, and the physical-to-virtual assumptions need separate validation.
The diagram below shows the intended experiment. Dashed nodes represent disabled or missing stages in the current source.
flowchart LR
A[DriverEntry] --> B[Initialize debugger logger]
B -. disabled .-> C[Query loaded kernel modules]
C -. disabled .-> D[Walk physical memory ranges]
D -. disabled .-> E[Copy one page at a time]
E -. disabled .-> F[Find FF 25 byte pairs]
F -. disabled .-> G[Resolve candidate target]
G -. disabled .-> H{Target in kernel and source outside modules?}
H -. yes .-> I[Record triage finding]
Several conditions can create findings that do not fit a simple loaded-module model, including firmware mappings, hypervisors, crash facilities, and security products. A future classifier should preserve enough context for an analyst to distinguish those cases.
Requirements:
- Windows 10 or newer development host
- Visual Studio 2022 with Desktop development with C++
- Windows Driver Kit
- An x64 disposable virtual machine for any runtime experiment
Open mapped_explorer.sln, select Debug | x64 or Release | x64, and build. From a Visual Studio Developer PowerShell prompt, the equivalent command is:
msbuild .\mapped_explorer.sln /m /p:Configuration=Debug /p:Platform=x64The project contains ARM64 configurations inherited from its template, but the implementation has only been considered as an x64 experiment. Do not treat ARM64 as a supported target.
Caution
Use a disposable VM with test signing and kernel debugging enabled. Physical-memory code running in kernel mode can crash or corrupt the guest. No driver binary or loading utility is included.
With the checked-in path, WinDbg receives one line from DriverEntry:
[mapped_explorer][info][DriverEntry] DriverEntry called . . .
No scan result follows because module enumeration and MemoryScan() are disabled. Any output claiming detected addresses does not come from the repository in its current state.
entry.cpp Driver entry point and disabled module-enumeration path
detector.cpp Physical-range walk and disabled candidate scan
detector.hpp Detector declarations and shared state
defs.hpp Native structures and system-information declarations
utils.cpp Physical-to-virtual and guarded copy helpers
klog.hpp Kernel debugger logger
A defensible next iteration should separate collection from classification:
- Bound and snapshot candidate physical ranges explicitly.
- Decode instructions instead of searching for two raw bytes.
- Isolate loaded-image enumeration behind a carefully validated compatibility layer.
- Record page protections and allocation ownership where Windows exposes them.
- Send structured findings to a user-mode viewer for filtering and export.
- Test classification against synthetic ranges before any kernel run.
Even with those stages in place, the output should remain an analyst triage signal rather than a verdict.