windows平台中,某些进程做了各种保护,比如hook了terminateProcess,又或者注册了进程终止函数的回调。当调用这些API或任务管理器终止该进程时,会被绕过,典型如某些杀毒软件,怎么才能终止这些进程了?
进程是由线程组成的,如果该进程名下所有线程都终止,此进程也会被windows回收和注销,终止进程的问题就转化成了终止线程;但如果直接调用terminateThread,同样面临terminateProcess被hook的窘境。深入逆向分析terminateThread后发现,真正终止线程的函数是PspTerminateThreadByPointer,整个调用逻辑为:NtTerminateThread->PsTerminateSystemThread->PspTerminateThreadByPointer,其中PsTerminateSystemThread是导入未文档化函数,可在驱动层掉用MmGetSystemRoutineAddress函数获取地址,进而得到PspTerminateThreadByPointer的地址(当然也能使用https://www.cnblogs.com/theseventhson/p/13024325.html该方法获取),核心函数如下:
1、根据起始和终止地址、特征码查找代码偏移
PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize) { PVOID pAddress = NULL; PUCHAR i = NULL; ULONG m = 0; // 扫描内存 for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++) { // 判断特征码 for (m = 0; m < ulMemoryDataSize; m++) { if (*(PUCHAR)(i + m) != pMemoryData[m]) { break; } } // 判断是否找到符合特征码的地址 if (m >= ulMemoryDataSize) { // 找到特征码位置, 获取紧接着特征码的下一地址 pAddress = (PVOID)(i + ulMemoryDataSize); break; } } return pAddress; }
2、(1)PsTerminateSystemThread是导出未文档化的函数,可以直接用MmGetSystemRoutineAddress得到函数地址
(2)windbg中根据PsTerminateSystemThread进一步查找PspTerminateThreadByPointer:这里用了e8作为特征码,直接定位到“e8dcf0fbff call nt!PspTerminateThreadByPointer (fffff803`d01c6210)”这行代码;
kd> u 0xfffff803`d0207110 //下面偏移x20 = 32byte处
nt!PsTerminateSystemThread:
fffff803`d0207110 4883ec28 sub rsp,28h
fffff803`d0207114 8bd1 mov edx,ecx
fffff803`d0207116 65488b0c2588010000 mov rcx,qword ptr gs:[188h]
fffff803`d020711f f7417400040000 test dword ptr [rcx+74h],400h
fffff803`d0207126 0f84a0630e00 je nt!PsTerminateSystemThread+0xe63bc (fffff803`d02ed4cc)
fffff803`d020712c 41b001 mov r8b,1
fffff803`d020712f e8dcf0fbff call nt!PspTerminateThreadByPointer (fffff803`d01c6210)
熟悉x86汇编的都知道:e8是call的硬编码,后面dcf0fbff是目标地址当对于当前的偏移,偏移为0xfffbf0dc。这个偏移很大,根据经验判断应该是个负数,0n-266020,那么PspTerminateThreadByPointer的计算方法:
PspTerminateThreadByPointer = 当前地址 + 4 + 偏移(负数)
=0xfffff800`63f7e130 + 0x4 + 0n-266020
= FFFFF80063F7E134 + 0n-266020
= -8,794,415,832,780 - 266020 //统一转成10进制
= -8,794,416,098,800
= FFFF F800 63F3 D210
继续windbg查一下:发现这个地址确实是PspTerminateThreadByPointer的,没错:
kd> u 0xfffff800`63f3d210
nt!PspTerminateThreadByPointer:
fffff800`63f3d210 48895c2408 mov qword ptr [rsp+8],rbx
fffff800`63f3d215 48896c2410 mov qword ptr [rsp+10h],rbp
fffff800`63f3d21a 4889742418 mov qword ptr [rsp+18h],rsi
fffff800`63f3d21f 57 push rdi
fffff800`63f3d220 4883ec30 sub rsp,30h
fffff800`63f3d224 8b81d0060000 mov eax,dword ptr [rcx+6D0h]
fffff800`63f3d22a 418ae8 mov bpl,r8b
fffff800`63f3d22d 488bb920020000 mov rdi,qword ptr [rcx+220h]
详细代码如下(这里pSpecialData用E8就好):
PVOID SearchPspTerminateThreadByPointer(PUCHAR pSpecialData, ULONG ulSpecialDataSize) { UNICODE_STRING ustrFuncName; PVOID pAddress = NULL; LONG lOffset = 0; PVOID pPsTerminateSystemThread = NULL; PVOID pPspTerminateThreadByPointer = NULL; // 先获取 PsTerminateSystemThread 函数地址 RtlInitUnicodeString(&ustrFuncName, L"PsTerminateSystemThread"); pPsTerminateSystemThread = MmGetSystemRoutineAddress(&ustrFuncName); if (NULL == pPsTerminateSystemThread) { ShowError("MmGetSystemRoutineAddress", 0); return pPspTerminateThreadByPointer; } // 然后, 查找 PspTerminateThreadByPointer 函数地址 pAddress = SearchMemory(pPsTerminateSystemThread, (PVOID)((PUCHAR)pPsTerminateSystemThread + 0xFF),//搜索255字节长度 pSpecialData, ulSpecialDataSize); if (NULL == pAddress) { ShowError("SearchMemory", 0); return pPspTerminateThreadByPointer; } // 先获取偏移, 再计算地址 lOffset = *(PLONG)pAddress;//0n-266020。注意这里向前跳,偏移是负数,有符号 pPspTerminateThreadByPointer = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset); return pPspTerminateThreadByPointer; }
3、得到PspTerminateThreadByPointer地址:
PVOID GetPspLoadImageNotifyRoutine() { PVOID pPspTerminateThreadByPointerAddress = NULL; RTL_OSVERSIONINFOW osInfo = { 0 }; UCHAR pSpecialData[50] = { 0 }; ULONG ulSpecialDataSize = 0; pSpecialData[0] = 0xE8; ulSpecialDataSize = 1; // E8 pSpecialData[0] = 0xE8; ulSpecialDataSize = 1; // 根据特征码获取地址 pPspTerminateThreadByPointerAddress = SearchPspTerminateThreadByPointer(pSpecialData, ulSpecialDataSize); return pPspTerminateThreadByPointerAddress; }
4、现在可以强杀进程了:
// 强制结束指定进程 NTSTATUS ForceKillProcess(HANDLE hProcessId) { PVOID pPspTerminateThreadByPointerAddress = NULL; PEPROCESS pEProcess = NULL; PETHREAD pEThread = NULL; PEPROCESS pThreadEProcess = NULL; NTSTATUS status = STATUS_SUCCESS; ULONG i = 0; #ifdef _WIN64 // 64 位 typedef NTSTATUS(__fastcall *PSPTERMINATETHREADBYPOINTER) (PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate); #else // 32 位 typedef NTSTATUS(*PSPTERMINATETHREADBYPOINTER) (PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate); #endif // 获取 PspTerminateThreadByPointer 函数地址 pPspTerminateThreadByPointerAddress = GetPspLoadImageNotifyRoutine(); if (NULL == pPspTerminateThreadByPointerAddress) { ShowError("GetPspLoadImageNotifyRoutine", 0); return FALSE; } // 获取结束进程的进程结构对象EPROCESS status = PsLookupProcessByProcessId(hProcessId, &pEProcess); if (!NT_SUCCESS(status)) { ShowError("PsLookupProcessByProcessId", status); return status; } // 遍历所有线程, 并结束所有指定进程的线程 for (i = 4; i < 0x80000; i = i + 4) { status = PsLookupThreadByThreadId((HANDLE)i, &pEThread); if (NT_SUCCESS(status)) { // 获取线程对应的进程结构对象 pThreadEProcess = PsGetThreadProcess(pEThread); // 结束指定进程的线程 if (pEProcess == pThreadEProcess) { ((PSPTERMINATETHREADBYPOINTER)pPspTerminateThreadByPointerAddress)(pEThread, 0, 1); DbgPrint("PspTerminateThreadByPointer Thread:%d\n", i); } // 凡是Lookup...,必需Dereference,否则在某些时候会造成蓝屏 ObDereferenceObject(pEThread); } } // 凡是Lookup...,必需Dereference,否则在某些时候会造成蓝屏 ObDereferenceObject(pEProcess); return status; }
5、测试环境: