实现原理
进程 EPROCESS 结构存储着进程一切信息,所以这个结构体很庞大,而且,不同系统, EPROCESS 结构定义也不相同,它里面的一些成员的偏移也不是固定一成不变的。
可使用函数 PsGetCurrentProcess 获取当前进程结构 EPROCESS; PsGetProcessId 从 EPROCESS 结构中获取进程的 PID 信息;使用 PsGetProcessImageFileName 函数,从 ERPROCESS 结构中获取进程的名称信息。
其中,EPROCESS 结构中的成员 ActiveProcessLinks 的数据类型是 LIST_ENTRY。它是一个进程活动双向链表,ActiveProcessLinks 的 Flink 成员指向下一个进程结构 EPROCESS 的 ActiveProcessLinks 成员的地址;ActiveProcessLinks 的 Blink 成员指向上一个进程结构 EPROCESS 的 ActiveProcessLinks 成员的地址。
下面是使用 WinDbg 获取 Win10 64 位系统下的 EPROCESS 结构定义代码:
lkd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2d8 ProcessLock : _EX_PUSH_LOCK
+0x2e0 RundownProtect : _EX_RUNDOWN_REF
+0x2e8 UniqueProcessId : Ptr64 Void
+0x2f0 ActiveProcessLinks : _LIST_ENTRY
+0x300 Flags2 : Uint4B
… …(省略)
当我们获取 ActiveProcessLinks 的地址的时候,我们可以根据 ActiveProcessLinks 在 EPROCESS 结构的偏移,计算出 EPROCESS 结构的地址。这样,我们就可以一直遍历下去,获取所有进程的信息。
事实上内核函数 ZwQuerySystemInformation 对进程查询也是找到活动进程链表头,然后遍历活动进程链。最后把每一个EPROCESS中包含的基本信息返回(包括进程ID名字等)。这就给进程隐藏的实现,提供了理论支持。
对于进程的隐藏,就是通过从 EPROCESS 结构中获取进程信息,判断该进程是否是要隐藏的进程,若是,则调用 RemoveEntryList 内核函数摘链,实现隐藏。所谓的摘链就是上一个 EPROCESS 结构成员 ActiveProcessLinks 的Flink 成员指向当前 EPROCESS 结构的下一个进程结构 EPROCESS 的 ActiveProcessLinks 成员的地址;当前 EPROCESS 结构的下一个进程结构 EPROCESS 成员 ActiveProcessLinks 的 Blink 指向当前 EPROCESS 结构的上一个 EPROCESS 结构的 ActiveProcessLinks 成员的地址。这样,当前的 EPROCESS 结构成员 ActiveProcessLinks 就没有链表指向,也就不能通过活动进程链遍历出来,于是便实现了指定进程的隐藏,而且这种隐藏方式并不影响被隐藏的进程正常运行。
其中,我们需要确定的就是,不同系统上,ActiveProcessLinks 在 EPROCESS 结构中的偏移位置。我们使用 WinDbg 在不同的系统中进行内核调试,查看 EPROCESS 的定义,总结出在不同系统上,ActiveProcessLinks 在 EPROCESS 结构中的偏移:
编码实现
枚举进程:
// 遍历进程
BOOLEAN EnumProcess()
{
PEPROCESS pFirstEProcess = NULL, pEProcess = NULL;
ULONG ulOffset = 0;
HANDLE hProcessId = NULL;
PUCHAR pszProcessName = NULL;
// 根据不同系统, 获取相应偏移大小
ulOffset = GetActiveProcessLinksOffset();
if (0 == ulOffset)
{
DbgPrint("GetActiveProcessLinksOffset Error!\n");
return FALSE;
}
// 获取当前进程结构对象
pFirstEProcess = PsGetCurrentProcess();
pEProcess = pFirstEProcess;
// 开始遍历枚举进程
do
{
// 从 EPROCESS 获取进程 PID
hProcessId = PsGetProcessId(pEProcess);
// 从 EPROCESS 获取进程名称
pszProcessName = PsGetProcessImageFileName(pEProcess);
// 显示
DbgPrint("[%d]%s\n", hProcessId, pszProcessName);
// 根据偏移计算下一个进程的 EPROCESS
pEProcess = (PEPROCESS)((PUCHAR)(((PLIST_ENTRY)((PUCHAR)pEProcess + ulOffset))->Flink) - ulOffset);
} while (pFirstEProcess != pEProcess);
return TRUE;
}
隐藏指定进程:
// 隐藏指定进程
BOOLEAN HideProcess(PUCHAR pszHideProcessName)
{
PEPROCESS pFirstEProcess = NULL, pEProcess = NULL;
ULONG ulOffset = 0;
HANDLE hProcessId = NULL;
PUCHAR pszProcessName = NULL;
// 根据不同系统, 获取相应偏移大小
ulOffset = GetActiveProcessLinksOffset();
if (0 == ulOffset)
{
DbgPrint("GetActiveProcessLinksOffset Error!\n");
return FALSE;
}
// 获取当前进程结构对象
pFirstEProcess = PsGetCurrentProcess();
pEProcess = pFirstEProcess;
// 开始遍历枚举进程
do
{
// 从 EPROCESS 获取进程 PID
hProcessId = PsGetProcessId(pEProcess);
// 从 EPROCESS 获取进程名称
pszProcessName = PsGetProcessImageFileName(pEProcess);
// 隐藏指定进程
if (0 == _stricmp(pszProcessName, pszHideProcessName))
{
// 摘链
RemoveEntryList((PLIST_ENTRY)((PUCHAR)pEProcess + ulOffset));
// 显示
DbgPrint("[Hide Process][%d][%s]\n", hProcessId, pszProcessName);
break;
}
// 根据偏移计算下一个进程的 EPROCESS
pEProcess = (PEPROCESS)((PUCHAR)(((PLIST_ENTRY)((PUCHAR)pEProcess + ulOffset))->Flink) - ulOffset);
} while (pFirstEProcess != pEProcess);
return TRUE;
}
根据系统版本获取偏移大小:
// 根据不同系统, 获取相应偏移大小
ULONG GetActiveProcessLinksOffset()
{
ULONG ulOffset = 0;
RTL_OSVERSIONINFOW osInfo = {0};
NTSTATUS status = STATUS_SUCCESS;
// 获取系统版本信息
status = RtlGetVersion(&osInfo);
if (!NT_SUCCESS(status))
{
DbgPrint("RtlGetVersion Error[0x%X]\n", status);
return ulOffset;
}
// 判断系统版本
switch (osInfo.dwMajorVersion)
{
case 6:
{
switch (osInfo.dwMinorVersion)
{
case 1:
{
// Win7
#ifdef _WIN64
// 64 Bits
ulOffset = 0x188;
#else
// 32 Bits
ulOffset = 0x0B8;
#endif
break;
}
case 2:
{
// Win8
#ifdef _WIN64
// 64 Bits
#else
// 32 Bits
#endif
break;
}
case 3:
{
// Win8.1
#ifdef _WIN64
// 64 Bits
ulOffset = 0x2E8;
#else
// 32 Bits
ulOffset = 0x0B8;
#endif
break;
}
default:
break;
}
break;
}
case 10:
{
// Win10
#ifdef _WIN64
// 64 Bits
ulOffset = 0x2F0;
#else
// 32 Bits
ulOffset = 0x0B8;
#endif
break;
}
default:
break;
}
return ulOffset;
}