1. 进程枚举
首先来介绍2个重要函数:
PCHAR PsGetProcessImageFileName(IN PEPROCESS pProcess)
这个函数是从内核导出的,声明后可以直接使用
作用是可以通过EPROCESS的指针获取对应进程映像名。实际上直接从EPROCESS结构内自己获取也可以。
该函数在ntifs.h内有声明,包含后就可以直接使用。
作用是通过PID获取对应进程的EPROCESS结构。
看一个例子:
NTSYSCALLAPI PCHAR PsGetProcessImageFileName(IN PEPROCESS pProcess);
VOID EnumProcess()
{
ULONG64 iPID = 0;
PEPROCESS EProcess = NULL;
NTSTATUS Status = STATUS_SUCCESS;
// 由于进程号都是以4为倍数的
for (iPID = 4; iPID < 20000; iPID += 4)
{
Status = PsLookupProcessByProcessId(iPID, &EProcess);
if (NT_SUCCESS(Status))
{
KdPrint(("%d\t%s\r\n", iPID, PsGetProcessImageFileName(EProcess)));
}
}
}
直接调用这个函数就可以列出进程列表。
2. 进程挂起与调度
在Ring3下Windows没有直接可以挂起进程的API,但是内核下却是有的。
挂起进程函数:
NTSTATUS PsSuspendProcess(PEPROCESS Process)
其可以通过进程的EPROCESS结构来挂起对应进程
调度进程函数:
NTSTATUS PsResumeProcess(PEPROCESS Process)
其可以通过进程的EPROCESS结构来继续对应进程
来看一下对应的功能代码:
/****************************
* 这些函数从内核导出但未声明 *
****************************/
// 从EPROCESS结构获取对应的进程映像名
NTSYSCALLAPI PCHAR PsGetProcessImageFileName(IN PEPROCESS pProcess);
// 进程挂起与调度内核函数
NTSYSCALLAPI NTSTATUS PsSuspendProcess(PEPROCESS Process);
NTSYSCALLAPI NTSTATUS PsResumeProcess(PEPROCESS Process);
VOID R0ProcessOpa(ULONG64 ulPID, BOOLEAN fHang)
{
PEPROCESS Process;
NTSTATUS status;
HANDLE hPID = (HANDLE)ulPID;
status = PsLookupProcessByProcessId(hPID, &Process);
if (!NT_SUCCESS(status))
{
return;
}
if (fHang)
{
PsSuspendProcess(Process);
}
else
{
PsResumeProcess(Process);
}
}
值的注意的是,我在Win7及以上版本(X64)发现这两个函数在内核中是导出的。只要声明即可。
但是在XP(X86)下实验时发现内核没导出。实际上可以通过暴利搜索搜到函数起始位置
这里我使用硬编码的方式来获取:
在我的XP版本下PsSuspendProcess地址是: 0x805cbdf8
在我的XP版本下PsResumeProcess地址是: 0x805cbcaa
接下来实现代码:
/****************************
* 这些函数从内核导出但未声明 *
****************************/
// 从EPROCESS结构获取对应的进程映像名
NTSYSCALLAPI PCHAR PsGetProcessImageFileName(IN PEPROCESS pProcess);
// 进程挂起与调度内核函数
NTSYSCALLAPI NTSTATUS PsSuspendProcess(PEPROCESS Process);
NTSYSCALLAPI NTSTATUS PsResumeProcess(PEPROCESS Process);
// 定义对应函数指针
typedef NTSTATUS(NTAPI *pfnPsSuspendProcess)(PEPROCESS Process);
typedef NTSTATUS (NTAPI *pfnPsResumeProcess)(PEPROCESS Process);
VOID R0ProcessOpa(ULONG64 ulPID, BOOLEAN fHang)
{
PEPROCESS Process;
NTSTATUS status;
HANDLE hPID = (HANDLE)ulPID;
// 通过获取硬编码函数地址
pfnPsResumeProcess PsResumeProcess = (pfnPsResumeProcess)0x805cbcaa;
pfnPsSuspendProcess PsSuspendProcess = (pfnPsSuspendProcess)0x805cbdf8;
status = PsLookupProcessByProcessId(hPID, &Process);
if (!NT_SUCCESS(status))
{
return;
}
if (fHang)
{
PsSuspendProcess(Process);
}
else
{
PsResumeProcess(Process);
}
}
3. 杀死进程
杀死进程主要使用:
NTSTATUS ZwTerminateProcess (_In_opt_ HANDLE ProcessHandle, _In_ NTSTATUS ExitStatus)
该函数使用方法非常简单,传入要杀死进程的句柄以及写入一个状态码就行。
在获取目标进程的句柄时需要用到ZwOpenProcess。需要传入的是ClientId中的UniqueProcess项。传给它目标进程PID。
NTSTATUS
ZwOpenProcess(
__out PHANDLE ProcessHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__in_opt PCLIENT_ID ClientId
);
实例代码:
BOOLEAN TerminateProcess(ULONG64 ProcessId)
{
CLIENT_ID Client = { 0 };
HANDLE hProcess = NULL;
NTSTATUS status;
OBJECT_ATTRIBUTES oa = { 0 };
Client.UniqueProcess = ProcessId;
InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
// 打开目标进程
status = ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &oa, &Client);
if (!NT_SUCCESS(status))
{
return(FALSE);
}
status = ZwTerminateProcess(hProcess, 0);
ZwClose(hProcess);
if (NT_SUCCESS(status))
{
return(TRUE);
}
return(FALSE);
}
(完)