一、概述
Hooking技术是一系列技术的通称,通过拦截在软件构件之间传递的函数调用(functioncalls)/信息(messages)/事件(events)来改变OS, 应用(application)或者其他软件构件的行为。譬如改变准备调用的函数指针使之指向需要的hook函数,在执行了hook函数之后再返回原来的函数指针。当我们试图用一段代码来分析程序中某段逻辑路径被执行的频率,或者想要在其中插入更多功能时就会用到钩子。通过Hook,我们可以暂停系统调用,或者通过改变系统调用的参数来改变正常的输出结果,甚至可以中止一个当前运行中的进程并且将控制权转移到自己手上。这种用来实现hooking技术的代码叫做hook(钩子)。恶意软件也经常使用hook。例如从正在运行的进程列表中隐藏或拦截按键事件、窃取密码,信用卡数据等敏感输入。
Hook实现的方法分为一下两种:
1.在应用运行之前更改源:通过逆向工程修改可执行文件或者库,通常用来拦截函数调用,从而监控或者完全替换掉调用。
通过反汇编器,可以找到在一个模块里面的函数入口点。更改入口点从而动态地加载我们需要的库;
修改可执行文件的入口表,入口表修改之后可以加载任何附加的的库模块,同时在应用程序调用函数时修改外部代码的调用;
通过包装库(wrapper library)拦截函数调用。包装库:满足所有原库可调用的函数,并附加了自己的实现。
2.运行时(runtime) 修改
操作系统和会提供途径让我们在运行时很容易地插入hook(需要插入hook的进程有足够的权限)当操作系统没有提供这一功能时。可以使用修改内存中的中断向量表,入口描述符表的方法(对于有共享库的操作系统)。(实质上这种办法也是修改代码,只是在运行时更改进程内存中的指令和结构)。Unix系统用LD_PRELOAD环境变量轻松地实现了函数重载,当你在创建了一个已经存在的共享函数库中的函数时,你会在DYLD_INSERT_LIBRARIES注册你自己的函数,这样就会调用自己的函数了。
API Hooking可分为以下几种类型:
1.本地挂钩:这些仅影响特定的应用程序。
2.全局挂钩:这些会影响所有系统进程。
在本文中,我们将介绍Windows的挂钩技术,该技术属于通过使用C/C++和本机API进行运行时修改来完成的本地类型。
二、Hooking 原理
2.1注入
使用运行时修改方法实现的本地Hook必须运行在目标程序的地址空间内。操纵目标过程并使之hook的程序称为注入器。
Hook分为一下几个步骤:
1.获取目标过程句柄。
2.在目标进程中分配内存,然后将外部DLL路径写入其中(此处是指编写包含该钩子的动态库路径)。
3.在目标进程内创建一个线程,该线程将加载库并设置钩子。
Windows API提供了一些适合于实现注射器的系统调用。假设目标进程尚未运行,我们想在目标程序启动后立即插入钩子。为此,注入器应首先调用CreateProcess API来运行目标进程。
为了在目标进程启动后立即设置钩子,注入器必须通过传递一个CREATE_SUSPENDED标志(dwCreationFlags)来挂起目标,然后在注入钩子之后,通过调用ResumeThread API函数来恢复目标进程。在挂起状态下启动进程的方法如下:
使用系统调用VirtualAllocEx和WriteProcessMemory的组合来执行目标进程中的内存分配和写入。注入器应首先使用VirtualAllocEx分配内存。
然后使用WriteProcessMemory向其写入数据。
下面是分配内存并将其写入目标进程:
下一步是在目标进程内创建一个线程,该线程用钩子加载库。利用CreateRemoteThread API实现:
因此,CreateRemoteThread在hProcess句柄指定的目标远程进程中创建一个状态参数为dwCreationFlags的新线程。新创建的线程将执行lpStartAddress指向的函数,并将lpParameter作为第一个参数传递给它。然后我们使用此API函数来启动线程并使其加载DLL,实现方式如下:
( 1 ) 作为参数lpStartAddress将指针传递给LoadLibrary()。
( 2 ) 将一个指向DLL钩子(使用VirtualAllocEx和WriteProcessMemory初始化的钩子)的指针作为参数lpParameter。
我们使用GetProcAddress在目标进程中找到指向LoadLibrary函数的指针地址。
其中hModule是指对导出LoadLibrary的DLL。HMODULE可以通过GetModuleHandle获得。
将带有钩子的DLL加载到目标进程中的代码如下:
这些都是注入的所有必要步骤。如果注入顺利,则将钩子库加载到目标进程中,然后再DllMain中执行该函数,这样我们可以设置任何所需的钩子。
2.2hook引擎
要实现挂钩本身,我们建议使用许多现有的方法。它们有很多作为开源、免费或部分免费的方式提供。例如功能强大的挂钩引擎Microsoft Detour在免费版本中支持x86架构(它需要付费才能hook x64)。另一个用得比较多的是NtHookEngine,它支持x86和x64并具有设计良好且非常简单的API。实际上,此引擎仅导出三个易于使用的功能:
HookFunction——设置一个hook。此函数可用于挂钩当前进程的虚拟地址空间中存在的任何功能。
UnhookFunction——删除特定的挂钩。
GetOriginalFunction——返回指向原始函数的指针。当需要在挂钩函数中调用原始函数时,这非常有用。
三、实例
3.1Hook Library
我们挂钩了GetAdaptersInfo方法,并伪造了网络适配器名称及其MAC地址值。
然后我们hook GetAdaptersInfo()与FakeGetAdaptersInfo()。在FakeGetAdaptersInfo()函数内,我们使用GetOriginalFunction()获取实际的适配器信息。接下来,我们用假值替换第一个适配器信息。
3.2注入器
注入器可以为x86或x64体系结构构建。但是,为x86构建的注射器不会在x64环境中运行。我们的注入器独自运行目标应用程序代码如下:
BOOL WINAPI InjectDll(__in LPCWSTR lpcwszDll, __in LPCWSTR targetPath)
{
SIZE_T nLength;
LPVOID lpLoadLibraryW = NULL;
LPVOID lpRemoteString;
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInformation;
memset(&startupInfo, 0, sizeof(startupInfo));
startupInfo.cb = sizeof(STARTUPINFO);
if (!CreateProcess(targetPath, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInformation))
{
PrintError(TEXT("Target process is failed to start"));
return FALSE;
}
lpLoadLibraryW = GetProcAddress(GetModuleHandle(L"KERNEL32.DLL"), "LoadLibraryW");
if (!lpLoadLibraryW)
{
PrintError(TEXT("GetProcAddress failed"));
// close process handle
CloseHandle( processInformation.hProcess);
return FALSE;
}
nLength = wcslen(lpcwszDll) * sizeof(WCHAR);
// allocate mem for dll name
lpRemoteString = VirtualAllocEx(processInformation.hProcess, NULL, nLength + 1, MEM_COMMIT, PAGE_READWRITE);
if (!lpRemoteString)
{
PrintError(TEXT("VirtualAllocEx failed"));
// close process handle
CloseHandle(processInformation.hProcess);
return FALSE;
}
// write dll name
if (!WriteProcessMemory(processInformation.hProcess, lpRemoteString, lpcwszDll, nLength, NULL)) {
PrintError(TEXT("WriteProcessMemory failed"));
// free allocated memory
VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);
// close process handle
CloseHandle(processInformation.hProcess);
return FALSE;
}
// call loadlibraryw
HANDLE hThread = CreateRemoteThread(processInformation.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)lpLoadLibraryW, lpRemoteString, NULL, NULL);
if (!hThread) {
PrintError(TEXT("CreateRemoteThread failed"));
}
else {
WaitForSingleObject(hThread, 4000);
//resume suspended process
ResumeThread(processInformation.hThread);
}
// free allocated memory
VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);
// close process handle
CloseHandle(processInformation.hProcess);
return TRUE;
}
3.3运行结果
为了检是否hook到目标函数,钩子是否正常工作,我们先直接运行程序,然后在运行钩子,比较两次运行的不通。
首先,我们在没有应用钩子的情况下运行目标应用程序:
然后我们检查加载了钩子的输出。请注意,列表中第一个适配器的“适配器名称”和“适配器地址”字段已更改。
原文来源:关键基础设施安全应急响应中心
本文始发于微信公众号(网络安全应急技术国家工程实验室):Windows API Hooking和DLL注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论