技术4
经验6680
魅力17
人气208
分享36
原创6
注册时间2022-4-22
最后登录2024-12-18
阅读权限100
在线时间316 小时
主题122
回帖581

版主
禁止发言
  
- 积分
- 5367
- 人气
- 208
- 分享
- 36
 
|
本帖最后由 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;
}
|
评分
-
查看全部评分
|