使用 PspTerminateThreadByPointer 强制结束进程

实现过程

我们知道,线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

也就是说,当一个进程中的所有线程都被结束的时候,这个进程也就没有了存在的意义,也随之结束了。这,便是我们本文介绍的这种强制杀进程的实现原理,即把进程中的线程都杀掉,从而让进程消亡,实现间接杀进程的效果。

Windows 提供了一个导出的内核函数 PsTerminateSystemThread 来帮助我们结束线程,所以,类似 360、QQ 等也会对重点监测该函数,防止结束自己的线程。我们通过逆向 PsTerminateSystemThread 函数,可以发现该函数实际上调用了未导出的内核函数 PspTerminateThreadByPointer 来实现的结束线程的操作。所以,我们可以通过查找 PspTerminateThreadByPointer 函数地址,调用直接它来结束线程,就可以绕过绝大部分的进程保护,实现强制杀进程。

PspTerminateThreadByPointer

    NTSTATUS PspTerminateThreadByPointer (
          PETHREAD pEThread, 
          NTSTATUS ntExitCode, 
          BOOLEAN bDirectTerminate
     );

但要注意,PspTerminateThreadByPointer 的函数指针的声明的调用约定:

    // 32 位
    typedef NTSTATUS(*PSPTERMINATETHREADBYPOINTER_X86) (
          PETHREAD pEThread, 
          NTSTATUS ntExitCode, 
          BOOLEAN bDirectTerminate
     );
    // 64 位
    typedef NTSTATUS(__fastcall *PSPTERMINATETHREADBYPOINTER_X64) (
          PETHREAD pEThread, 
          NTSTATUS ntExitCode, 
          BOOLEAN bDirectTerminate
     );

其中,PsTerminateSystemThread 里会调用 PspTerminateThreadByPointer 函数。我们使用 WinDbg 逆向 Win8.1 x64 里的 PsTerminateSystemThread 函数,如下所示:

    nt!PsTerminateSystemThread:
    fffff800`83904518 8bd1            mov     edx,ecx
    fffff800`8390451a 65488b0c2588010000 mov   rcx,qword ptr gs:[188h]
    fffff800`83904523 f7417400080000  test    dword ptr [rcx+74h],800h
    fffff800`8390452a 7408            je      nt!PsTerminateSystemThread+0x1c (fffff800`83904534)
    fffff800`8390452c 41b001          mov     r8b,1
    fffff800`8390452f e978d9fcff      jmp     nt!PspTerminateThreadByPointer (fffff800`838d1eac)
    fffff800`83904534 b80d0000c0      mov     eax,0C000000Dh
    fffff800`83904539 c3              ret

由上面代码可以知道,我们可以通过扫描 PsTerminateSystemThread 内核函数中的特征码,从而获取 PspTerminateThreadByPointer 函数的偏移,再根据偏移计算出该函数的地址。其中,不同系统中的特征码也会不同,下面是我使用 WinDbg 逆向各个系统上总结的特征码的情况:
在这里插入图片描述
那么,我们强制杀进程的实现原理为:

  • 首先,根据特征码扫描内存,获取 PspTerminateThreadByPointer 函数地址

  • 然后,调用 PsLookupProcessByProcessId 函数,根据将要结束进程 ID 获取对应的进程结构对象 EPROCESS

  • 接着,遍历所有的线程 ID,并调用 PsLookupThreadByThreadId 函数根据线程 ID 获取对应的线程结构 ETHREAD

  • 然后,调用函数 PsGetThreadProcess 获取线程结构 ETHREAD 对应的进程结构 EPROCESS

  • 这时,我们可以通过判断该进程是不是我们指定要结束的进程,若是,则调用 PspTerminateThreadByPointer 函数结束线程;否则,继续遍历下一个线程 ID

  • 重复上述 3、4、5 的操作,直到线程遍历完毕

这样,我们就可以查杀指定进程的所有线程,线程被结束之后,进程也随之结束。注意的是,当调用 PsLookupProcessByProcessId 和 PsLookupThreadByThreadId 等 LookupXXX 系列函数获取对象的时候,都需要调用 ObDereferenceObject 函数释放对象,否则在某些时候会造成蓝屏。

编码实现

强制结束指定进程:

    // 强制结束指定进程
    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;
    }

获取 PspTerminateThreadByPointer 函数地址:

    // 获取 PspTerminateThreadByPointer 函数地址
    PVOID GetPspLoadImageNotifyRoutine()
    {
        PVOID pPspTerminateThreadByPointerAddress = NULL;
        RTL_OSVERSIONINFOW osInfo = { 0 };
        UCHAR pSpecialData[50] = { 0 };
        ULONG ulSpecialDataSize = 0;
        // 获取系统版本信息, 判断系统版本
        RtlGetVersion(&osInfo);
        if (6 == osInfo.dwMajorVersion)
        {
            if (1 == osInfo.dwMinorVersion)
            {
                // Win7
    #ifdef _WIN64
                // 64 位
                // E8
                pSpecialData[0] = 0xE8;
                ulSpecialDataSize = 1;
    #else
                // 32 位
                // E8
                pSpecialData[0] = 0xE8;
                ulSpecialDataSize = 1;
    #endif    
            }
            else if (2 == osInfo.dwMinorVersion)
            {
                // Win8
    #ifdef _WIN64
                // 64 位
    #else
                // 32 位
    #endif
            }
            else if (3 == osInfo.dwMinorVersion)
            {
                // Win8.1
    #ifdef _WIN64
                // 64 位
                // E9
                pSpecialData[0] = 0xE9;
                ulSpecialDataSize = 1;
    #else
                // 32 位
                // E8
                pSpecialData[0] = 0xE8;
                ulSpecialDataSize = 1;
    #endif            
            }
        }
        else if (10 == osInfo.dwMajorVersion)
        {
            // Win10
    #ifdef _WIN64
            // 64 位
            // E9
            pSpecialData[0] = 0xE9;
            ulSpecialDataSize = 1;
    #else
            // 32 位
            // E8
            pSpecialData[0] = 0xE8;
            ulSpecialDataSize = 1;
    #endif
        }
        // 根据特征码获取地址
        pPspTerminateThreadByPointerAddress = SearchPspTerminateThreadByPointer(pSpecialData, ulSpecialDataSize);
        return pPspTerminateThreadByPointerAddress;
    }

根据特征码获取 PspTerminateThreadByPointer 数组地址:

    // 根据特征码获取 PspTerminateThreadByPointer 数组地址
    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),
            pSpecialData, ulSpecialDataSize);
        if (NULL == pAddress)
        {
            ShowError("SearchMemory", 0);
            return pPspTerminateThreadByPointer;
        }
        // 先获取偏移, 再计算地址
        lOffset = *(PLONG)pAddress;
        pPspTerminateThreadByPointer = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset);
        return pPspTerminateThreadByPointer;
    }

指定内存区域的特征码扫描:

    // 指定内存区域的特征码扫描
    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;
    }
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值