injdrv Documentation

repository·master·Indexed 23 days ago

https://github.com/wbenny/injdrv

A proof-of-concept Windows Driver designed to inject DLLs into user-mode processes at an early stage of initialization using Asynchronous Procedure Calls (APCs). It includes the injldr CLI for driver management and ETW tracing, and supports multiple injection methods: Thunk (all architectures), Thunkless (x64), and wow64log.dll reparse (ARM64/all). The project demonstrates early-stage injection, PEB hiding, and function hooking using Detours.

Tokens
3.7K
Snippets
8
Records
18
Agent score
80%

What's inside injdrv

  1. Injection methods in injdrv

    master

    The project implements three distinct methods for DLL injection depending on the target architecture and requirements:

    1. "Thunk" method

    • Target: All architectures.
    • Mechanism: Injects a DLL of the same architecture as the process. It allocates memory in the user-mode address space containing the DLL path and a small shellcode (thunk) that calls LdrLoadDll.
    • Implementation Detail: To avoid security concerns with PAGE_EXECUTE_READWRITE and bypass ZwProtectVirtualMemory limitations on older Windows versions, it uses a section (ZwCreateSection) to map memory with PAGE_READWRITE, writes the data, unmaps it, and re-maps it with PAGE_EXECUTE_READ. To avoid deadlocks with AddressCreationLock, it uses a kernel-mode APC to perform the mapping.

    2. "Thunkless" method

    • Target: Windows x64 only.
    • Mechanism: Injects an x64 DLL into both x64 (native) and x86 (Wow64) processes.
    • Implementation Detail: This method bypasses Control Flow Guard (CFG) issues on Windows 10 by pointing the APC's NormalRoutine directly to the address of LdrLoadDll in the 64-bit ntdll.dll. It leverages the way KiUserApcDispatcher passes parameters to effectively call LdrLoadDll(NULL, 0, &DllName, &ContinueContext).
  2. Injection method: "wow64log.dll reparse"

    master

    This method is used to inject a native DLL into all processes and is available on all architectures.

    Mechanism: When a Wow64 process starts, it attempts to load wow64log.dll. This file is typically missing from standard Windows installations. The driver uses a filter driver to intercept IRP_MJ_CREATE requests for wow64log.dll. When detected, the driver uses IoReplaceFileObjectName to redirect the file path to the custom native DLL provided by injdrv. This causes the Wow64 subsystem to load the injected native DLL instead.

  3. How DLL injection works in injdrv

    master

    The driver uses Asynchronous Procedure Calls (APCs) to inject DLLs into user-mode processes. It achieves this by registering two callbacks:

    • PsSetCreateProcessNotifyRoutineEx: For process create/exit notification.
    • PsSetLoadImageNotifyRoutine: For image load notification.

    Injection Lifecycle:

    1. When a process is created, the driver allocates a structure to track loaded DLLs and function addresses (like LdrLoadDll).
    2. The driver waits for specific system DLLs to load (e.g., ntdll.dll for native processes, or the Wow64-specific DLLs for Wow64 processes).
    3. Once the target DLLs are loaded, the driver queues a user-mode APC to the process. This APC triggers the loading of the target DLL.
    4. The injected DLL is forced to execute by calling KeTestAlertThread(UserMode), which sets Thread->ApcState.UserApcPending to TRUE, causing immediate delivery on the next kernel-to-user transition.

    Constraints:

    • Injected DLLs must depend only on ntdll.dll because they are injected during very early process initialization.
    • Native processes (e.g., csrss.exe) and Pico processes (e.g., WSL applications) are not injected.
  4. Compile injdrv

    master

    To compile injdrv, you must first fetch the required git submodules (DetoursNT and Detours). Then, use Visual Studio 2017 to compile the included solution. The only required dependency is the Windows Driver Kit (WDK).

    Follow these steps:

    1. Clone the repository with submodules: git clone --recurse-submodules git@github.com:wbenny/injdrv.git
    2. Open the solution file in Visual Studio 2017.
    3. Ensure the WDK is installed.
    git clone --recurse-submodules git@github.com:wbenny/injdrv.git
  5. Install and use injdrv

    master

    To use injdrv for DLL injection, you must first enable Test-Signing on Windows and reboot.

    Setup:

    1. Enable Test-Signing (requires Administrator privileges):
      bcdedit /set testsigning on
      shutdown /r /t 0
    2. Reboot the machine.
    
    **Running the tool:**
    - **Install the driver:** Run `injldr -i`. The driver will stay active and wait for new processes to be created. Once a process is created, it will be hooked.
    - **Start tracing only:** Run `injldr` without parameters to start an ETW tracing session that prints information about called hooked functions.
    - **Uninstall the driver:** Run `injldr -u`.
    
    **Note:** The tool defaults to specific injection methods based on architecture: `InjMethodThunk` on Windows x86, `InjMethodThunkless` on Windows x64, and `InjMethodWow64LogReparse` on Windows ARM64. To change this (e.g., to inject an x86 DLL into an x86 Wow64 process), you must set the injection method to `InjMethodThunk` in the source and ensure the corresponding `injdll` architectures are compiled and placed in the same directory as `injldr.exe`.
    
  6. Understand the injdrv driver lifecycle

    master

    The injdrv driver operates using standard Windows kernel driver entry and unload routines, integrated with process and image load notifications:

    1. Initialization (DriverEntry):

      • Sets up the DriverUnload routine to DriverDestroy.
      • Initializes INJ_SETTINGS with buffers for DLL paths.
      • Calls InjCreateSettings to locate DLLs via the registry.
      • Calls InjInitialize to prepare the core injection logic.
      • Registers InjCreateProcessNotifyRoutineEx to monitor process creation.
      • Registers InjLoadImageNotifyRoutine to monitor image (DLL/EXE) loading.
    2. Cleanup (DriverDestroy):

      • Unregisters the LoadImage notification routine.
      • Unregisters the CreateProcess notification routine.
      • Calls InjDestroy to clean up allocated resources.
  7. Configure injection method based on architecture

    master

    The injdrv driver selects an injection method automatically during DriverEntry based on the compilation target architecture:

    ArchitectureMethodConstant
    x86 (_M_IX86)ThunkInjMethodThunk
    x64 (_M_AMD64)ThunklessInjMethodThunkless
    ARM64 (_M_ARM64)Wow64LogReparseInjMethodWow64LogReparse
  8. Mocking ETW functions for NTDLL-only DLLs

    master

    Because injected DLLs must only depend on ntdll.dll, they cannot directly use ETW functions from advapi32.dll (like EventWrite). However, since advapi32.dll functions are often just wrappers that redirect to ntdll.dll functions, you can mock them in your DLL by defining them to point to the EtwEvent* equivalents in ntdll.dll.

    //
    // Include support for ETW logging. 
    // Note that following functions are mocked, because they're
    // located in advapi32.dll.  Fortunately, advapi32.dll simply
    // redirects calls to these functions to the ntdll.dll.
    //
    
    #define EventActivityIdControl  EtwEventActivityIdControl
    #define EventEnabled            EtwEventEnabled
    #define EventProviderEnabled    EtwEventProviderEnabled
    #define EventRegister           EtwEventRegister
    #define EventSetInformation     EtwEventSetInformation
    #define EventUnregister         EtwEventUnregister
    #define EventWrite              EtwEventWrite
    #define EventWriteEndScenario   EtwEventWriteEndScenario
    #define EventWriteEx            EtwEventWriteEx
    #define EventWriteStartScenario EtwEventWriteStartScenario
    #define EventWriteString        EtwEventWriteString
    #define EventWriteTransfer      EtwEventWriteTransfer
    
    #include <evntprov.h>
  9. injldr CLI reference

    master

    The injldr utility provides the following command-line options:

    • -i: Installs the driver and waits for newly created processes to be hooked.
    • -u: Uninstalls the driver.
    • (No arguments): Starts an ETW tracing session to print information about called hooked functions.
  10. Initialize injection settings with InjCreateSettings

    master

    The InjCreateSettings function populates an INJ_SETTINGS structure by reading the driver's location from the Windows Registry. It specifically looks for the ImagePath key under the provided RegistryPath to determine the directory where the injection DLLs are located.

    It automatically resolves the paths for the following architecture-specific DLLs based on the driver's directory:

    • injdllx86.dll (x86)
    • injdllx64.dll (x64)
    • injdllARM.dll (ARM32)
    • injdllARM64.dll (ARM64)

    If the ImagePath is not a REG_EXPAND_SZ type or if the path cannot be parsed, the function returns an error status.

    NTSTATUS
    NTAPI
    InjCreateSettings(
      _In_ PUNICODE_STRING RegistryPath,
      _Inout_ PINJ_SETTINGS Settings
    )
  11. Use the injected DLL entry points

    master

    The injdll is a demonstrative DLL designed to be injected into target processes. It uses NtDllMain as its primary entry point to handle process attachment and detachment. When the DLL is attached to a process, it performs several stealth and monitoring actions: it hides itself from the Process Environment Block (PEB), registers an ETW provider for logging, and installs function hooks using Detours. When detached, it unhooks the functions to ensure system stability.

    // The primary entry point for the DLL
    EXTERN_C
    BOOL
    NTAPI
    NtDllMain(
      _In_ HANDLE ModuleHandle,
      _In_ ULONG Reason,
      _In_ LPVOID Reserved
    )
    {
      switch (Reason)
      {
        case DLL_PROCESS_ATTACH:
          OnProcessAttach(ModuleHandle);
          break;
    
        case DLL_PROCESS_DETACH:
          OnProcessDetach(ModuleHandle);
          break;
        // ...
      }
    
      return TRUE;
    }
  12. Reference: Injected DLL Hooked Functions

    master

    The following NTDLL functions are intercepted by the injected DLL to log their activity via ETW:

    • NtQuerySystemInformation: Logs the SystemInformationClass, the SystemInformation pointer, and the SystemInformationLength.
    • NtCreateThreadEx: Logs the ProcessHandle and the StartRoutine pointer.
    // Hooked NtQuerySystemInformation
    NTSTATUS NTAPI HookNtQuerySystemInformation(
      _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
      _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation,
      _In_ ULONG SystemInformationLength,
      _Out_opt_ PULONG ReturnLength
    );
    
    // Hooked NtCreateThreadEx
    NTSTATUS NTAPI HookNtCreateThreadEx(
      _Out_ PHANDLE ThreadHandle,
      _In_ ACCESS_MASK DesiredAccess,
      _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
      _In_ HANDLE ProcessHandle,
      _In_ PVOID StartRoutine,
      _In_opt_ PVOID Argument,
      _In_ ULONG CreateFlags,
      _In_ SIZE_T ZeroBits,
      _In_ SIZE_T StackSize,
      _In_ SIZE_T MaximumStackSize,
      _In_opt_ PPS_ATTRIBUTE_LIST AttributeList
    );