在看雪找来的文章,和代理DLL的原理基本是一样的。
5 M; |: r+ }, w* v9 R1 m7 x0 v0 p' B) x
原文7 n+ M& r0 \( C8 ^4 x; H+ T/ T
http://bbs.pediy.com/showthread.php?t=85368
6 f1 c9 C& Y# T- s# x. I& J7 C- s! n0 {6 C3 Y. ~" l& l
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
) m' W k' p0 p8 E0 g) \1 r# t& Y其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
$ i3 Z9 C/ {& q. ]+ G+ C) }还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.5 q- M- e. F+ _6 q5 z8 T
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
; m5 ^ A8 t3 _$ C pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
% `/ g0 l6 n* Z& y DWORD oldpro=0;$ }, z' Y- ?: Q% j; o z' \
memcpy(d3dcen5bytes,pC,5);
6 }7 P* F" y0 Z# ^- P! W VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
1 p' { E! Q5 I( @+ {. j% C *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
. _& o, @! f) q" C! |" ^7 k! Q7 ]1 S+ H *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-54 F, T2 f$ h# f; {! T4 ?7 G
' X/ k) e8 W3 a4 w1 v0 q/ X1 Y* e# k' z* ^' D
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:% p J; T7 P2 |2 V1 K) ]6 i
HRESULT CreateDevice(
1 y+ i" s5 j) ~6 J UINT Adapter,6 Z& _9 d9 u8 w: k, L
D3DDEVTYPE DeviceType,
" y5 t0 m9 d/ r3 w1 ~" J( u HWND hFocusWindow,
; e( Z7 R2 x7 q& ~5 x0 B DWORD BehaviorFlags,
& l. S# i6 z% p6 v) k" K y D3DPRESENT_PARAMETERS * pPresentationParameters,
9 x- q( s, C0 S: ~( | IDirect3DDevice9 ** ppReturnedDeviceInterface
+ b3 D, t4 |5 J; f6 w w);
: a; D* N3 g# f" s7 b r n
- j. x6 m t& X/ c' X) H% Q+ P0 |, j |2 S9 F) M+ b+ k
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:" ?) @2 ]( b, c+ V5 A. j
HRESULT _stdcall hookedCreateDevice(0 s# i; V0 E' q& |
LPDIRECT3D9 pDx9,9 v* c9 ?; |' k$ f
UINT Adapter,
# S( y5 O* p7 _7 ~% L: ^ D3DDEVTYPE DeviceType,/ \+ R% s$ r5 n1 q+ _! n' m0 h
HWND hFocusWindow,+ A# [( \# S. v- o% h; S
DWORD BehaviorFlags,3 }! u4 f- P f/ k
D3DPRESENT_PARAMETERS * pPresentationParameters,+ M- u' K# z5 ~1 u5 ~& ~
IDirect3DDevice9 ** ppReturnedDeviceInterface
/ r3 v$ f5 J- d' B& ~1 G& V
. C1 R7 m7 y, J/ l! G, ^; E7 G/ l );
: z _ M) ?6 N. o y
6 q% L) G0 ~' [& E7 d" x$ _! U其中的LPDIRECT3D9 pDx9就是this指针." g3 ~4 F Z2 k1 P9 a
4 ]( ^8 _; [) a# Y# M& o; @, |hookedDirect3DCreate9的关键代码如下:
9 [% q7 W2 [* l; XpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针7 G) W/ K; q& p, M1 O1 t$ P
# i( A% h m# t# ]- g
DWORD oldpro=0;! l; U$ @ y9 C$ d
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节8 r! p9 P2 b: Y
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);4 J6 j6 `" A+ x1 @) j; t5 [
*(BYTE*)pCdev=0xe9;
9 b* ^/ _" W+ z% l- o4 B" R t% g *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;, s9 z. P |( z$ v) e2 N8 ]8 ~; ^2 E
8 p* x; r- t. n8 n, c' p在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
1 ]9 m( N/ w# npPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针4 D! v. N8 k: o0 F
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
+ P. t( |7 d) C) n DWORD oldpro=0;9 |8 L# c7 T E5 f7 J/ p$ {/ }
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
4 R7 |8 L5 v3 L+ H *(BYTE*)pPre=0xe9;5 X/ X& z1 y+ M; E+ \, Y f+ p9 r
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
4 d0 y5 R7 ]. o9 U4 s
+ N! \: I7 V9 P3 t# |+ O实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
3 V+ b; ^! k! V8 f% kchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
4 H! m$ T+ X2 b0 t6 S; f" t% } DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
0 z; a' u) V: F" L# b5 [ //在这里写入您的其它绘图代码+ }4 e3 {- q' n! g! U3 ~, B
$ @8 K4 }1 w8 n0 f% A
; W' M, S# t f8 m
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:* Z$ r$ `- |0 B Q
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
# o2 ]; K( J7 ?! S, ?{
3 W9 A A _* X; ^& R
4 T/ y+ p% Z# u$ O5 I- P1 F; i if(m_pD3D && pDxdevice){% Y& V! \7 J# ]' w J
4 [, S2 x1 t4 B9 q$ N, R. o; K6 F( f0 d RECT myrect;
) Q9 Q( W8 x/ `. P* \( Y' @ myrect.top=150; //文本块的y坐标8 F; Y* n( z R% ]
myrect.left=0; //文本块的左坐标
( R! m, P% T3 N$ [" r' a6 L myrect.right=500+myrect.left;+ k" `5 c4 O( K" A3 _- S5 j8 m+ U* E
myrect.bottom=100+myrect.top;
, ~% M. {2 f9 `8 E. [" B, i/ V pDxdevice->BeginScene();//开始绘制
, v. z$ `! s4 Y. U/ F0 n& R; m; g9 A
D3DXFONT_DESCA lf;. i6 N H& \0 `- v1 T K9 X! ]
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));- |6 h4 N; {, u1 z! n
lf.Height = 24; //字体高度8 m- G9 S- {7 L, y. x9 W2 w
lf.Width = 12; // 字体宽度
P: f8 g# q1 p) A) C lf.Weight = 100;
" g) g1 x$ I7 `7 { lf.Italic = false;0 M' ]; K, u" ^+ x! j
lf.CharSet = DEFAULT_CHARSET;
8 ?% b7 w+ Z! j# h1 F strcpy(lf.FaceName, "Times New Roman"); // 字型5 \* Y8 B* f! K* @& k. F
ID3DXFont* font=NULL;
5 a$ V# a0 \: a7 D% K. ` if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象1 X% l; m3 s# c% [1 O' V, t
return false;
( s2 U6 @; G/ p* w9 Q0 l; Y- F- B- H8 k
font->DrawText(0 ~7 y" ?2 A( C$ y
NULL,6 p6 {1 ^$ C+ x7 v: `) D( h" o
strText, // 要绘制的文本; P! Y5 b* l C/ O) i! _. l* W
nbuf,
0 I8 I- o, r! B( T0 o/ h &myrect,
: W# z1 `* c2 T* z/ F DT_TOP | DT_LEFT, // 字符居左显示
9 O2 G4 A. i9 B, S D3DCOLOR_ARGB(255,255,255,0)); 8 w! p/ J1 }( U; K0 v* {
% c0 u2 g: r! `) f/ B. h/ H pDxdevice->EndScene();//结束绘制
+ R: l( S, y; ^" W& a font->Release();//释放对象9 |8 n- U6 n; W+ s% U8 i
}
3 C$ v# t0 H' ^' U. e1 \* U7 i- v return true;
5 x+ n; H; M: h( G ?8 e- q; M}
; N7 y5 u/ q9 y3 H/ z0 h1 X- L4 u3 }! j$ i. w! K* Z# Y
源代码:- @6 d6 d8 R% P( M
HookDx.rar
* L3 s* }& x8 ^- J
# A. j$ J8 N5 {3 N( D a% C后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险. |