HOOK技术的一些简单总结

很久没写博客了, 一个月一篇仍是要尽可能保证,今天谈下Hook技术。

在Window平台上开发任何稍微底层一点的东西,基本上都是Hook满天飞, 普通应用程序如此,安全软件更是如此, 这里简单记录一些经常使用的Hook技术。

基本上作Windows开发都知道这个API, 它给咱们提供了一个拦截系统事件和消息的机会, 而且它能够将咱们的DLL注入到其余进程。
可是随着64位时代的到来和Vista以后的UAC机制开启,这个API不少时候不能正常工做了:

首先,32位DLL无法直接注入到64位的应用程序里面, 由于他们的地址空间彻底不同的。固然尽管无法直接注入,可是在权限范围内,系统会尽可能以消息的方式让你能收到64位程序的消息事件。

其次,UAC打开的状况下低权限程序无法Hook高权限程序, 实际上低权限程序以高权限程序窗口为Owner建立窗口也会失败, 低权限程序在高权限程序窗口上模拟鼠标键盘也会失败。

有人说咱们能够关闭UAC, Win7下你确实能够,可是Win8下微软已经不支持真正关闭UAC, 从这里咱们也能够看到微软技术过渡的方式, 中间会提供一个选项来让你慢慢适应,最后再把这个选项关掉, UAC和Aero模式都是如此。

那么咱们如何解决这些问题?

对于64位问题 , 解决方法是提供2个DLL,分别能够Hook32和64位程序。

对于权限问题, 解决方法是提高权限, 经过注册系统服务, 由服务程序建立咱们的工做进程。这里为何要建立一个其余进程而不直接在服务进程里干活? 由于Vista后咱们有了Session隔离机制,服务程序运行在Session 0,咱们的其余程序运行在Session 1, Session 2等, 若是咱们直接在服务程序里干活,咱们就只能在Session 0里工做。经过建立进程,咱们能够在 DuplicateTokenEx后将Token的SessionID设置成目标Session,而且在 CreateProcessAsUser时指定目标WinStation和Desktop, 这样咱们就既得到了System权限,而且也能够和当前桌面进程交互了。

不少人可能都不知道这个API, 可是这个API其实挺重要的, 看名字就知道它是Hook事件(Event)的, 具体哪些事件能够看 这里.

为何说这个API重要, 由于这个API大部分时候没有SetWindowsHookEx的权限问题, 也就是说这个API可让你Hook到高权限程序的事件, 它同时支持进程内( WINEVENT_INCONTEXT)和进程外( WINEVENT_OUTOFCONTEXT)2种Hook方式, 你能够以进程外的方式Hook到64位程序的事件。

为何这个API没有权限问题, 由于它是给Accessibility用的, 也就是它是给自动测试和残障工具用的, 因此它要保证有效。

我曾经看到这样一个程序,当任何程序(不管权限高低)有窗口拖动(拖标题栏改变位置或是拖边框改变大小), 程序都能捕获到, 当时很好奇它是怎么作到的?
Spy了下窗口消息, 知道有这样2个消息: WM_ENTERSIZEMOVE和 WM_EXITSIZEMOVE表示进入和退出这个事件, 可是那也只能得到本身的消息,其余程序的消息它是如何捕获到的?当时怀疑用的是Hook, 却发现没有DLL注入。查遍了Windows API 也没有发现有API能够查询一个窗口是否在这个拖动状态。最后发现用的是SetWinEventHook EVENT_SYSTEM_MOVESIZESTART和EVENT_SYSTEM_MOVESIZEEND。

API Hook
常见的API Hook包括2种, 一种是基于PE文件的导入表(IAT), 还有 一种是修改前5个字节直接JMP的inline Hook.

对于基于IAT的方式, 原理是PE文件里有个导入表, 表明该模块调用了哪些外部API,模块被加载到内存后, PE加载器会修改该表,地址改为外部API重定位后的真实地址, 咱们只要直接把里面的地址改为咱们新函数的地址, 就能够完成对相应API的Hook。《Windows核心编程》里第22章 有个封装挺好的CAPIHook类,咱们能够直接拿来用。
我曾经用API Hook来实现自动测试,见这里 API Hook在TA中的应用

