[高级技巧]Windows下Hook API 劫持函数
金坷居士2016/05/17软件综合 IP:澳大利亚
Hook API这个方法,可以在自己的程序中,改变甚至重写Windows的API,或者在其他DLL中的函数。
实际并没有改写,只是修改调用为自己的函数而已。
这个技术用处多多,比如写游戏作弊器和间谍程序,检测程序对API的调用参数等等。
参考了这两个例子,第一个包含演示程序:
XXXXXXXXXXXXXXXXXXXt/friendan/article/details/12222651
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/blog/static/181485234201241310454415/
实际上就是修改了函数入口的汇编代码,跳转到自己的函数里,自己的函数可以做些坏事然后再跳到原来的函数里去23333
首先找到要修改的函数的入口地址,然后保存下入口处的二进制代码(长度根据系统架构和程序而定)
开hook的时候需要修改函数的入口内容,32位和64位的方法不太一样:
32位用的是jmp指令,以当前地址为参考,跳转到指定的地址,就是自己函数的地址-原来函数的地址-5(5是指令长度,0xe9是jmp,后面4字节是相对地址)
不过在64位下,这个就不适用了,jmp能跳转的地址范围有限,在64位情况下很可能跳不到,于是就需要改成这样
mov rax,XX XX XX XX XX XX XX XX
PUSH RAX
RET
那堆xx就是64位的函数地址,这里写自己函数的地址。对应的十六进制码为48 B8 XX XX XX XX XX XX XX XX 50 C3,一共12字节
不知道为啥,网上涉及Hook API的程序都喜欢内嵌汇编,但是我觉得这样很困惑,降低可读性,于是我用memset等代替了那些汇编
还有64位环境下C内嵌汇编不方便,需要asm文件
关闭hook的时候把保存的原来函数的入口处的代码写回去就好了
有一点需要注意,在自己函数中需要调用原来的被修改的函数时,需要先关闭hook,不然就变成递归了,死循环,程序就炸了
下面是完整代码,支持32位和64位,自动切换
<code class="lang-cpp">typedef struct APIHOOK *LPAPIHOOK;
LPAPIHOOK APIHook_CreateHook(HANDLE hProc, FARPROC oldFunc, void* newFunc);
void APIHook_DestoryHook(LPAPIHOOK hook);
void APIHook_TurnOnHook(LPAPIHOOK hook);
void APIHook_TurnOffHook(LPAPIHOOK hook);
 
typedef struct APIHOOK {
#ifdef _AMD64_
    BYTE OldCode[12];
    BYTE NewCode[12];
#else
    BYTE OldCode[5];
    BYTE NewCode[5];
#endif  
    FARPROC oldProc;
    HANDLE hP;
    bool status;
}APIHOOK;
 
void APIHook_TurnOnHook(LPAPIHOOK hook) {
    if (hook == NULL)
        return;
 
    DWORD dwTemp = 0;
    DWORD dwOldProtect;
 
#ifdef _AMD64_
    VirtualProtectEx(hook->hP, hook->oldProc, 12, PAGE_READWRITE, &dwOldProtect);
    WriteProcessMemory(hook->hP, hook->oldProc, hook->NewCode, 12, 0);
    VirtualProtectEx(hook->hP, hook->oldProc, 12, dwOldProtect, &dwTemp);
#else
    VirtualProtectEx(hook->hP, hook->oldProc, 5, PAGE_READWRITE, &dwOldProtect);
    WriteProcessMemory(hook->hP, hook->oldProc, hook->NewCode, 5, 0);
    VirtualProtectEx(hook->hP, hook->oldProc, 5, dwOldProtect, &dwTemp);
#endif
 
    hook->status = true;
}
 
void APIHook_TurnOffHook(LPAPIHOOK hook) {
    if (hook == NULL)
        return;
 
    DWORD dwTemp = 0;
    DWORD dwOldProtect;
 
 
#ifdef _AMD64_
    VirtualProtectEx(hook->hP, hook->oldProc, 12, PAGE_READWRITE, &dwOldProtect);
    WriteProcessMemory(hook->hP, hook->oldProc, hook->OldCode, 12, 0);
    VirtualProtectEx(hook->hP, hook->oldProc, 12, dwOldProtect, &dwTemp);
#else
    VirtualProtectEx(hook->hP, hook->oldProc, 5, PAGE_READWRITE, &dwOldProtect);
    WriteProcessMemory(hook->hP, hook->oldProc, hook->OldCode, 5, 0);
    VirtualProtectEx(hook->hP, hook->oldProc, 5, dwOldProtect, &dwTemp);
#endif
 
    hook->status = false;
}
 
