X64论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

热搜: 样本 软件
查看: 131|回复: 2

浅谈在驱动程序中结束进程的三种方式

[复制链接]

0

技术

5

魅力

2

原创

实习版主

禁止发言

Rank: 7Rank: 7Rank: 7

积分
2432
人气
80
分享
24

最佳新人活跃会员

发表于 2022-8-11 15:33:01 | 显示全部楼层 |阅读模式
本帖最后由 YFSafe 于 2022-8-12 11:48 编辑

(注:一定要看到最后!!!)
在Windows平台,经常会碰到恶意或者流氓软件,我们使⽤常规的⽅式是⽆法结束其进程,有些甚⾄用任务管理器终⽌都会报错。在开始之前,我们需要了解⼀个进程的本质。

        -进程是基于线程调度才会执⾏的。

        -如果⼀个进程下所有线程都“死亡”,那么也就意味着进程的结束。

在用户层中,开发者可以通过Win32Api OpenProcess和TerminateProcess来终止一个给定的进程.但是,这种方法对于一个做了保护的进程完全无效,例如国内各大安全软件,一些rootkit恶意软件等.这时,我们就需要使用内核驱动来实现终止进程.
在WDK中,Windows提供了一个导出函数:ZwTerminateProcess来结束进程.它的原型如下
[C++] 纯文本查看 复制代码
NTSYSAPI NTSTATUS ZwTerminateProcess(
  [in, optional] HANDLE   ProcessHandle,
  [in]           NTSTATUS ExitStatus
);

此函数接受两个参数:
ProcessHandle:需结束进程的句柄
ExitStatus:进程的退出码
如果操作成功,ZwTerminateProcess的返回值为STATUS_SUCCESS.其他返回值对应的结果如下
返回码描述
STATUS_OBJECT_TYPE_MISMATCH指定的句柄不是进程句柄。
STATUS_INVALID_HANDLE指定的句柄无效。
STATUS_ACCESS_DENIED驱动程序无法访问指定的进程对象。
STATUS_PROCESS_IS_TERMINATING指定的进程已经终止。

如果调用者传入的句柄为当前进程的句柄,ZwTerminateProcess没有返回值. 要获得驱动程序可以为ProcessHandle参数指定的进程句柄,驱动程序可以调用ZwOpenProcess。句柄必须是内核句柄,只能在内核模式下访问的句柄。如果句柄是使用 OBJ_KERNEL_HANDLE 标志创建的,则它是内核句柄。 ZwOpenProcess函数打开进程对象的句柄并设置对该对象的访问权限.它的原型如下:
[C++] 纯文本查看 复制代码
NTSTATUS ZwOpenProcess(
  [out]          PHANDLE            ProcessHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,
  [in, optional] PCLIENT_ID         ClientId
);

参数:
[out] ProcessHandle:
一个指向HANDLE变量的指针,本函数会将得到的句柄存入此指针指向的变量内.
[in] DesiresAccess:
一个ACCESS_MASK值,其中包含调用者向进程对象请求的访问权限.
[in] ObjectAttributes:
一个指向OBJECT_ATTRIBUTES结构的指针,该结构指定要应用于进程对象句柄的属性.在 Windows Vista 和更高版本的 Windows 中,此结构的ObjectName字段必须设置为NULL.在 Windows Server 2003、Windows XP 和 Windows 2000 中,此字段可以作为选项指向对象名称.
[in, optional] ClientId:
指向客户端 ID 的指针,用于标识要打开其进程的线程.在 Windows Vista 和更高版本的 Windows 中,此参数必须是指向有效客户端 ID的非NULL指针.在 Windows Server 2003、Windows XP 和 Windows 2000 中,此参数是可选的,如果ObjectAttributes指向的OBJECT_ATTRIBUTES结构指定了对象名称,则可以将其设置为NULL .
如果调用成功,本函数返回STATUS_SUCCESS.其他返回值见下表.
返回码描述
STATUS_INVALID_PARAMETER_MIX在 Windows Vista 和更高版本的 Windows 中,调用方提供了对象名称或未能提供客户端 ID。在 Windows Server 2003、Windows XP 和 Windows 2000 中,调用者提供了对象名称和客户端 ID。
STATUS_INVALID_CID指定的客户端 ID 无效。
STATUS_INVALID_PARAMETER请求的访问权限对进程对象无效。
STATUS_ACCESS_DENIED无法授予请求的访问权限。
附:使用ZwTerminateProcess结束进程完整源代码

