zhang888 发表于 10-21 21:13:53

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

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

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

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

在用户层中,开发者可以通过Win32Api OpenProcess和TerminateProcess来终止一个给定的进程.但是,这种方法对于一个做了保护的进程完全无效,例如国内各大安全软件,一些rootkit恶意软件等.这时,我们就需要使用内核驱动来实现终止进程.
在WDK中,Windows提供了一个导出函数:ZwTerminateProcess来结束进程.它的原型如下
纯文本查看 复制代码NTSYSAPI NTSTATUS ZwTerminateProcess( HANDLE   ProcessHandle,         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函数打开进程对象的句柄并设置对该对象的访问权限.它的原型如下:
纯文本查看 复制代码NTSTATUS ZwOpenProcess(          PHANDLE            ProcessHandle,         ACCESS_MASK      DesiredAccess,         POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID         ClientId);
参数:
ProcessHandle:
一个指向HANDLE变量的指针,本函数会将得到的句柄存入此指针指向的变量内.
DesiresAccess:
一个ACCESS_MASK值,其中包含调用者向进程对象请求的访问权限.
ObjectAttributes:
一个指向OBJECT_ATTRIBUTES结构的指针,该结构指定要应用于进程对象句柄的属性.在 Windows Vista 和更高版本的 Windows 中,此结构的ObjectName字段必须设置为NULL.在 Windows Server 2003、Windows XP 和 Windows 2000 中,此字段可以作为选项指向对象名称.
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结束进程完整源代码

纯文本查看 复制代码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判断地址是否合法!!!否则有一定概率会蓝屏!!!

上代码:
纯文本查看 复制代码BOOLEAN ZeroKill(ULONG PID)   //X32X64{      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的原型如下:
纯文本查看 复制代码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:    fffff800`83904523 f7417400080000test    dword ptr ,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 函数释放对象,否则在某些时候会造成蓝屏。


编码实现

强制结束指定进程:


纯文本查看 复制代码// 强制结束指定进程    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(&quot;PsLookupProcessByProcessId&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(&quot;PspTerminateThreadByPointer Thread:%d\n&quot;, i);                }                // 凡是Lookup...,必需Dereference,否则在某些时候会造成蓝屏                ObDereferenceObject(pEThread);            }      }      // 凡是Lookup...,必需Dereference,否则在某些时候会造成蓝屏      ObDereferenceObject(pEProcess);      return status;    }
获取 PspTerminateThreadByPointer 函数地址:

纯文本查看 复制代码// 获取 PspTerminateThreadByPointer 函数地址    PVOID GetPspLoadImageNotifyRoutine()    {      PVOID pPspTerminateThreadByPointerAddress = NULL;      RTL_OSVERSIONINFOW osInfo = { 0 };      UCHAR pSpecialData = { 0 };      ULONG ulSpecialDataSize = 0;      // 获取系统版本信息, 判断系统版本      RtlGetVersion(&osInfo);      if (6 == osInfo.dwMajorVersion)      {            if (1 == osInfo.dwMinorVersion)            {                // Win7    #ifdef _WIN64                // 64 位                // E8                pSpecialData = 0xE8;                ulSpecialDataSize = 1;    #else                // 32 位                // E8                pSpecialData = 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 = 0xE9;                ulSpecialDataSize = 1;    #else                // 32 位                // E8                pSpecialData = 0xE8;                ulSpecialDataSize = 1;    #endif                        }      }      else if (10 == osInfo.dwMajorVersion)      {            // Win10    #ifdef _WIN64            // 64 位            // E9            pSpecialData = 0xE9;            ulSpecialDataSize = 1;    #else            // 32 位            // E8            pSpecialData = 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 函数地址并保存在pPsTerminateSystemThread中      RtlInitUnicodeString(&ustrFuncName, L&quot;PsTerminateSystemThread&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;    }
指定内存区域的特征码扫描:
纯文本查看 复制代码// 指定内存区域的特征码扫描    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)                {                  break;                }            }            // 判断是否找到符合特征码的地址            if (m >= ulMemoryDataSize)            {                // 找到特征码位置, 获取紧接着特征码的下一地址                pAddress = (PVOID)(i + ulMemoryDataSize);                break;            }      }      return pAddress;    }
页: [1]
查看完整版本: 浅谈在驱动程序中结束进程的三种方式