Ring 3 Ring 0
┌─────────────────┐ ┌─────────────────┐
│ R3Comm.exe │ IOCTL │ Inject.sys │
│ (CLI Tool) │ ───────► │ (Driver) │
│ │ ◄─────── │ │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
CreateFile() IRP Dispatch Routine
DeviceIoControl()
A Windows kernel-mode DLL injection framework that uses kernel APC (Asynchronous Procedure Call) to inject a DLL into newly created processes.
| Component | Type | Description |
|---|---|---|
inject |
Kernel Driver | Inject.sys — registers a process-creation callback, queues user-mode APCs into new processes to call LoadLibraryW |
InjectDll |
User-mode DLL | The payload DLL injected into target processes (customize DllMain with your own logic) |
R3Comm |
User-mode CLI | R3Comm.exe — configures the driver via IOCTL: sets addresses, DLL path, toggles callback, manages whitelist |
-
Load the driver —
Inject.sysregisters as a kernel driver and creates a device object (\Device\MyMonitor) with a symbolic link (\\.\MyMonitorLink). -
Configure addresses —
R3Comm.exe set-loadlibresolveskernel32.dll/ntdll.dllbase addresses andLoadLibraryWviaGetModuleHandle+GetProcAddress, then sends them to the driver. On x64 Windows these addresses are system-wide (same in every process). -
Set the DLL path —
R3Comm.exe set-dll <path>tells the driver which DLL to inject. -
Enable the callback —
R3Comm.exe callback-onregistersPsSetCreateProcessNotifyRoutineEx. From this point on, every new process triggers the driver's callback. -
APC injection — When a new process is created (and passes the whitelist check if active):
- The callback queues a work item to switch to
PASSIVE_LEVEL - The work item attaches to the target process's address space (
KeStackAttachProcess) - Allocates memory in the target via
ZwAllocateVirtualMemory, copies the DLL path - Iterates the target's thread list (using dynamically resolved
EPROCESS.ThreadListHead/ETHREAD.ThreadListEntryoffsets) - Queues a user-mode APC on a thread with
LoadLibraryWas the normal routine and the DLL path as the argument - When the thread enters alertable state,
LoadLibraryW(dllPath)executes, loading the DLL
- The callback queues a work item to switch to
-
Whitelist — Use
whitelist-add/whitelist-removeto restrict injection to specific executables. The whitelist uses a bitmap with Fibonacci hashing over file identity (SectionObjectPointer + DeviceObject + FileSize).
- Visual Studio 2022 with Windows Driver Kit (WDK) and Windows SDK
- Target system: Windows 10/11 x64 or ARM64
- Test signing mode enabled (or a valid kernel signing certificate):
bcdedit /set testsigning on
- Open each
.slnin Visual Studio:inject→ buildsInject.sysInjectDll→ buildsInjectDll.dllR3Comm→ buildsR3Comm.exe
- Build in Release or Debug for your target architecture (x64 / ARM64).
⚠️ Architecture mismatch will cause silent failure. R3Comm resolvesLoadLibraryW/kernel32/ntdlladdresses from its own process. A 32-bit R3Comm on x64 Windows runs under WoW64 and resolves 32-bit addresses — the kernel driver would then write wrong addresses into 64-bit processes. Always match the architecture: x64 R3Comm ↔ x64 driver, ARM64 R3Comm ↔ ARM64 driver.
- Copy
Inject.systo the target machine. - Create a kernel service:
sc create MyMonitor type= kernel binPath= C:\path\to\Inject.sys sc start MyMonitor
- Verify the driver is running:
R3Comm.exe status
One-step (config file) — create a config once, then apply with a single command:
# 1. Create config
R3Comm.exe config-set dll_path C:\Tools\InjectDll.dll
# 2. (Optional) Add whitelist entries
R3Comm.exe whitelist-add C:\Windows\notepad.exe
# 3. Apply everything to the driver
R3Comm.exe applyManual (step by step) — issue each command individually:
# 1. Resolve addresses automatically
R3Comm.exe set-loadlib
# 2. Set the DLL to inject
R3Comm.exe set-dll C:\Tools\InjectDll.dll
# 3. Start injecting into every new process
R3Comm.exe callback-on
# 4. (Optional) Restrict injection to specific executables
R3Comm.exe whitelist-add C:\Windows\notepad.exe
R3Comm.exe whitelist-add C:\Program Files\MyApp\app.exe
# 5. Stop injecting
R3Comm.exe callback-offR3Comm automatically reads R3Comm.ini from the same directory as the executable (override with --config <path> or the R3COMM_CONFIG environment variable).
Config format (key = value, # for comments):
# R3Comm configuration
dll_path = "C:\Tools\InjectDll.dll"
set_loadlib = true
enable_callback = true
whitelist = "C:\Windows\notepad.exe"
whitelist = "C:\Program Files\MyApp\app.exe"Config keys:
| Key | Type | Default | Description |
|---|---|---|---|
dll_path |
string | (empty) | Full path of the DLL to inject |
set_loadlib |
bool | true |
Auto-resolve kernel32/ntdll/LoadLibraryW on apply |
enable_callback |
bool | true |
Enable the process-creation callback on apply |
whitelist |
string, repeatable | none | File path to add to the injection whitelist on apply |
Boolean values accept 1/0, true/false, yes/no, on/off (case-insensitive).
Config management commands:
R3Comm.exe config-show # Display current config
R3Comm.exe config-set dll_path "C:\..." # Set a config key and save
R3Comm.exe config-set whitelist "C:\app.exe" # Add to whitelist
R3Comm.exe config-set whitelist-remove "C:\app.exe" # Remove from whitelist
R3Comm.exe config-set whitelist-clear # Clear all whitelist entries
R3Comm.exe --config C:\path\to\my.ini apply # Use a custom config file| Command | Description |
|---|---|
help |
Show help |
status |
Check if driver is accessible |
set-loadlib |
Auto-resolve kernel32/ntdll/LoadLibraryW addresses |
set-dll <path> |
Set the DLL path for injection |
callback-on |
Enable process-creation callback (start injecting) |
callback-off |
Disable process-creation callback (stop injecting) |
whitelist-add <path> |
Add a file path to the whitelist |
whitelist-remove <path> |
Remove a file path from the whitelist |
whitelist-query <path> |
Query whether a path is in the whitelist |
apply |
Load config and apply all settings to the driver |
config-show |
Display the effective config file path and contents |
config-set <key> [value] |
Set a config value and save to file. Whitelist keys: whitelist (append), whitelist-remove <path>, whitelist-clear |
Use
--config <file>(or-c <file>) before any command to override the default config file path:R3Comm.exe --config C:\path\to\my.ini apply R3Comm.exe -c my.ini config-show
- SeDebugPrivilege required — all IOCTLs verify the caller holds
SeDebugPrivilege - PPL bypass prevention —
PsIsProtectedProcessLightcheck skips protected processes - System process exclusion — processes with PID ≤ 4 are never injected
- Spin-lock synchronization — all global state (address info, DLL path, callback flag) is protected by
ProcessCallBackSpinLock - RAII guards —
SpinLockGuardandObjectReferenceGuardprevent leaks from early returns - Graceful shutdown —
DriverUnloaddrains in-flight work items and pending APCs before freeing resources
Edit InjectDll/dllmain.cpp and add your logic inside DLL_PROCESS_ATTACH:
case DLL_PROCESS_ATTACH:
// Your code runs inside every injected process
OutputDebugStringW(L"[InjectDll] Injected successfully");
// MessageBoxW(nullptr, L"Injected!", L"APC-Injector", MB_OK);
break;APC-Injector/
├── inject/ # Kernel driver (MyMonitor.sys)
│ ├── Injector.cpp # DriverEntry, IOCTL dispatch, process callback
│ ├── APC.cpp # APC injection logic, work item routine
│ ├── APC.h # APC header aggregation
│ ├── ApcTypes.h # KAPC type definitions, function typedefs
│ ├── WhiteList.cpp # Bitmap whitelist (Fibonacci hash, file identity)
│ ├── WhiteList.h # Whitelist declarations
│ ├── ThreadOffset.cpp # Dynamic EPROCESS/ETHREAD offset discovery
│ ├── ThreadOffset.h # Thread offset declarations
│ ├── Common.h # Shared global state, function typedefs
│ ├── Injection.h # INJECT_CONTEXT, DEVICE_EXTENSION structs
│ ├── IOCTL.h # IOCTL code definitions
│ ├── AddressInfo.h # BaseAddressInfo struct
│ ├── RAIIGuard.h # SpinLockGuard, ObjectReferenceGuard
│ └── debug.h # LOG_INFO / LOG_ERROR macros
│
├── InjectDll/ # Payload DLL (injected into targets)
│ ├── dllmain.cpp # DllMain entry point
│ ├── pch.h / pch.cpp # Precompiled headers
│ └── framework.h # Windows header includes
│
├── R3Comm/ # User-mode driver communication tool
│ ├── main.cpp # CLI command parser
│ ├── DriverComm.cpp # DeviceIoControl wrapper, IOCTL handlers
│ ├── DriverComm.h # DriverComm class declaration
│ ├── Config.cpp # Config file parser (UTF-8 key=value)
│ ├── Config.h # Config struct and function declarations
│ └── Common.h # Shared IOCTL/struct definitions (mirrors driver)
│
├── LICENSE # MIT License
├── README.md
└── README.zh-CN.md
Instead of hardcoding EPROCESS/ETHREAD structure offsets (which change between Windows builds), the driver scans memory at runtime:
FindThreadListHeadOffset— walks EPROCESS from offset0x200to0x1000, locating theThreadListHeadby verifying doubly-linked list integrityFindThreadListEntryOffset— uses the discoveredThreadListHeadto find which field inside ETHREAD contains theThreadListEntry, verified by checkingIoThreadToProcess
The whitelist identifies files by a composite hash of:
SectionObjectPointer(shared across all FileObjects for the same data stream)DeviceObject(disk identifier)FileSizeXORValidDataLength * FibonacciConstant(from the FSRTL_ADVANCED_FCB_HEADER)
This provides a stable, collision-resistant identity that survives path changes. The bitmap is 256 KiB (≈ 2 million bits).
- Each queued APC increments
PendingApcCount ApcKernelRoutine(runs atAPC_LEVELafter the APC fires) decrements the count and frees the KAPCApcRundownRoutine(runs if the thread terminates before the APC fires) handles cleanup identicallyDriverUnloadwaits onAllApcsCompletedEventbefore freeing driver .text, ensuring no in-flight APCs reference freed code
| Architecture | Status |
|---|---|
| x64 | Supported |
| ARM64 | Supported (uses vld1q_u8/vst1q_u8 for atomic FCB reads) |
中文版摘要请查看:README.zh-CN.md
R3Comm
I really don't want to write this part; AI can handle it completely, requiring only manual review. Note: (Use AI appropriately)
Buy me a coffee? ☕️
BTC: bc1q9h3z5ny2awv2p9602nrpsf4gvkxe9dyv5rn3hd
ETH: 0x31ec0694d0992d8ece95bcb416ce04027a1a6b7b
MIT License — see LICENSE for full text.
Copyright (c) 2026 @ZYS-Create1024
Disclaimer: This project is for educational and authorized security research purposes only. Injecting code into processes without consent may violate laws and software terms of service. Use responsibly.