对于基于Jmp方式的inline hook, 原理是修改目标函数的前5个字节, 直接Jmp到咱们的新函数。虽然原理挺简单, 可是由于用到了平台相关的汇编代码, 通常人很难写稳定。真正在项目中用仍是要求稳定, 因此咱们通常用微软封装好的Detours, 对于Detours的原理,这里有篇不错的文章  微软研究院Detour开发包之API拦截技术

比较一下2种方式: 
IAT的方式比较安全简单, 可是只适用于Hook导入函数方式的API。
Inline Hook相对来讲复杂点, 可是它能Hook到任何函数(API和内部函数),可是它要求目标函数大于5字节, 同时把握好修改时机或是Freeze其余线程, 由于多线程中改写可能会引发冲突。

COM Hook
Window上由于有不少开发包是以COM方式提供的(好比DirectX), 因此咱们就有了拦截COM调用的COM Hook。
由于COM里面很关键的是它的接口是C++里虚表的形式提供的, 因此COM的Hook不少是时候其实就是虚表 (vtable)的Hook。
关于C++ 对象模型和虚表能够看我这篇  探索C++对象模型

对于COMHook,考虑下面2种case:

一种是咱们Hook程序先运行,而后启动某个游戏程序(DirectX 9), 咱们想Hook游戏的绘画内容。

这种方式下, 咱们能够先Hook API  Direct3DCreate9, 而后咱们继承于IDirect3D9, 本身实现一个COM对象返回回去, 这样咱们就能够拦截到全部对该对象的操做, 随心所欲了, 固然咱们本身现实的COM对象内部会调用真正的 Direct3DCreate9,封装真正的 IDirect3D9。

固然有时咱们可能不用替代整个COM组件,咱们只须要修改其中一个或几个COM函数, 这种状况下咱们能够建立真正的 IDirect3D9对象后直接修改它的虚表, 把其中某些函数改为咱们本身的函数地址就能够了。

其实ATL就是用接口替代的方式来调试和记录COM接口引用计数的次数, 具体能够看我这篇  理解ATL中的一些汇编代码

还有一种case是游戏程序已经在运行了, 而后才启动咱们的Hook进程, 咱们怎么样才能Hook到里面的内容?

这种状况下咱们首先要对程序内存有比较详细的认识, 才能思考建立出来的D3D对象的虚表位置, 从而进行Hook, 关于程序内存布局,可见我这篇  理解程序内存

理论上说COM对象若是是以C++接口的方式实现, 虚表会位于PE文件的只读数据节(.rdata), 而且全部该类型的对象都共享该虚表, 因此咱们只要建立一个该类型对象,咱们就能够得到其余人建立的该类型对象的虚表位置,咱们就能够改写该虚表实现Hook(实际操做时须要经过VirtualProtect修改页面的只读属性才能写入)。

可是实际上COM的虚表只是一块内存, 它并不必定是以C++实现, 因此它能够存在于任何内存的任何地方。另外对象的虚表也不必定是全部同类型的对象共享同一虚表, 咱们彻底能够每一个对象都有本身的一份虚表。好比我发现 IDirect3D9是你们共享同一虚表的(存在D3D9.dll的), 可是IDirect3DDevice9就是每一个对象都有本身的虚表了(存在于堆heap)。因此若是你要Hook  IDirect3DDevice9接口,经过修改虚表实际上无法实现。

可是尽管有时每一个对象的虚表不同,同类型对象虚表里的函数地址却都是同样的, 因此这种状况下咱们能够经过inline Hook直接修改函数代码。固然有些状况下若是是静态连接库,即便函数代码也是每一个模块都有本身的一份, 这种状况下就只能反汇编获取虚表和函数的地址了。

最后,总结一下, 上面主要探讨了Windows上的各类Hook技术,经过将这些Hook技术组起来, 能够实现不少意想不到的功能, 好比咱们彻底能够经过Hook D3D实现Win7任务栏那种Thumbnail预览的效果(固然该效果能够直接由DWM API实现, 可是若是咱们能够经过HOOK已动画的方式实现是否是更有趣 )。
相关文章
相关标签/搜索