一、Inline HOOK介绍
1、内联钩子简介
Inline hook(内联钩子)是一种在程序运行时修改函数执行流程的技术。它通过修改函数的原始代码,将目标函数的执行路径重定向到自定义的代码段,从而实现对目标函数的拦截和修改。
内联钩子通常用于函数的动态修改和函数的跟踪,常见的应用场景包括代码注入、API挂钩、调试、性能分析等。它可以用于实现函数的监控、跟踪参数、修改返回值、修改参数等操作。
内联钩子的实现方式通常有以下几个步骤:
- 定位目标函数的地址:通过函数名或者导入表等方式找到目标函数在内存中的地址。
- 修改目标函数的内存权限:将目标函数的内存权限修改为可写可执行,以便后续修改函数的指令。
- 备份目标函数的原始指令:将目标函数的原始指令备份到自定义的缓冲区中。
- 修改目标函数的指令:将目标函数的指令修改为跳转到自定义代码的指令,以实现拦截和修改。
- 编写自定义代码:编写自定义的代码,实现对目标函数的拦截和修改逻辑。
- 执行自定义代码:将自定义的代码插入到目标函数的执行流程中,使其被调用时执行自定义逻辑。
- 恢复目标函数的原始指令:在自定义代码执行完毕后,恢复目标函数的原始指令,以确保目标函数的正常执行。
2、为什么要引入inline hook?
我们上一节介绍的IAT HOOK当中有一个很大的问题就是我们只能hook掉存在于IAT表当中的函数,如果我们想hook的函数不在IAT表当中IAT hook就无能为力了,为了弥补这一缺陷,我们引出了inline hook。
在这里我们浅谈一下hook的本质,就是在执行函数A之前,让他去执行我们的代码B,然后再回过来去执行A,达到暗度陈仓的目的。或者进一步,就是把call dword ptr ds:[addr]里的addr替换为我们自定义函数的地址。
!!Attention!!:
这里还有个小bug就是这里的call的地址,并不一定就是真正的函数的地址,比如在debug版的程序当中你call的函数地址他不一定就是真正的函数入口点,而是call到一个jmp指令的位置,然后通过jmp指令跳转到真正的函数入口点的位置。为了防止函数地址发生变更,我们使用GetProcAddress函数来获取MessageBoxA开始的准确地址。
至于这样做有什么好处?
》主要是便于编译器去调试,有时候重新编译的时候,函数地址可能会发生变动,就需要修改每一个函数的地址,比较麻烦,为了省事,就统一使用jmp跳转,这样函数地址变动的时候,只需要修改jmp后面的地址就可以了!
3、Inline hook的主要用途
Inline hook(内联钩子)在软件开发和系统调试中有广泛的用途。以下是一些常见的用途:
-
动态函数拦截与修改:通过内联钩子可以拦截目标函数的调用,修改函数的输入参数或返回值,实现对函数行为的定制化控制。这可以用于实现调试器、破解软件保护机制、API挂钩等功能。
-
API监控和追踪:通过内联钩子可以监控和记录系统中特定API函数的调用信息,用于分析程序行为、检测恶意行为、收集性能统计数据等。这对于系统调试、性能分析和安全监控非常有用。
-
代码注入和修改:内联钩子可以用于将自定义的代码注入到目标进程的执行流程中,实现对程序的动态修改和扩展。这在软件插件、热修复、代码破解等方面有应用。
-
反调试和反破解:内联钩子可以用于对抗调试器和破解工具,通过拦截和修改关键函数的调用,使得调试器无法正确运行或破解工具无法识别关键逻辑。这用于软件保护和反破解技术。
-
动态代码分析和跟踪:通过内联钩子可以在程序执行过程中动态跟踪代码执行路径、记录函数调用顺序、分析运行时行为等。这对于调试、优化和逆向工程非常有帮助。
二、编程前准备工作
1、如何在vs调试dll文件
打断点之后,会自动启动目标进程,然后等待dll被加载进来
将dll文件注入到目标进程
然后发现dll已经进入断点了
2、我们直接在MessageBoxA的函数体里面填写JMP不会破坏源代码导致出错吗?
》先给答案:不会。分析如下:
call MessageBoxA会把下一条指令mov eax,dword ptr [y]所在的地址压栈,然后不管是执行我们的自定义函数的ret指令,还是原来的MessageBoxA最后的ret指令,都会返回到指令mov eax,dword ptr [y]去执行,从而达到了顺序执行,不破坏原exe的目的。
三、思路分析
1、首先初始化钩子:获取要hook的函数的地址空间的起始位置,在函数空间的这块内存里面存放的就是指令的硬编码,我们需要把原指令存放在oldCode[5]里面,然后把新指令(也就是跳转到哪个地址去执行)放到newCode[5]里面即可。
2、安装钩子:把函数地址原来的指令修改为新指令,具体来说就是E9到我们自定义的函数地址去执行。
3、进程执行结束之后卸载钩子,也就是把hook的函数地址的指令给他还原回去。
四、改写上节案例
我们书接上回,坤坤在进行篮球表演🏀,台下的ikuns尖叫声太大,影响了居民休息,我们采取把ikuns替换成小黑子的方式来起到消音效果,但是其实不妨换一个思路,我们直接把坤坤大变活人,替换成小鬼哥哥,下面的观众可能会考虑到远古毒兽泰裤剌的致命毒性而纷纷有序离场,从而也可以达到控制噪声的效果!
五、完整代码:
以下是dll文件的完整内容:
#include "pch.h"
//我们这次hook的是MessageBoxA
DWORD g_oldFuncAddr = 0;
BYTE g_oldCode[5] = { 0 };
BYTE g_newCode[5] = { 0xE9 };
BOOL initHook();
bool installHook();
bool unistallHook();
int WINAPI MyMessageBoxA(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
)
{
unistallHook();
int result = MessageBoxA(hWnd, "本活动由小鬼哥哥赞助", "太裤剌!", MB_OK);
installHook();
return result;
}
BOOL initHook() {
//获取需要hook的函数MessageBoxA的地址
HMODULE hDll = LoadLibraryA("user32.dll");
g_oldFuncAddr =(DWORD)GetProcAddress(hDll, "MessageBoxA");
if (g_oldFuncAddr > 0) {
MessageBoxA(0, "找到了MessageBoxA函数", "标题", MB_OK);
}
//保留原汇编代码
memcpy(g_oldCode, (char*)g_oldFuncAddr, 5);
//保存新汇编代码
DWORD offset = (DWORD)MyMessageBoxA - g_oldFuncAddr - 5;
memcpy(&g_newCode[1], &offset, 4);
return true;
}
bool installHook() {
DWORD oldProtect = 0;
VirtualProtect((char*)g_oldFuncAddr, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((char*)g_oldFuncAddr, g_newCode, 5);
VirtualProtect((char*)g_oldFuncAddr, 5, oldProtect, &oldProtect);
return true;
}
bool unistallHook() {
DWORD oldProtect = 0;
VirtualProtect((char*)g_oldFuncAddr, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((char*)g_oldFuncAddr, g_oldCode, 5);
VirtualProtect((char*)g_oldFuncAddr, 5, oldProtect, &oldProtect);
return true;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: {
initHook();
bool bret=installHook();
if (bret) {
MessageBoxW(0, L"钩子安装成功!", L"标题", MB_OK);
}
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH: {
bool bret=unistallHook();
if (bret) {
MessageBoxW(0, L"钩子卸载成功", L"标题", MB_OK);
}
break;
}
}
return TRUE;
}
六、运行结果
hook之前:
hook之后:
今天的学习就到此结束啦!同时Windows API的学习也要告一段落了,后面我们会进入shellcode以及汇编、反汇编的学习,喜欢的话多多支持一下吧!🧡🧡🧡