本文将介绍一种简便的方法来设置系统范围内的全局API钩子。将使用AppInit_DLLs注册表键进行DLL注入,并使用Mhook库进行API钩子设置。为了说明这种技术,将展示如何轻松地从运行中的进程列表中隐藏calc.exe。
API钩子意味着拦截某些API函数调用。通过它,可以改变任何软件的行为。钩子被广泛用于防病毒软件、安全应用程序、系统实用程序、编程工具等。
有两种类型的钩子:本地和全局。本地钩子仅应用于特定应用程序。全局钩子应用于系统中的所有进程。本文展示的钩子技术是全局的,影响所有进程的所有会话(与特定桌面绑定的SetWindowsHooks方式不同)。
AppInit_DLLs基础设施是一种机制,用于在所有用户模式进程中加载任意列表的DLL,这些进程与User32.dll相关联(实际上,很少有可执行文件不与它相关联)。这些DLL由User32.dll在其初始化时加载。
AppInit_DLLs基础设施的行为由存储在注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows键下的一组值配置。这些注册表值如表1所示:
值 | 描述 | 示例值 |
---|---|---|
LoadAppInit_DLLs | 全局启用或禁用AppInit_DLLs的值。 | 0x0 – AppInit_DLLs禁用。0x1 – AppInit_DLLs启用。 |
AppInit_DLLs | 空格或逗号分隔的要加载的DLL列表。应使用短文件名指定DLL的完整路径。 | C:\PROGRA~1\Test\Test.dll |
RequireSignedAppInit_DLLs | 要求代码签名的DLL。 | 0x0 – 加载任何DLL。0x1 – 仅加载代码签名的DLL。 |
有几种库用于API钩子。它们通常做的事情包括:
Mhook是一个免费的开源库,用于API钩子。它支持x86和x64平台,使用起来非常简单。Mhook接口简单且相当自描述:
BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction);
BOOL Mhook_Unhook(PVOID *ppHookedFunction);
有关库使用的更多信息,请参见下一段中显示的代码示例或访问Mhook主页。
将编写一个用户模式DLL。首先,应该下载最新的Mhook源代码并将其添加到项目中。如果使用预编译头文件,请为Mhook文件关闭它。
如上所述,示例将隐藏calc.exe从运行中的进程列表中。
通过调用NTAPI函数NtQuerySystemInformation查询运行中的进程列表。因此,需要向项目中添加一些NTAPI内容。不幸的是,winternl.h头文件不包含完整的信息,不得不自己定义所需的数据类型:
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef struct _MY_SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER Reserved[3];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
ULONG BasePriority;
HANDLE ProcessId;
HANDLE InheritedFromProcessId;
} MY_SYSTEM_PROCESS_INFORMATION, *PMY_SYSTEM_PROCESS_INFORMATION;
typedef NTSTATUS (WINAPI *PNT_QUERY_SYSTEM_INFORMATION)(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
创建一个全局变量并初始化它以存储原始函数地址:
PNT_QUERY_SYSTEM_INFORMATION OriginalNtQuerySystemInformation =
(PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress(::GetModuleHandle(L"ntdll"), "NtQuerySystemInformation");
在钩子函数中,首先调用原始函数。然后检查SystemInformationClass。如果是SystemProcessInformation,遍历运行中的进程列表,找到所有calc.exe的条目,将它们从列表中删除。就是这样!
注意:此函数必须具有与原始函数相同的签名。
NTSTATUS WINAPI HookedNtQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
)
{
NTSTATUS status = OriginalNtQuerySystemInformation(SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength);
if (SystemProcessInformation == SystemInformationClass && STATUS_SUCCESS == status)
{
PMY_SYSTEM_PROCESS_INFORMATION pCurrent = NULL;
PMY_SYSTEM_PROCESS_INFORMATION pNext = (PMY_SYSTEM_PROCESS_INFORMATION)SystemInformation;
do {
pCurrent = pNext;
pNext = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrent + pCurrent->NextEntryOffset);
if (!wcsncmp(pNext->ImageName.Buffer, L"calc.exe", pNext->ImageName.Length))
{
if (0 == pNext->NextEntryOffset)
{
pCurrent->NextEntryOffset = 0;
}
else
{
pCurrent->NextEntryOffset += pNext->NextEntryOffset;
}
pNext = pCurrent;
}
}
while (pCurrent->NextEntryOffset != 0);
}
return status;
}
设置钩子非常简单:在DLL加载到新进程时,从DllMain调用Mhook_SetHook:
BOOL WINAPI DllMain(
__in HINSTANCE hInstance,
__in DWORD Reason,
__in LPVOID Reserved
)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH:
Mhook_SetHook((PVOID*)&OriginalNtQuerySystemInformation, HookedNtQuerySystemInformation);
break;
}
}
取消钩子是通过在DLL从进程卸载时,从DllMain调用Mhook_Unhook来执行的:
BOOL WINAPI DllMain(
__in HINSTANCE hInstance,
__in DWORD Reason,
__in LPVOID Reserved
)
{
switch (Reason)
{
case DLL_PROCESS_DETACH:
Mhook_Unhook((PVOID*)&OriginalNtQuerySystemInformation);
break;
}
}
现在是时候展示所描述的钩子在行动中了。构建项目并将生成的AppInitHook.dll放到C盘的根目录。
打开注册表编辑器并定位AppInit_DLLs注册表键(键是HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows)。然后指定钩子DLL的路径(在例子中是C:\AppInitHook.dll)。
修改注册表后,钩子开始工作。让运行几个calc.exe实例。然后打开Windows任务管理器并查看进程选项卡。根本没有calc.exe!
让看看由Mark Russinovich编写的另一个流行工具 - Process Explorer。