在看雪找来的文章,和代理DLL的原理基本是一样的。
: ?0 l* e+ s' r8 H; l' W$ i- f, R) g; K- C
原文6 Q% v" k" u5 i) b/ @; Z
http://bbs.pediy.com/showthread.php?t=85368
0 S7 Q& |! K$ Q3 ]2 r% \9 y4 v" Z+ v, C" i0 k7 U0 w% X
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.1 H- t# V, S% M$ v4 v: N. f3 O8 B# z h
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
7 G9 e5 M4 [0 n0 a还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.5 u6 f0 e9 b4 G7 t+ |' f0 Z
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.# G/ O+ S; u9 @- D1 D
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
/ C4 A$ c+ E8 l ? DWORD oldpro=0;
0 c: d% j0 v |, I/ x, Z memcpy(d3dcen5bytes,pC,5);4 i$ K6 t7 y- r5 Q2 a$ M: h
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);+ A+ H9 g ]2 }" M
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码: V: u" ^; C: Z/ G/ W$ ~8 ^
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5. M4 a! q$ y L. M# m2 f% p: l
) m: d6 @/ L" W- K3 i& y2 _5 |- B+ U% y4 ^
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
, Y- }/ Z c- f" x7 a) qHRESULT CreateDevice(
5 t$ }2 x! N* w UINT Adapter,
6 i# o. G% g' q4 D O D3DDEVTYPE DeviceType,
/ V# q7 R3 Z2 n5 q- x! s% { HWND hFocusWindow,- K( h. N* t/ r. ~3 ?
DWORD BehaviorFlags,
6 d( f" ^8 w4 D7 _, P* s7 W D3DPRESENT_PARAMETERS * pPresentationParameters,
$ P) n* y( T6 ^- b IDirect3DDevice9 ** ppReturnedDeviceInterface/ O. P4 j/ {# V! j) v
);
9 v" L5 v# I! I8 o8 y. L- d @: {" ]( G3 _
8 m) e1 E o' q3 s
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
% y. G9 c6 J3 R1 i1 WHRESULT _stdcall hookedCreateDevice(- |2 d! @4 [9 V$ E+ }7 U
LPDIRECT3D9 pDx9,
7 o: C4 B6 v2 B* A( S2 y+ |8 t/ `' ` UINT Adapter,5 L; O, v, V9 M; `& q0 M# W; Z
D3DDEVTYPE DeviceType,# d* c- w" a9 s7 B
HWND hFocusWindow,
' G6 F2 m: M3 K9 \! u) w DWORD BehaviorFlags,
. |" K3 R. K* s3 q x8 G. G D3DPRESENT_PARAMETERS * pPresentationParameters,
/ S5 o5 M% C F: T- J IDirect3DDevice9 ** ppReturnedDeviceInterface
' N D- T3 S9 u' e, B+ u9 {8 M3 Z, w
);7 M2 ~/ C$ k( D4 j7 b6 S
* T8 W9 E1 e4 s其中的LPDIRECT3D9 pDx9就是this指针.% P$ y+ n" i5 w1 }4 ^
- j( _2 ^ }" d E* | H
hookedDirect3DCreate9的关键代码如下:
: j' G8 R1 `, a* R3 c, TpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针3 k' M+ D- _* l; V$ R
1 R( Y& z4 a) |3 k: x DWORD oldpro=0;* ]5 b7 L0 l8 z2 S1 f
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
$ L0 W5 c4 c* ~( u: \1 k VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
! {* p& F7 h2 Y' f! y ` *(BYTE*)pCdev=0xe9;
* W1 ~6 d. f# l. `% g *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;+ t; F2 R2 e: p4 p/ c1 S, U
# T" b. _2 v: \8 U1 E
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
. F! d3 j" {6 M3 O! T& d FpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
* \: W3 z/ ~7 J) S- X/ L3 a1 ~ memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
" T0 x( z1 m. U( e! v DWORD oldpro=0;0 e$ @( k( T2 Q" I1 i( @
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
p" }9 m! u6 A$ v& N2 d2 E *(BYTE*)pPre=0xe9;
6 K7 L. @% |# ]6 ]3 o( v% C7 N *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;& X; F! k5 y; M7 E1 }. G
* f. F2 {- z9 i' h @. ?: T
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:3 Y9 S: h- U- P9 e
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";! x' Q- ~# Y* f0 W. K+ A+ a
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本9 U2 S$ w: Z; N3 g- s# s) s1 t; ?
//在这里写入您的其它绘图代码
: e0 s1 s2 U) F9 k5 Y) `& @* ?- {7 l/ I# m2 k8 h
9 Z' n% Y: a* S3 S5 ~! O+ k1 m+ J! [
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:4 N# |) e7 M. Q# O
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)7 l8 l5 T% f, q! E
{) p' ^- J5 ~% m" `% l5 `
! L$ ]- V& J/ q: M
if(m_pD3D && pDxdevice){& P) l8 Q# \. U5 A+ h- e' U X
0 N0 \" m9 N7 @! b3 F2 H6 K+ \ RECT myrect;
J* E5 [3 [( o" `( Y myrect.top=150; //文本块的y坐标1 m( G# b& o$ y+ |7 S% Q
myrect.left=0; //文本块的左坐标
$ o. ^9 K& H8 ? myrect.right=500+myrect.left;
' N* n2 e# R; M myrect.bottom=100+myrect.top;
' P# P. T7 v& R& H pDxdevice->BeginScene();//开始绘制/ g* G! k. @& Z; j! W* ?, I, a
1 I% N _! M8 @5 V% ~( `! @ D3DXFONT_DESCA lf;
- f4 v2 r6 J3 F, A! F ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
& M6 O7 O( a" [ lf.Height = 24; //字体高度
+ `% k9 [5 v) S7 X' S lf.Width = 12; // 字体宽度
( b, r8 c9 z2 y. L* o" ?0 \, s lf.Weight = 100; 1 D! E# k# b" p `
lf.Italic = false;" u0 b1 W; t' G4 c, d0 i
lf.CharSet = DEFAULT_CHARSET;
& g# Y1 q7 `$ i$ G, R strcpy(lf.FaceName, "Times New Roman"); // 字型
) I$ V8 E& }4 c' A. ~# k# w! l ID3DXFont* font=NULL;
8 Z( p, V9 g' f: @& V. Q if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
4 Y3 ^) `: O$ [8 \7 [1 O) ] return false;. b' q8 ~3 T" W$ f( }
9 }( S, [4 ? g1 {$ T
font->DrawText(! H& F4 u+ ^9 ^) _, R" x, i7 R0 Z
NULL,+ ^9 d/ Y( V, v, c3 l! y0 s* t
strText, // 要绘制的文本4 H. G7 _5 n8 E! c2 {2 C/ R1 B
nbuf,
& Z$ s9 e. o* B0 [7 D9 _4 H &myrect,
2 z2 n& `! d4 Z" z DT_TOP | DT_LEFT, // 字符居左显示) d+ K+ Z, U% L- t% C9 Y4 s) X3 ^
D3DCOLOR_ARGB(255,255,255,0)); . G1 m! E8 t, G2 n3 p
6 Y( p" L- q8 F1 \
pDxdevice->EndScene();//结束绘制
; K5 z4 n: D3 h& d font->Release();//释放对象
+ s a# h4 B0 b. J }. S3 z) |3 A7 `; Z$ M: |8 O$ z2 @
return true;! U7 c4 i1 O2 T5 a6 q* ^
}; s% `% \" ~$ w+ v, K
' ?# \) Z" V+ |& w& {
源代码:
* c' X$ t9 D7 f, ^HookDx.rar
# A1 d' d J; }, Q' B, ^8 Y
! b3 _: F" J$ P' N后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险. |