Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Buid status Platform

APC-Injector

       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

How It Works

  1. Load the driverInject.sys registers as a kernel driver and creates a device object (\Device\MyMonitor) with a symbolic link (\\.\MyMonitorLink).

  2. Configure addressesR3Comm.exe set-loadlib resolves kernel32.dll / ntdll.dll base addresses and LoadLibraryW via GetModuleHandle + GetProcAddress, then sends them to the driver. On x64 Windows these addresses are system-wide (same in every process).

  3. Set the DLL pathR3Comm.exe set-dll <path> tells the driver which DLL to inject.

  4. Enable the callbackR3Comm.exe callback-on registers PsSetCreateProcessNotifyRoutineEx. From this point on, every new process triggers the driver's callback.

  5. 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.ThreadListEntry offsets)
    • Queues a user-mode APC on a thread with LoadLibraryW as the normal routine and the DLL path as the argument
    • When the thread enters alertable state, LoadLibraryW(dllPath) executes, loading the DLL
  6. Whitelist — Use whitelist-add / whitelist-remove to restrict injection to specific executables. The whitelist uses a bitmap with Fibonacci hashing over file identity (SectionObjectPointer + DeviceObject + FileSize).

Quick Start

Prerequisites

  • 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

Build

  1. Open each .sln in Visual Studio:
    • inject → builds Inject.sys
    • InjectDll → builds InjectDll.dll
    • R3Comm → builds R3Comm.exe
  2. Build in Release or Debug for your target architecture (x64 / ARM64).

⚠️ Architecture mismatch will cause silent failure. R3Comm resolves LoadLibraryW / kernel32 / ntdll addresses 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.

Deploy

  1. Copy Inject.sys to the target machine.
  2. Create a kernel service:
    sc create MyMonitor type= kernel binPath= C:\path\to\Inject.sys
    sc start MyMonitor
  3. Verify the driver is running:
    R3Comm.exe status

Usage

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 apply

Manual (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-off

Configuration File

R3Comm 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

Full Command Reference

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

Security Design

  • SeDebugPrivilege required — all IOCTLs verify the caller holds SeDebugPrivilege
  • PPL bypass preventionPsIsProtectedProcessLight check 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 guardsSpinLockGuard and ObjectReferenceGuard prevent leaks from early returns
  • Graceful shutdownDriverUnload drains in-flight work items and pending APCs before freeing resources

Customizing the Payload DLL

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;

Project Structure

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

Technical Details

Dynamic Offset Discovery

Instead of hardcoding EPROCESS/ETHREAD structure offsets (which change between Windows builds), the driver scans memory at runtime:

  • FindThreadListHeadOffset — walks EPROCESS from offset 0x200 to 0x1000, locating the ThreadListHead by verifying doubly-linked list integrity
  • FindThreadListEntryOffset — uses the discovered ThreadListHead to find which field inside ETHREAD contains the ThreadListEntry, verified by checking IoThreadToProcess

Whitelist Hashing

The whitelist identifies files by a composite hash of:

  • SectionObjectPointer (shared across all FileObjects for the same data stream)
  • DeviceObject (disk identifier)
  • FileSize XOR ValidDataLength * 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).

APC Lifecycle

  • Each queued APC increments PendingApcCount
  • ApcKernelRoutine (runs at APC_LEVEL after the APC fires) decrements the count and frees the KAPC
  • ApcRundownRoutine (runs if the thread terminates before the APC fires) handles cleanup identically
  • DriverUnload waits on AllApcsCompletedEvent before freeing driver .text, ensuring no in-flight APCs reference freed code

Platform Support

Architecture Status
x64 Supported
ARM64 Supported (uses vld1q_u8/vst1q_u8 for atomic FCB reads)

README.zh-cn

中文版摘要请查看README.zh-CN.md

AI Construction Section

  • R3Comm

I really don't want to write this part; AI can handle it completely, requiring only manual review. Note: (Use AI appropriately)

Donate

Buy me a coffee? ☕️

BTC: bc1q9h3z5ny2awv2p9602nrpsf4gvkxe9dyv5rn3hd

ETH: 0x31ec0694d0992d8ece95bcb416ce04027a1a6b7b

License

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.

About

Kernel APC Injection Example (with Whitelist System) 内核APC注入示例(带白名单系统)

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages