概述:使用 PsTerminateProcess 结束进程

参考文章:

0x01 PsTerminateProcess 说明

定义及实现

PsTerminateProcess 函数用于结束一个进程。其声明如下所示:

NTSTATUS
NTAPI
PsTerminateProcess(IN PEPROCESS Process, IN NTSTATUS ExitStatus)

其中第一个参数是 PEPROCESS 的指针,如果你只知道pid,可以通过 PsLookupProcessByProcessId 获得,而第二个参数指定退出状态码。

PsTerminateProcess 函数的实现非常简单,就是调用了 PspTerminateProcess

PsTerminateProcess 的实现

NTSTATUS
NTAPI
PsTerminateProcess(IN PEPROCESS Process,
                   IN NTSTATUS ExitStatus)
{
    /* Call the internal API */
    return PspTerminateProcess(Process, ExitStatus);
}

PsTerminateProcess 是一个导出函数,而 PspTerminateProcess 是一个内部函数。也就是说,你只要包含的正确的头文件,就可以使用 PsTerminateProcess 杀掉一个进程,而想用 PspTerminateProcess 你就得多费点时间把它的函数地址找出来。所以为安全起见,大多数的杀毒软件都有HookPsTerminateProcess,“PspTerminateProcess` 就不一定了。

如下所示为 PsTerminateProcess 的反汇编代码:

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 函数

函数定义

NTSTATUS 
PspTerminateProcess(
    EPROCESS pEprocess,
    NTSTATUS ExitCode);
参数说明
pEprocess要关闭的进程的EPROCESS
ExitCode进程的退出码

由于这个函数是未导出的,所以不能直接调用,要先在内存中找到这个函数才可以调用。这里选择找到这个函数的办法是通过内核模块遍历的办法来查找这个函数。

函数实现

PspTerminateProcess 函数实现如下所示:

PETHREAD Thread;
NTSTATUS Status = STATUS_NOTHING_TO_TERMINATE;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG,
         "Process: %p ExitStatus: %p\n", Process, ExitStatus);
PSREFTRACE(Process);
 
/* Check if this is a Critical Process */
if (Process->BreakOnTermination)
{
     /* Break to debugger */
     PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
                           Process,
                           Process->ImageFileName);
}

先是检查是否有调试器挂在上面并且要求退出进程时中断到调试器,如果需要则调用 PspCatchCriticalBreak 中断过去。PspCatchCriticalBreak 的实现我总觉得应该是ReactOS 里调内核的临时做法,NT内核应该不是这么干的,所以暂时略过不看。(其实逻辑很简单,一眼就能扫明白),接着看 PspTerminateProcess

/* Set the delete flag */
InterlockedOr((PLONG)&Process->Flags, PSF_PROCESS_DELETE_BIT);
然后调用如下方法在Flags域里置位,标明自己已经结束
 
/* Get the first thread */
Thread = PsGetNextProcessThread(Process, NULL);
while (Thread)
{
    /* Kill it */
    PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
    Thread = PsGetNextProcessThread(Process, Thread);
 
    /* We had at least one thread, so termination is OK */
    Status = STATUS_SUCCESS;
}

接着就是最关键的一步:先找到进程下的第一个线程,然后顺着线程列表访问所有的线程,调用 PspTerminateThreadByPointer 结束它。等所有的线程都退出的时候,进程自然也就消亡了(最后一个线程退出时负责干掉进程)。

PsGetNextProcessThread 是在 EPROCESS 结构 ThreadListHead 指向的线程列表里遍历,程序很简单。有趣的是 PspTerminateThreadByPointer 函数,这是结束线程的核心程序。程序主体在我手头的ReactOS里没有,泄露的nt4代码看过但又不便贴出,所以只能略说:大致的做法就是生成一个APC插入到目标线程里,而该APC所作的事情就是调用 PspExitThread 退出。

关于APC的详细内容请看这里。相信有心的筒子完全有能力自己山寨一个 PspTerminateThreadByPointer 出来。

PspTerminateProcess所作的最后一件事情呢,就是检测是否真有线程被结束,如果根本就没有这样的线程,那还得自己负责把句柄表里表示自己的那一项清掉:

/* Check if there was nothing to terminate or if we have a debug port */
if ((Status == STATUS_NOTHING_TO_TERMINATE) || (Process->DebugPort))
{
    /* Clear the handle table anyway */
    ObClearProcessHandleTable(Process);
}

但凡有追求一点的杀软呢,这里面的绝大多数函数比如 PsTerminateProcess,“PspTerminateProcessPspTerminateThreadByPointer` 等都会有hook住,防止有人干坏事。强势一点的连APC相关的操作也要hook住。

如何查找 PspTerminateProcess

#include <ntifs.h>
 
typedef NTSTATUS(*pfnPspTerminateThreadByPointer)(PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate);
 
VOID DriverUnload(IN PDRIVER_OBJECT driverObject);
PVOID FindTargetFunc();    // 寻找函数地址
PEPROCESS PsGetThreadProcess(PETHREAD pEThraed);  // 根据EPROCESS得到EPROCESS
 
ULONG g_uPID = 176;  // 要关闭的进程PID
 
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;
    }
 
    //获取要关闭的进程的EPROCESS
    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;
}