概述:使用 PsTerminateProcess
结束进程
参考文章:
0x01 PsTerminateProcess
说明
定义及实现
PsTerminateProcess 函数用于结束一个进程。其声明如下所示:
1 2 3 NTSTATUS NTAPI PsTerminateProcess (IN PEPROCESS Process, IN NTSTATUS ExitStatus)
其中第一个参数是 PEPROCESS
的指针,如果你只知道pid,可以通过 PsLookupProcessByProcessId
获得,而第二个参数指定退出状态码。
PsTerminateProcess
函数的实现非常简单,就是调用了 PspTerminateProcess
PsTerminateProcess
的实现
1 2 3 4 5 6 7 8 NTSTATUS NTAPI PsTerminateProcess (IN PEPROCESS Process, IN NTSTATUS ExitStatus) { return PspTerminateProcess (Process, ExitStatus); }
PsTerminateProcess
是一个导出函数,而 PspTerminateProcess
是一个内部函数。也就是说,你只要包含的正确的头文件,就可以使用 PsTerminateProcess
杀掉一个进程,而想用 PspTerminateProcess
你就得多费点时间把它的函数地址找出来。所以为安全起见,大多数的杀毒软件 都有hook住 PsTerminateProcess
,``PspTerminateProcess` 就不一定了。
如下所示为 PsTerminateProcess
的反汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 kd> u PsTerminateSystemThread L10 nt!PsTerminateSystemThread: 805d3594 8bff mov edi ,edi 805d3596 55 push ebp 805d3597 8bec mov ebp ,esp 805d3599 64a124010000 mov eax ,dword ptr fs :[00000124h ] 805d359f f6804802000010 test byte ptr [eax +248h ],10h 805d35a6 7507 jne nt!PsTerminateSystemThread+0x1b (805d35af) 805d35a8 b80d0000c0 mov eax ,0C000000Dh 805d35ad eb09 jmp nt!PsTerminateSystemThread+0x24 (805d35b8) 805d35af ff7508 push dword ptr [ebp +8 ] 805d35b2 50 push eax 805d35b3 e828fcffff call nt!PspTerminateThreadByPointer (805d31e0) 805d35b8 5d pop ebp 805d35b9 c20400 ret 4
这里的 e82bfcffff
可以作为特征码。
PspTerminateProcess
函数
函数定义
1 2 3 4 NTSTATUS PspTerminateProcess ( EPROCESS pEprocess, NTSTATUS ExitCode);
参数
说明
pEprocess
要关闭的进程的EPROCESS
ExitCode
进程的退出码
由于这个函数是未导出的,所以不能直接调用,要先在内存中找到这个函数才可以调用。这里选择找到这个函数的办法是通过内核模块遍历的办法来查找这个函数。
函数实现
PspTerminateProcess
函数实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PETHREAD Thread; NTSTATUS Status = STATUS_NOTHING_TO_TERMINATE;PAGED_CODE ();PSTRACE (PS_KILL_DEBUG, "Process: %p ExitStatus: %p\n" , Process, ExitStatus);PSREFTRACE (Process);if (Process->BreakOnTermination) { PspCatchCriticalBreak ("Terminating critical process 0x%p (%s)\n" , Process, Process->ImageFileName); }
先是检查是否有调试器挂在上面并且要求退出进程时中断到调试器,如果需要则调用 PspCatchCriticalBreak
中断过去。PspCatchCriticalBreak
的实现我总觉得应该是ReactOS 里调内核的临时做法,NT内核应该不是这么干的,所以暂时略过不看。(其实逻辑很简单,一眼就能扫明白),接着看 PspTerminateProcess
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 InterlockedOr ((PLONG)&Process->Flags, PSF_PROCESS_DELETE_BIT); 然后调用如下方法在Flags域里置位,标明自己已经结束 Thread = PsGetNextProcessThread (Process, NULL );while (Thread) { PspTerminateThreadByPointer (Thread, ExitStatus, FALSE); Thread = PsGetNextProcessThread (Process, Thread); Status = STATUS_SUCCESS; }
接着就是最关键的一步:先找到进程下的第一个线程,然后顺着线程列表访问所有的线程,调用 PspTerminateThreadByPointer
结束它。等所有的线程都退出的时候,进程自然也就消亡了(最后一个线程退出时负责干掉进程)。
PsGetNextProcessThread
是在 EPROCESS
结构 ThreadListHead
指向的线程列表里遍历,程序很简单。有趣的是 PspTerminateThreadByPointer
函数,这是结束线程的核心程序。程序主体在我手头的ReactOS里没有,泄露的nt4代码看过但又不便贴出,所以只能略说:大致的做法就是生成一个APC插入到目标线程里,而该APC所作的事情就是调用 PspExitThread
退出。
关于APC的详细内容请看这里 。相信有心的筒子完全有能力自己山寨一个 PspTerminateThreadByPointer
出来。
PspTerminateProcess所作的最后一件事情呢,就是检测是否真有线程被结束,如果根本就没有这样的线程,那还得自己负责把句柄表里表示自己的那一项清掉:
1 2 3 4 5 6 if ((Status == STATUS_NOTHING_TO_TERMINATE) || (Process->DebugPort)) { ObClearProcessHandleTable (Process); }
但凡有追求一点的杀软呢,这里面的绝大多数函数比如 PsTerminateProcess
,``PspTerminateProcess和
PspTerminateThreadByPointer` 等都会有hook住,防止有人干坏事。强势一点的连APC相关的操作也要hook住。
如何查找 PspTerminateProcess
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <ntifs.h> typedef NTSTATUS (*pfnPspTerminateThreadByPointer) (PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate) ; VOID DriverUnload (IN PDRIVER_OBJECT driverObject) ;PVOID FindTargetFunc () ; PEPROCESS PsGetThreadProcess (PETHREAD pEThraed) ; ULONG g_uPID = 176 ; NTSTATUS DriverEntry (IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath) { NTSTATUS status = STATUS_SUCCESS; PETHREAD pEThread = NULL ; PEPROCESS pEProcess = NULL , pThreadProcess = NULL ; ULONG uThreadId = 0 ; pfnPspTerminateThreadByPointer PspTerminateThreadByPointer = NULL ; PspTerminateThreadByPointer = (pfnPspTerminateThreadByPointer)FindTargetFunc (); if (PspTerminateThreadByPointer == NULL ) { goto exit; } status = PsLookupProcessByProcessId ((HANDLE)g_uPID, &pEProcess); if (!NT_SUCCESS (status)) { DbgPrint ("PsLookupProcessByProcessId Error 0x%X\r\n" , status); goto exit; } for (uThreadId = 4 ; uThreadId < 0x8000 ; uThreadId += 4 ) { status = PsLookupThreadByThreadId ((HANDLE)uThreadId, &pEThread); if (NT_SUCCESS (status)) { pThreadProcess = PsGetThreadProcess (pEThread); if (pThreadProcess == pEProcess) { PspTerminateThreadByPointer (pEThread, 0 , 1 ); DbgPrint ("成功关闭线程\r\n" ); } ObDereferenceObject (pEThread); } } ObDereferenceObject (pEProcess); driverObject->DriverUnload = DriverUnload; exit: return STATUS_SUCCESS; } VOID DriverUnload (IN PDRIVER_OBJECT driverObject) { DbgPrint ("驱动卸载完成\r\n" ); } PVOID FindTargetFunc () { PVOID pFunAddr = NULL ; PUCHAR pPsTerminateSystemThreadAddr = NULL ; UNICODE_STRING uStrFuncName = RTL_CONSTANT_STRING (L"PsTerminateSystemThread" ); pPsTerminateSystemThreadAddr = (PUCHAR)MmGetSystemRoutineAddress (&uStrFuncName); while (MmIsAddressValid (pPsTerminateSystemThreadAddr) && *pPsTerminateSystemThreadAddr != 0xC2 ) { if (*pPsTerminateSystemThreadAddr == 0xE8 ) { pFunAddr = (PVOID)((ULONG)pPsTerminateSystemThreadAddr + 5 + *(PULONG)(pPsTerminateSystemThreadAddr + 1 )); break ; } pPsTerminateSystemThreadAddr++; } return pFunAddr; }