找回密码
 立即注册
搜索

[经验杂志] 浅谈在驱动程序中结束进程的三种方式

[复制链接]
发表于 10-21 21:13:53 | 显示全部楼层 |阅读模式
(注:一定要看到最后!!!)
在Windows平台,经常会碰到恶意或者流氓软件,我们使⽤常规的⽅式是⽆法结束其进程,有些甚⾄用任务管理器终⽌都会报错。在开始之前,我们需要了解⼀个进程的本质。

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

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

用户层中,开发者可以通过Win32Api OpenProcess和TerminateProcess来终止一个给定的进程.但是,这种方法对于一个做了保护的进程完全无效,例如国内各大安全软件,一些rootkit恶意软件等.这时,我们就需要使用内核驱动来实现终止进程.
在WDK中,Windows提供了一个导出函数:ZwTerminateProcess来结束进程.它的原型如下
[C++] 纯文本查看 复制代码NTSYSAPI NTSTATUS ZwTerminateProcess(  [in, optional] HANDLE   ProcessHandle,  [in]           NTSTATUS ExitStatus);
此函数接受两个参数:
ProcessHandle:需结束进程的句柄
ExitStatus:进程的退出码
如果操作成功,ZwTerminateProcess的返回值为STATUS_SUCCESS.其他返回值对应的结果如下
[table][tr][td]返回码[/td][td]描述[/td][/tr][tr][td]STATUS_OBJECT_TYPE_MISMATCH[/td][td]指定的句柄不是进程句柄。[/td][/tr][tr][td]STATUS_INVALID_HANDLE[/td][td]指定的句柄无效。[/td][/tr][tr][td]STATUS_ACCESS_DENIED[/td][td]驱动程序无法访问指定的进程对象。[/td][/tr][tr][td]STATUS_PROCESS_IS_TERMINATING[/td][td]指定的进程已经终止。
如果调用者传入的句柄为当前进程的句柄,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.其他返回值见下表.
[table][tr][td]返回码[/td][td]描述[/td][/tr][tr][td]STATUS_INVALID_PARAMETER_MIX[/td][td]在 Windows Vista 和更高版本的 Windows 中,调用方提供了对象名称或未能提供客户端 ID。在 Windows Server 2003、Windows XP 和 Windows 2000 中,调用者提供了对象名称和客户端 ID。[/td][/tr][tr][td]STATUS_INVALID_CID[/td][td]指定的客户端 ID 无效。[/td][/tr][tr][td]STATUS_INVALID_PARAMETER[/td][td]请求的访问权限对进程对象无效。[/td][/tr][tr][td]STATUS_ACCESS_DENIED[/td][td]无法授予请求的访问权限。附:使用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(&rocessHandle, 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有sLookupProcessByProcessId,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(&quot;GetPspLoadImageNotifyRoutine&quot;, 0);            return FALSE;        }        // 获取被结束进程的进程的EPROCESS        status = PsLookupProcessByProcessId(hProcessId, &pEProcess);        if (!NT_SUCCESS(status))        {            ShowError(&quotsLookupProcessByProcessId&quot;, 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(&quotspTerminateThreadByPointer Thread:%d\n&quot;, 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&quotsTerminateSystemThread&quot;);        pPsTerminateSystemThread = MmGetSystemRoutineAddress(&ustrFuncName);        if (NULL == pPsTerminateSystemThread)        {            ShowError(&quot;MmGetSystemRoutineAddress&quot;, 0);            return pPspTerminateThreadByPointer;        }        // 查找 PspTerminateThreadByPointer 函数地址        pAddress = SearchMemory(pPsTerminateSystemThread,            (PVOID)((PUCHAR)pPsTerminateSystemThread + 0xFF),            pSpecialData, ulSpecialDataSize);        if (NULL == pAddress)        {            ShowError(&quot;SearchMemory&quot;, 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;    }
声明
1,本帖收录内容来源于系统采集或网友自主提交,不代表本网站立场!
2,本帖收录内容,仅供参考使用,本站不对其安全性,正确性等作出保证。
3,如您认为本帖收录内容侵犯了您的版权或者违规,请在右下角进行举报,发现直接删帖!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|wcbb导航网-属于年轻人的导航网-网站导航-素材导航论坛! ( 桂ICP备2024029389号-2 )|网站地图

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