看流星社区

 找回密码
 注册账号
查看: 1991|回复: 0

函数hook注意事项

[复制链接]

该用户从未签到

发表于 2015-4-8 12:30:46 | 显示全部楼层 |阅读模式
我们在深入测试一些没有代码的软件时,往往需要进行函数hook,简单地说就是把目标函数替换我们的函数,用比较技术的语言描述就是:

(1) Target函数:要拦截的函数,通常为Windows的API。
(2) Trampoline函数:Target函数的复制品。因为Detours将会改写Target函数,所以先把Target函数复制保存好,一方面仍然保存Target函数的过程调用语义,另一方面便于以后的恢复。
(3) Detour函数:用来替代Target函数的函数(往往是我们自己写的函数)。
一般进行函数hook的原理是在Target函数的开头加入JMP Address_of_ Detour_Function指令(共5个字节)把对Target函数的调用引导到自己的Detour函数, 把Target函数的开头的5个字节加上JMP Address_of_Target _ Function + 5作为Trampoline函数地址。

  其实关于函数hook方法的资料已经汗牛充栋,而且微软也早就推出了官方的库来辅助这一过程,无需我再赘述,我在这里只想谈下面一个话题:如何写好Detour函数,这是我们总结了前人和自身实践经验得出的。
  进行过函数hook实践的同学是否有过这种经历,注入我们的代码或dll,对目标函数进行hook后,执行到我们的函数或者执行过我们的函数后,程序会出错或崩溃(甚至蓝屏)。这是正常的,编写detour函数和普通的编程很不一样,它就像深入敌后的间谍,要考虑很多细节,稍不注意会破坏Context,影响目标进程的正常运行。那么要注意什么呢?
1.  自己定义的detour函数和目标target函数的参数很返回值完全一致
因为没有代码,所以对于目标函数的参数和返回值的确定是需要逆向分析的,在有pdb的情况下很嗨,直接IDA等工具能帮你准确确定。没有pdb的情况下需要通过参数所占内存大小、特征甚至动态调试去确定,这里想强调一点是指针类型、数值类型、和引用类型的区分很关键且容易出错。另外有一种可怕的经验是返回值是一个结构,没经验无从讲起,但愿不要遇到这种情况。
2.  函数调用方式的确定与选择
我假设大家都知道了常见的cdecl和stdcall还有fastcall三种调用方式的异同,通常情况下碰到的函数调用都是cdecl和stdcall方式。他们最大的区别是前者是函数调用者负责栈平衡,后者是函数内负责栈平衡。我们编写detour函数时最好明确声明函数调用方式,原则是和target函数声明方式保持一致。Tips:就hook这种特殊情形,一般用stdcall更方便,因为我们的控制范围是函数内部,要向控制函数调用上下文要绕写弯路,而stdcall是在函数内部控制栈平衡的,so,under control!
3.  现场保护
在编写detour函数时要时刻有一个观念就是这段代码的正确运行是以不破坏当前进程正常运行为前提的,但一个问题是我们往往不知道进程需要用到哪些现场,所以就是通过pushad-popad对寄存器进行完整保护。但应该在什么情况下使用pushad和popad呢?这个需要视具体情况而定,但有一个原则是:在开始执行自己的指令前pushad,在执行原生代码前popad,这个原则不是很准确,只是想表示:不要让自己的指令执行破坏原来的context。
4.  This:“勿忘我!”
对于hook一般的系统函数,在detour函数中可以直接调用Trampoline函数,但在hook一些类成员函数后,不能直接调用Trampoline函数,因为进程确定对象成员函数需要以该对象this指针为入口进行索骥。所以hook了类成员函数,又要在detour函数中调用Trampoline函数,需要在调用前将对象的地址(this指针的内容)存入ecx中。
  就我目前的经验和水平只能总结这些,其实上述的123点背后都有一个共同的背后灵就是“栈平衡”,函数调用的最基本规则就是要在函数调用前和函数调用结束后保证sp指在同一个地方,所以你如果碰到hook崩溃的情况,第一步想到的调试方法就是对比查看函数执行前后的栈的情况然后就是比较寄存器的异同。

补充:来自taker的观点
“很少用Detours,具体细节不太了解。不过在HOOK的时候,如果是自己写代码而没有用Detours的话,记得带上反汇编引擎去算被HOOK命令的长度,否则会出现同样的HOOK函数 有时候崩有时候不崩的情况(之前针对该问题测试过DeTours,发现DeTours里有反汇编引擎处理了指令长度)
还有就是注意寄存器。由于对DeTours不了解,不知道在HOOK后它有没有其他的包装,如果有的话 那么就有可能破坏寄存器(VS2010起大部分函数调用都会更多的使用寄存器而非纯堆栈。OBJECTIV C写的代码也是 大部分使用REG,反正这个是由编译器决定的)。最好是使用nake函数。避免普通函数构造EBP/ESP框架破坏堆栈,并自己的函数头上PUSHAD PUSHFD,尾上POPFD POPAD。在代码中堆栈操作部分加上0x28就可以了。但这样就不能构造函数内局部变量。除非你自己手工维护堆栈。”
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-4-20 06:17

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表