冒险解谜游戏中文网 ChinaAVG
标题:
API Hook的几种实现
[打印本页]
作者:
shane007
时间:
2009-11-24 17:51
标题:
API Hook的几种实现
原文
4 w* j' ?" [9 Q7 X2 S, J" h0 l
http://www.cnblogs.com/rogeryu/archive/2009/06/04/1496538.html
* l4 ?4 o; T# |) S8 B* _
/ R) J$ L- j4 \* ]; z, C- o6 C
所谓的API Hook,就是利用某种技术将API的调用转为我们自己定义的函数的调用。这种技术在实际项目里面的应用也是很广泛的。最近,我在做关于我们项目的自动化测试的时候,就遇到了这种情况。在写测试代码之前,我们对测试代码有一些要求。1. 不能因为测试代码而修改原代码。2. 原有的模块是以dll格式输出的,在做测试的时候,要测的类和函数也只能使用dll的导出类或者函数,而不能将源文件重新编译。由于这些限制,导致测试用例往往不能在普通的机器上运行。比如这样一个函数:
: L, R" ] {; m' b3 Z0 U6 y( R
5 @3 P' ]( s8 `: J- `5 P' ?
int func()
' M3 c" b! t% w0 F2 K* {8 x
{
# R6 T. J! j- B) K
//Some initializing codes
5 D1 W) ^# R6 f+ a
int hardware_code = get_hardware_code();
2 v3 y; D& _+ C, P, y" K8 J# g6 R, u
if (is_valid_code(hardware_code))
4 C" c# Z. U- k/ J
{
" M% E6 Y" } d5 x& D
//
7 D$ |) m; u+ X' T
}
. c6 C6 o X2 N2 i: K9 ~4 U# Y/ ^2 H
//
' u* u5 F$ Y8 z% V1 d. S. t
return ret;
+ W4 T3 Z/ }9 b4 W3 m% a" u
}
+ N! E% |. |; @( J6 i+ i
此处,函数get_hardware_code()是与特定平台相关的,在普通PC上运行肯定无法获得正确的结果。如果拿不到正确的结果,也就不能对函数func()进行测试了。于是,我们就可以利用API Hook技术,在测试代码里面,把所有对get_hardware_code()的调用换成我们自定义的函数mock_get_hardware_code()的调用,这样,在我们自己定义的函数里面,可以返回一个有效的代码以保证原代码能够正确的往下执行。
" u. |- L9 W1 [. L
9 G- `5 q* v7 r2 z& C
经过研究,API Hook有这么几种方法。
( X& U( ?/ [1 m2 K5 `) l' o' B
4 I" V8 j- i3 A- x I0 x& t7 c. H
1. 改写函数的首地址。
. O* C. H* o& ?2 S# _
( P5 n- F/ P+ l, N
这个是在《Windows核心编程》里面大师提到的API Hook的方法之一。原理就是,首先获得要被Hook的函数的地址(比如get_hardware_code()),然后将其首地址之后的若干字节(通常是5个字节)改成一条jmp指令,而jmp的目标地址就是自定义函数的地址(此处为mock_get_hardware_code())。这样,当函数每次执行目标函数的时候,就会跳转到我们自定义的函数里面去。这种方法很简洁,据说在Win16的年代经常被使用。但是大师并不推荐,好像是因为这种方法在多线程的环境下会有什么问题(具体的我忘记了,大家可以翻书看看)。
. D# X) \- y- a2 l
! U0 \: b8 y8 s8 U# p1 _7 w
这样的话,只要我们能得到被调函数的地址,我们就可以随心所欲的修改。当然,由于大师的不推荐,我这种方法只是实现了一下,并未真正应用。
4 j: A8 k1 `6 n) B7 B! Q
, s$ X' X: G6 a& o! A4 T$ ?
2. 改写导入表
* C5 D. {9 n" W& {) n( b& Y
+ r, g8 \2 e, K, y0 \, M
这个也是《Windows核心编程》里面提到的,也是大师所推荐的。具体来说,就是遍历当前进程里面的所有模块,对其中每一个模块查找它的导入表。如果找到被测函数所在的dll,并且发现这个函数,那么就把这个地址修改成自定义函数的地址。关于如何从导入表中发现被测函数,我也总结了两种方式。1)对于一般的C导出函数,可以直接通过比较地址的方式去找,这个也在核心编程上面有一个小例子。2)对于C++中的导出的类成员函数而言,由于C++的指向成员函数的指针和普通的指针有所差别(我没仔细研究过,从网上查的),在将一个成员函数指针转化成普通的函数指针的时候编译通不过,因此我采取了第二种查找方式,也就是查找函数名。这还有一个问题,由于C++的导出成员函数名都进行了修饰,类似于?MethodName@ClassName@...@Z这种怪异的名字,不过,只要知道类名和方法名,然后查找MethodName@ClassName字符串就行。如果找到了这样的函数,然后在修改它的地址就行了。
% a% A3 Z: {- b0 W. i1 l! s
# q& r9 A' ?7 A5 E4 q1 b9 l
另,关于导入表,大家可以去看雪论坛上有关Windows PE文件格式的介绍。此处就不多说了。
, n; F* L6 K0 D- w4 w: d0 }
% W: t. J1 a. V1 O; W
3. 改写虚函数表。
% O4 R7 [; Y, g1 X
+ P* {( u5 U5 O+ L
本来以为通过方法2就能hook住所有的导出类成员函数和普通函数,但还是出现了一个问题,因为我在尝试hook一个成员函数的时候,发现这个函数根本没有在导入表里面。后通过反汇编发现,由于那个导出类成员函数是一个虚函数,因为在通过指针调用的时候,它实际上是从虚函数表里面获得的函数地址进行调用的。因此对于hook这类函数,就需要改写它的虚函数表了。关于这个需要对C++的内存布局有所了解。我在这里就说一种比较简单的方式吧。
" o' m* J+ d; m+ M2 f: G
% d( _7 _4 P; r7 l2 n
一般来说,对于某个含有虚函数表的C++类,this指针指向的地址,取值就是虚函数表指针。虚函数表指针指向了虚函数表,里面的每一个元素都指向了实际要调用的函数的地址。因此,可以按照这样的方式访问虚函数表指针:
" h% y- Y! u$ F
, a/ D# ] B. A- V5 A2 O) `
int** pVTable = (int**)this;
" p; K: G2 }( {6 M" H
1 D; t! U1 L& r7 o! h1 ]( j
也就是将指向对象的指针强制转化成指针的指针,这样就可以通过取值就可以访问虚函数表:
& ~4 ^) s1 k$ O. ?
7 u, ?7 A9 n6 p9 H( P6 _- B; ?
(*pVTable)[0] = address of virtual function 1;
# t: {! y8 y# B3 t8 e2 O6 e8 \) X
|2 I& I0 ~1 M4 F! n4 _& t, x
(*pVTable)[1] = address of virtual function 2;
% c' O, O+ O. ]6 o C, z
% Z) d9 J$ J: y9 r$ \9 }6 q7 n
...
6 F- O8 s, k, @6 r
$ S- ~3 A8 c/ i. X( U( X3 {* [4 q
因此,我们就可以改写虚函数的地址了,从而达到hook的目的。这种技术来源于网上。当然,我对C++的内存布局也不是十分的清楚,如果一个类进行了多重继承,它的虚函数表是什么样子我也不太明白,这里只是说明了这样一种技术。
# R, p5 A0 m4 M. K$ }; n4 m7 t
i/ o$ l" o* W% y! t: b
以上就是目前为止我应用到的三种API Hook的技术,其实实际应用到的也就是后两种,这两种技术能够满足我目前项目的需要了。如果还有其他关于API Hook技术的话大家也可以交流。
# @3 h& V+ T, D' l: V+ k8 l
1 j! y* W) l- o4 U
另外需要说明的一点是,上述三种方法中不管哪一种在改写地址的时候,由于Windows一般将那个地址所在的页面设置了保护属性,因为你需要用VirtualProtect函数将页面改为可读可写的属性才能改写,否则会有异常的。
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2