CnPack Forum » 技术板块灌水区 » [原创]Api Hook 细析(一)


2008-5-14 22:20 Yock.W
[原创]Api Hook 细析(一)

//Author:Alex(Yock.W)
//转载请署名出处
前言 基础知识
本系列文章会对常用的几种API HOOK方法进行全面的分析。
Hook是什么?
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
Hook原理
每个Hook都有一个关联的链表,由系统维护,链表指针指向被Hook子程调用的回调函数:
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]LRESULT WINAPI HookCallBack(
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][/color][color=#0000ff]int[/color][color=#000000] nCode,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]WPARAM wParam,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]LPARAM lParam);[/color][/size]
nCode 事件代码
wParam 消息的类型
lParam 包含的消息
当被Hook的类型关联的消息发生时,系统会把这个消息传递到链表指针指向的Hook子程,由子程内部对消息进行处理,。
钩子安装与卸载
使用Windows提供的API SetWindowsHookEx来将Hook子程指针安装到Hook链表,安装的Hook子程始终在Hook链表头部。
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]HHOOK SetWindowsHookEx(
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][/color][color=#0000ff]int[/color][color=#000000] idHook,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]HOOKPROC lpfn,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]HINSTANCE hMod,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]DWORD dwThreadId);[/color][/size]
idHook 安装的钩子类型
lpfn 子程函数指针
hMod 应用程序实例的句柄
dwThreadId Hook关联的线程标识符
当被Hook的消息类型发生时,系统会调用与这个Hook关联的链表头的Hook子程。由子程序决定是否要把这个事件传递给下个子程。Hook子程传递事件给下一个Hook子程需要调用使用Windows提供的API CallNextHookEx。
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]LRESULT CallNextHookEx(
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]HHOOK hhk,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][/color][color=#0000ff]int[/color][color=#000000] nCode,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]WPARAM wParam,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]LPARAM lParam);[/color][/size]
hhk 当前钩子的句柄,由SetWindowsHookEx返回
nCode 事件代码
wParam 消息的类型
lParam 包含的消息
钩子在使用完,需调用UnHookWindowsHookEx来卸载。
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]UnHookWindowsHookEx(
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]HHOOK hhk);[/color][/size]
Api Hook 的几种方法
一.修改函数过程映射的地址空间
Api Hook:
我们都知道,在调用函数的时候,会先Call 0xFFFFFF,来到函数映射在内存的地址空间里,这时候,传入的参数已入栈,那么,我们只需要修改函数头部,跳转到我们自定义的过程,这样,我们就达到了对API函数监视的目的。那么,我们需要多大的空间来写入代码让其跳转到我们自定义的过程里呢?
聪明的你肯定已经想到了。没错,只需5字节即可。Why? 看一下代码:
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]jmp [/color][color=#000000]0xFFFFFFFF[/color][/size]
jmp 指令占1字节,地址是4字节,所以,只需5字节即可。
有的看官可能会问了,为什么不用Call呢?
因为Call指令在实现跳转以后会通过ret返回到Call下面的指令继续执行,而Jmp指令实现跳转以后则全有子程来控制了。
我们知道了通过以上方法来修改函数头部数据来达到我们的目的,那么,我们先要得到指向函数头部的地址,通过LoadLibrary和GetProcAddress即可获取到我们要监视的函数在内存中映射的地址指针。我们以MessageBoxA为例:
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]typedef [/color][color=#0000ff]int[/color][color=#000000] (__stdcall [/color][color=#000000]*[/color][color=#000000]TMessageBoxA)(HWND hWnd,LPSTR lpText,LPSTR lpCaption,[/color][color=#0000ff]int[/color][color=#000000] uType);
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][img]http://www.cppblog.com/Images/dot.gif[/img][img]http://www.cppblog.com/Images/dot.gif[/img]
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][/color][color=#0000ff]extern[/color][color=#000000] [/color][color=#000000]"[/color][color=#000000]C[/color][color=#000000]"[/color][color=#000000] TMessageBoxA __fastcall GetPFuncAddr(LPSTR DllName,[/color][color=#0000ff]const[/color][color=#000000] [/color][color=#0000ff]char[/color][color=#000000]*[/color][color=#000000] FullName);
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][img]http://www.cppblog.com/Images/dot.gif[/img][img]http://www.cppblog.com/Images/dot.gif[/img]
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]TMessageBoxA __fastcall GetPFuncAddr(LPSTR DllName,[/color][color=#0000ff]const[/color][color=#000000] [/color][color=#0000ff]char[/color][color=#000000]*[/color][color=#000000] FullName)
[img]http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img][img]http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif[/img][/color][img]http://www.cppblog.com/Images/dot.gif[/img][color=#000000]{
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#0000ff]void[/color][color=#000000] [/color][color=#000000]*[/color][color=#000000]DllModule;
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]DllModule [/color][color=#000000]=[/color][color=#000000] LoadLibrary(DllName);
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]TMessageBoxA PAddr [/color][color=#000000]=[/color][color=#000000] (TMessageBoxA)GetProcAddress(DllModule,FullName);
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#0000ff]return[/color][color=#000000] PAddr;
[img]http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]}[/color][/size]
在得到了指向内存映射里函数头部的指针以后,我们下一步,来读出函数头部的5字节。为什么要读出函数头部的5字节呢?呵呵,当然是留作恢复时写会数据了。ReadProcessMemory:
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]BOOL ReadProcessMemory(
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]HANDLE hProcess,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]LPCVOID lpBaseAddress,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]LPVOID lpBuffer,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]DWORD nSize,
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]LPDWORD lpNumberOfBytesRead);[/color][/size]
hProcess 进程句柄
lpBaseAddress 读内存开始地址
lpBuffer 保存数据的指针
nSize 数据大小
lpNumberOfBytesRead //address of number of bytes read
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]BYTE [/color][color=#000000]*[/color][color=#000000]pFuncData [/color][color=#000000]=[/color][color=#000000] [/color][color=#0000ff]new[/color][color=#000000] BYTE[[/color][color=#000000]4[/color][color=#000000]];
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]LPDWORD iRead;
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]ReadProcessMemory(([/color][color=#0000ff]void[/color][color=#000000] [/color][color=#000000]*[/color][color=#000000])GetCurrentProcess(),PMessageBoxA,pFuncData,[/color][color=#000000]5[/color][color=#000000],iRead);
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][/color][/size]
保存原函数入口点的数据以后,嘿嘿,当然是写入指向我们自己的函数地址了。
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]BYTE AsmCode[[/color][color=#000000]4[/color][color=#000000]];
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]AsmCode[[/color][color=#000000]0[/color][color=#000000]] [/color][color=#000000]=[/color][color=#000000] [/color][color=#000000]0xE9[/color][color=#000000];
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]__asm
[img]http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img][img]http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif[/img][/color][img]http://www.cppblog.com/Images/dot.gif[/img][color=#000000]{
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]lea eax, MyMessageBoxA
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]mov ebx,PMessageBoxA
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]sub eax,ebx
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]sub eax,[/color][color=#000000]5[/color][color=#000000]
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]mov dword ptr[AsmCode[/color][color=#000000]+[/color][color=#000000]1[/color][color=#000000]],eax
[img]http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]}[/color][color=#000000]
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img]WriteProcessMemory(([/color][color=#0000ff]void[/color][color=#000000] [/color][color=#000000]*[/color][color=#000000])GetCurrentProcess(),PMessageBoxA,AsmCode,[/color][color=#000000]5[/color][color=#000000],iWrite);
[img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][/color][/size]
MyMessageBoxA是我们自己构造的函数:
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#0000ff]int[/color][color=#000000] WINAPI MyMessageBoxA(HWND hWnd,LPSTR lpText,LPSTR lpCaption,[/color][color=#0000ff]int[/color][color=#000000] uType)
[img]http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img][img]http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif[/img][/color][img]http://www.cppblog.com/Images/dot.gif[/img][color=#000000]{
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#008000]//[/color][color=#008000]我们来弹出一个消息框,改变一下传入的标题参数值
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#008000]//[/color][color=#008000]那么首要做的,就是先恢复MessageBoxA[/color][color=#008000]
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#000000]WriteProcessMemory(([/color][color=#0000ff]void[/color][color=#000000] [/color][color=#000000]*[/color][color=#000000])GetCurrentProcess(),PMessageBoxA,pFuncData,[/color][color=#000000]5[/color][color=#000000],iWrite);
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#0000ff]int[/color][color=#000000] Result [/color][color=#000000]=[/color][color=#000000] MessageBoxA(hWnd,lpText,[/color][color=#000000]"[/color][color=#000000]ApiHook Test[/color][color=#000000]"[/color][color=#000000],uType);
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img]WriteProcessMemory(([/color][color=#0000ff]void[/color][color=#000000] [/color][color=#000000]*[/color][color=#000000])GetCurrentProcess(),PMessageBoxA,AsmCode,[/color][color=#000000]5[/color][color=#000000],iWrite);
[img]http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif[/img][/color][color=#0000ff]return[/color][color=#000000] Result;
[img]http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]}[/color][/size]
恢复API的原入口处数据:
[size=13px][img]http://www.cppblog.com/Images/OutliningIndicators/None.gif[/img][color=#000000]WriteProcessMemory(([/color][color=#0000ff]void[/color][color=#000000] [/color][color=#000000]*[/color][color=#000000])GetCurrentProcess(),PMessageBoxA,pFuncData,[/color][color=#000000]5[/color][color=#000000],iWrite);[/color][/size]
防止修改函数入口数据来进行API HOOK的方法:
在程序启动的时候保存关键API入口的5字节,定期检查一次,发现被修改了,改回即可。
----------------------------------------------------------------
代码回头附上,码字太累了,前言浪费了点时间,#- - 感觉和扫盲差不多……
下一篇中,会针对SEH技术实现API HOOK和如何防止SEH APIHOOK进行详解。
扫盲到此结束。

[[i] 本帖最后由 Yock.W 于 2008-5-14 22:34 编辑 [/i]]

2008-5-14 22:49 Passion
写的好。

另外说一句,Windows提供的HOOK钩子技术,与自行覆盖地址进行的API hook,两者不一样吧。前者不能说是API hook技术。

2008-5-15 05:41 Yock.W
前言讲的是基础知识,是为认识HOOK做一下铺垫。呵呵,已经标明了是前言

2008-5-16 12:28 Passion
此文章转贴至CnPack文档中心了,欢迎再接再励地写。:handshake

2008-5-18 12:13 bahamut8348
我就要转,转了就不保留作者信息:lol:

页: [1]


Powered by Discuz! Archiver 5.0.0  © 2001-2006 Comsenz Inc.