[C++] 纯文本查看 复制代码
BOOLEAN KillProcess(LONG pid)
{
        HANDLE ProcessHandle;
        NTSTATUS status;
        OBJECT_ATTRIBUTES ObjectAttributes;
        CLIENT_ID Cid;

        // 初始化ObjectAttributes和Cid
        HalQuerySystemInformation()
        InitializeObjectAttributes(&ObjectAttributes, 0, 0, 0, 0);
        Cid.UniqueProcess = (HANDLE)pid;
        Cid.UniqueThread = 0;
        // 打开进程句柄
        status = ZwOpenProcess(&ProcessHandle, PROCESS_ALL_ACCESS, &ObjectAttributes, &Cid);
        if (NT_SUCCESS(status))
        {
                DbgPrint("Open Process %d Successful!\n", pid);
                // 结束进程
                ZwTerminateProcess(ProcessHandle, status);
               
                // 关闭句柄
                ZwClose(ProcessHandle);
                return TRUE;
        }
        DbgPrint("Open Process %d Failed!\n", pid);
        return FALSE;
}




——————————————————————————————一根分割线——————————————————————————————



有一些安全软件可能会在内核层面hook住ZwTerminateProcess或者ZwOpenProcess,让我们不能通过使用这两个函数来结束他们的进程.接下来介绍的就是一种方法:内存清零.

内存清零的原理就是切入到要结束的进程内部,向进程的虚拟内存中写入垃圾数据,使进程自己崩溃退出.
要用到的内核api有:PsLookupProcessByProcessId,KeStackAttachProcess,ObOpenObjectByPointer,ZwTerminateProcess等等.

本函数主要逻辑:使用KeStackAttachProcess挂靠到进程虚拟内存空间,遍历进程0~0x7fffffff内存地址,对每个地址使用MmIsAddressValid判断地址是否合法.如地址合法,使用ProbeForWrite将地址调整为可读可写,最后使用memset向地址写入垃圾信息,使进程自行退出.(关键逻辑,请反复观看!!!)
请注意:在写入垃圾信息时一定要先用MmIsAddressValid判断地址是否合法!!!否则有一定概率会蓝屏!!!

上代码:
[C++] 纯文本查看 复制代码
BOOLEAN ZeroKill(ULONG PID)   //X32  X64
{
        NTSTATUS ntStatus = STATUS_SUCCESS;
        int i = 0;
        PVOID handle;
        PEPROCESS Eprocess;
        ntStatus = PsLookupProcessByProcessId(PID, &Eprocess);
        if (NT_SUCCESS(ntStatus))
        {
                PKAPC_STATE pKs = (PKAPC_STATE)ExAllocatePool(NonPagedPool, sizeof(PKAPC_STATE));
                KeStackAttachProcess(Eprocess, pKs);//Attach进程虚拟空间
                for (i = 0; i <= 0x7fffffff; i += 0x1000)
                {
                        if (MmIsAddressValid((PVOID)i))
                        {
                                _try
                                {
                                   ProbeForWrite((PVOID)i,0x1000,sizeof(ULONG));
                                   memset((PVOID)i,0xcc,0x1000);
                                }_except(1) { continue; }
                        }
                        else {
                                if (i > 0x1000000)  //填这么多足够破坏进程数据了
                                        break;
                        }
                }
                KeUnstackDetachProcess(pKs);
                if (ObOpenObjectByPointer((PVOID)Eprocess, 0, NULL, 0, NULL, KernelMode, &handle) != STATUS_SUCCESS)
                        return FALSE;
                ZwTerminateProcess((HANDLE)handle, STATUS_SUCCESS);
                ZwClose((HANDLE)handle);
                return TRUE;
        }
        return FALSE;
}



——————————————————————————————还是分割线——————————————————————————————
(注意:此方法是我在个人博客上写过的方法,现在原封不动的搬过来)

我们知道,线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
也就是说,当一个进程中的所有线程都被结束的时候,这个进程也就没有了存在的意义,也随之结束了。这,便是我们本文介绍的这种强制杀进程的实现原理,即把进程中的线程都杀掉,从而让进程消亡,实现间接杀进程的效果。
Windows 提供了一个导出的内核函数 PsTerminateSystemThread 来帮助我们结束线程,所以,类似 360、QQ 等也会对重点监测该函数,防止结束自己的线程。我们通过逆向 PsTerminateSystemThread 函数,可以发现该函数实际上调用了未导出的内核函数 PspTerminateThreadByPointer 来实现的结束线程的操作。所以,我们可以通过查找 PspTerminateThreadByPointer 函数地址,调用直接它来结束线程,就可以绕过绝大部分的进程保护,实现强制杀进程。