LPAPIHOOK APIHook_CreateHook(HANDLE hProc, FARPROC oldFunc, void* newFunc) {
    LPAPIHOOK ret = (LPAPIHOOK)malloc(sizeof(APIHOOK));
    ret->hP = hProc;
    ret->oldProc = oldFunc;
 
    if (ret->oldProc == NULL)
    {
        return NULL;
    }
 
 
#ifdef _AMD64_
    memcpy_s(ret->OldCode, 12, (BYTE*)(ret->oldProc), 12);
 
    ret->NewCode[0] = 0x48;
    ret->NewCode[1] = 0xB8;
    ULONG64 addr = (ULONG64)newFunc;
    memcpy_s(ret->NewCode + 2, 8, &addr, 8);
    ret->NewCode[10] = 0x50;
    ret->NewCode[11] = 0xC3;
#else
    memcpy_s(ret->OldCode, 5, (BYTE*)(ret->oldProc), 5);
 
    ret->NewCode[0] = 0xe9; //jmp
    ULONG32 aNew = (ULONG32)newFunc - (ULONG32)(void*)(ret->oldProc) - 5;
    memcpy_s(ret->NewCode + 1, 4, &aNew, 4);
#endif
 
    ret->status = false;
 
    return ret;
}
 
void APIHook_DestoryHook(LPAPIHOOK hook) {
    if (hook == NULL)
        return;
    if (hook->status)
        APIHook_TurnOffHook(hook);
    free(hook);
}</code>



APIHook_CreateHook用来创造一个Hook, hProc是要被Hook的进程的句柄,这里是当前进程的句柄, oldFunc是要被修改的函数的地址,newFunc是新函数的指针
newFunc的声明要和原来函数一样,这点需要特别注意,
比如下面替换TrackPopupMenuEx的例子:

<code class="lang-cpp">BOOL WINAPI MyTrackPopupMenuEx(HMENU hMenu, UINT uFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm)
{
    APIHook_TurnOffHook(apiHook_TPM);
 
    //Do something here
 
    BOOL ret = TrackPopupMenuEx(hMenu, uFlags, x, y, hwnd, lptpm);
     
    //Do something here
 
    APIHook_TurnOnHook(apiHook_TPM);
    return ret;
}
 
 
    LPAPIHOOK apiHook_TPM; 
    //Create a api hook
    HMODULE hmod = LoadLibrary(TEXT("User32.dll"));
 
 
    apiHook_TPM = APIHook_CreateHook(OpenProcess(PROCESS_ALL_ACCESS, 0, GetCurrentProcessId()),
        GetProcAddress(hmod, "TrackPopupMenuEx"),
        MyTrackPopupMenuEx);
 
    APIHook_TurnOnHook(apiHook_TPM);
 
 
 
    //Dont forget APIHook_DestoryHook(apiHook_TPM);</code>

