Windows
0
[高级技巧]Windows下Hook API 劫持函数
金坷居士 2016-5-17 23:10:07
Hook API这个方法,可以在自己的程序中,改变甚至重写Windows的API,或者在其他DLL中的函数。
实际并没有改写,只是修改调用为自己的函数而已。
这个技术用处多多,比如写游戏作弊器和间谍程序,检测程序对API的调用参数等等。
参考了这两个例子,第一个包含演示程序:
http://blog.csdn.net/friendan/article/details/12222651
http://snowdream2013.blog.163.com/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位,自动切换
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);
}



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

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);

APIHook_TurnOffHook关闭Hook
APIHook_TurnOnHook开启Hook
APIHook_DestoryHook关闭并释放Hook所占的资源
这个例子中的代码只能修改自己程序也就是当前进程中的API调用,跨进程则无效,如果需要修改其他程序的API调用,需要先进行线程注入,之后我会贴代码
2016-5-17 23:15:46
1楼
0
支持,学习了。。。→_→
2楼
0
其实R3下API Hook有很多种实现方法,inline hook只是一种,比较方便粗暴,但是需要进行代码注入不是特别安全(如果hook的时候某个线程正好执行到这里就可能发生无法意料的情况),而写入jmp指令是无法实现原子操作的。
iat hook麻烦一点(需要懂PE机构,并且搜索到iat的位置),但是安全很多(本质上是修改指针)。没试过64为下还能不能iat hook,不过似乎PE+机构中iat(导入表)依然存在,理论上可行
3楼
0
我记得x86(32位)下大多数WindowsAPI开头都是相当于空指令的mov edi,edi,因而如果只改写这两个字节,自己调用时就不必恢复,只需将函数指针增加两个字节调用。
金坷居士(作者)
4楼
0
引用 phpskycn:
其实R3下API Hook有很多种实现方法,inline hook只是一种,比较方便粗暴,但是需要进行代码注入不是特别安全(如果hook的时候某个线程正好执行到这里就可能发生无法意料的情况),而写入jmp指令是无法实现原子操作的。
iat ...
IAT hook比较难哎 似乎还对汇编要求高 咱x86汇编基本就是狒狒
金坷居士(作者)
5楼
0
引用 金星凌日:
我记得x86(32位)下大多数WindowsAPI开头都是相当于空指令的mov edi,edi,因而如果只改写这两个字节,自己调用时就不必恢复,只需将函数指针增加两个字节调用。
发现一个奇怪情况 大部分函数比如MessageBoxW GetMenuItemInfo等能被正确Hook但是CreateWindowExW这玩意就不能正确hook 在自己的CreateWindowExW中调用原来的函数始终得不到正确的结果 GetLastError返回0x57 似乎是参数不正确的意思orz
6楼
0
引用 金坷居士:
IAT hook比较难哎 似乎还对汇编要求高 咱x86汇编基本就是狒狒
IAT对汇编的要求比inline hook低,主要得了解PE+结构。
Hook失败的时候需要检查堆栈,看看参数之类的是否正确,调试器里面单步跟一下,并对比正常情况
7楼
0
引用 金坷居士:
发现一个奇怪情况 大部分函数比如MessageBoxW GetMenuItemInfo等能被正确Hook但是CreateWindowExW这玩意就不能正确hook 在自己的CreateWindowExW中调用原来的函数始终得不到正确的结果 ...
在什么版本的系统下出现了这样的问题?我在版本较低的32位Windows下测试,可以Hook。
金坷居士(作者)
8楼
0
引用 phpskycn:
IAT对汇编的要求比inline hook低,主要得了解PE+结构。
Hook失败的时候需要检查堆栈,看看参数之类的是否正确,调试器里面单步跟一下,并对比正常情况
是参数填错了orz 小错误
金坷居士(作者)
9楼
0
引用 金星凌日:
在什么版本的系统下出现了这样的问题?我在版本较低的32位Windows下测试,可以Hook。
搞定了2333
2016-6-17 16:52:57
10楼
0

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

两个字节怎么hook。。。

[修改于 3 年前 - 2016-06-17 16:54:27]

11楼
0

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

jmp short

2018-7-11 13:47:04
金坷居士(作者)
12楼
0

其实可以不用造轮子了

https://github.com/TsudaKageyu/minhook

这玩意在windows下用着挺好的

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

万流景仰
专栏收藏夹发私信
学术分 11科创币 16.43总主题 171 帖总回复 1711 楼拥有证书:会员 学者 机友 笔友
注册于 2011-09-23 14:21最后登录 2019-04-25 01:17

个人简介

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

Github  https://github.com/kccd/nkc.git

科创研究院 (c)2001-2019

蜀ICP备11004945号-2 川公网安备51010802000058号