浅谈在驱动程序中结束进程的三种方式
(注:一定要看到最后!!!)在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("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 函数地址:
纯文本查看 复制代码// 获取 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"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) { break; } } // 判断是否找到符合特征码的地址 if (m >= ulMemoryDataSize) { // 找到特征码位置, 获取紧接着特征码的下一地址 pAddress = (PVOID)(i + ulMemoryDataSize); break; } } return pAddress; }
页:
[1]