APIHook_TurnOffHook关闭Hook
APIHook_TurnOnHook开启Hook
APIHook_DestoryHook关闭并释放Hook所占的资源
这个例子中的代码只能修改自己程序也就是当前进程中的API调用,跨进程则无效,如果需要修改其他程序的API调用,需要先进行线程注入,之后我会贴代码
来自:计算机科学 / 软件综合
12
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
acmilan
7年11个月前 IP:四川
819529
支持,学习了。。。→_→
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
phpskycn
7年11个月前 IP:浙江
819530
其实R3下API Hook有很多种实现方法,inline hook只是一种,比较方便粗暴,但是需要进行代码注入不是特别安全(如果hook的时候某个线程正好执行到这里就可能发生无法意料的情况),而写入jmp指令是无法实现原子操作的。
iat hook麻烦一点(需要懂PE机构,并且搜索到iat的位置),但是安全很多(本质上是修改指针)。没试过64为下还能不能iat hook,不过似乎PE+机构中iat(导入表)依然存在,理论上可行
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
7年11个月前 IP:陕西
819540
我记得x86(32位)下大多数WindowsAPI开头都是相当于空指令的mov edi,edi,因而如果只改写这两个字节,自己调用时就不必恢复,只需将函数指针增加两个字节调用。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金坷居士作者
7年11个月前 IP:澳大利亚
819559
引用 phpskycn:
其实R3下API Hook有很多种实现方法,inline hook只是一种,比较方便粗暴,但是需要进行代码注入不是特别安全(如果hook的时候某个线程正好执行到这里就可能发生无法意料的情况),而写入jmp指令是无法实现原子操作的。
iat ...
IAT hook比较难哎 似乎还对汇编要求高 咱x86汇编基本就是狒狒
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金坷居士作者
7年11个月前 IP:澳大利亚
819560
引用 金星凌日:
我记得x86(32位)下大多数WindowsAPI开头都是相当于空指令的mov edi,edi,因而如果只改写这两个字节,自己调用时就不必恢复,只需将函数指针增加两个字节调用。
发现一个奇怪情况 大部分函数比如MessageBoxW GetMenuItemInfo等能被正确Hook但是CreateWindowExW这玩意就不能正确hook 在自己的CreateWindowExW中调用原来的函数始终得不到正确的结果 GetLastError返回0x57 似乎是参数不正确的意思orz
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
phpskycn
7年11个月前 IP:浙江
819583
引用 金坷居士:
IAT hook比较难哎 似乎还对汇编要求高 咱x86汇编基本就是狒狒
IAT对汇编的要求比inline hook低,主要得了解PE+结构。
Hook失败的时候需要检查堆栈,看看参数之类的是否正确,调试器里面单步跟一下,并对比正常情况
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
7年11个月前 IP:陕西
819592
引用 金坷居士:
发现一个奇怪情况 大部分函数比如MessageBoxW GetMenuItemInfo等能被正确Hook但是CreateWindowExW这玩意就不能正确hook 在自己的CreateWindowExW中调用原来的函数始终得不到正确的结果 ...
在什么版本的系统下出现了这样的问题?我在版本较低的32位Windows下测试,可以Hook。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金坷居士作者
7年11个月前 IP:澳大利亚
819603
引用 phpskycn:
IAT对汇编的要求比inline hook低,主要得了解PE+结构。
Hook失败的时候需要检查堆栈,看看参数之类的是否正确,调试器里面单步跟一下,并对比正常情况
是参数填错了orz 小错误
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金坷居士作者
7年11个月前 IP:澳大利亚
819604
引用 金星凌日:
在什么版本的系统下出现了这样的问题?我在版本较低的32位Windows下测试,可以Hook。
搞定了2333
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan
7年10个月前 修改于 7年10个月前 IP:四川
821610

引用 金星凌日 : 我记得x86(32位)下大多数WindowsAPI开头都是相当于空指令的mov edi,edi,因而如果只改写这两个字节,自己调用时就不必恢复,只需将函数指针增加两个字节调用。

两个字节怎么hook。。。

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
phpskycn
7年10个月前 IP:浙江
821624

引用 acmilan : > 引用 金星凌日 : 我记得x86(32位)下大多数WindowsAPI开头都是相当于空指令的mov edi,edi,因而如果只改写这两个字节,自己调用时就不必恢复,只需将函数指针增加两个字节调用。……

jmp short

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金坷居士作者
5年9个月前 IP:澳大利亚
847963

其实可以不用造轮子了

XXXXXXXXXXXXXXXXXX/TsudaKageyu/minhook

这玩意在windows下用着挺好的

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
金坷居士
学者 机友 笔友
文章
170
回复
1711
学术分
11
2011/09/23注册,1个月18天前活动

怪哉!灵异的三极管电流流向! 这素一个在仿真的RCC电路,示波器上绿色的是集电极电流红色的是发射极电流。窝萌都知道发射姬电流素集电极电流和基极电流之和,所以讲道理发射极电流一定比集电极略大。可仿真结果刷了三观,Q1集电极电流一部分流经基极,然后流经Q2的C->E。

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}