PspTerminateThreadByPointer的原型如下:
[C++] 纯文本查看 复制代码
NTSTATUS PspTerminateThreadByPointer (
          PETHREAD pEThread,
          NTSTATUS ntExitCode,
          BOOLEAN bDirectTerminate
     );


PspTerminateThreadByPointer 的函数指针的声明的调用约定如下:
[C++] 纯文本查看 复制代码
// 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 函数,如下所示:
[AppleScript] 纯文本查看 复制代码
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



那么,我们使用PspTerminateThreadByPointer强制杀进程的实现原理为:
首先,根据特征码扫描内存,获取 PspTerminateThreadByPointer 函数地址
然后,调用 PsLookupProcessByProcessId 函数,根据将要结束进程 ID 获取对应的进程结构对象 EPROCESS
接着,遍历所有的线程 ID,并调用 PsLookupThreadByThreadId 函数根据线程 ID 获取对应的线程结构 ETHREAD
然后,调用函数 PsGetThreadProcess 获取线程结构 ETHREAD 对应的进程结构 EPROCESS
这时,我们可以通过判断该进程是不是我们指定要结束的进程,若是,则调用 PspTerminateThreadByPointer 函数结束线程;否则,继续遍历下一个线程 ID
重复上述 3、4、5 的操作,直到线程遍历完毕
这样,我们就可以查杀指定进程的所有线程,线程被结束之后,进程也随之结束。注意的是,当调用 PsLookupProcessByProcessId 和 PsLookupThreadByThreadId 等 LookupXXX 系列函数获取对象的时候,都需要调用 ObDereferenceObject 函数释放对象,否则在某些时候会造成蓝屏。


编码实现

强制结束指定进程:


[C++] 纯文本查看 复制代码
// 强制结束指定进程
    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;
        }
        // 遍历4~0x80000线程id,结束指定进程下的线程
        for (i = 4; i < 0x80000; i = i + 4)
        {
            status = PsLookupThreadByThreadId((HANDLE)i, &pEThread);
            if (NT_SUCCESS(status))
            {
                // 获取当前遍历到的线程的父进程EProcess
                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 函数地址:

[C++] 纯文本查看 复制代码
// 获取 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 数组地址:
[C++] 纯文本查看 复制代码
// 根据特征码获取 PspTerminateThreadByPointer 数组地址
    PVOID SearchPspTerminateThreadByPointer(PUCHAR pSpecialData, ULONG ulSpecialDataSize)
    {
        UNICODE_STRING ustrFuncName;
        PVOID pAddress = NULL;
        LONG lOffset = 0;
        PVOID pPsTerminateSystemThread = NULL;
        PVOID pPspTerminateThreadByPointer = NULL;
        // 获取 PsTerminateSystemThread 函数地址并保存在pPsTerminateSystemThread中
        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;
    }

指定内存区域的特征码扫描:
[C++] 纯文本查看 复制代码
// 指定内存区域的特征码扫描
    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经验 +40 人气 +2 分享 +3 原创 +1 收起 理由
hackerbob + 20 + 2 + 2 赞一个!
henry217 + 20 + 1 + 1

查看全部评分

YF工作室驻x64论坛分部
工作室曾开发的软件:YFSafe安全软件,YFChat在线聊天软件,MBRTools等。
欢迎有能力的你加入我们一起共同进步。请发邮件至[email protected]

0

技术

1

魅力

0

原创

略知一二

Rank: 3Rank: 3

积分
838
人气
24
分享
0
发表于 2022-8-12 15:10:11 | 显示全部楼层
好厉害!

2

技术

5

魅力

2

原创

病毒研究组

Rank: 8Rank: 8

积分
2693
人气
143
分享
17

最佳新人活跃会员

发表于 2022-8-13 13:55:27 | 显示全部楼层
很好很好。。。。。。
今天下午休息,正好来论坛逛逛
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|X64论坛 ( 沪ICP备2020028431号-4 )|网站地图

GMT+8, 2022-9-25 22:48 , Processed in 0.058733 second(s), 10 queries , MemCache On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表