本帖最后由 shane007 于 2021-2-13 22:23 编辑
3 n& N9 u/ V$ o7 r4 _
5 K2 R! k6 I, S5 U% {& l& zDirrectxHook 工具
9 v; I- W. _+ F9 p0 {4 M* e% K5 }+ ]* H g3 v
标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法. }. D' z6 t3 `' j8 g, o) _! B* A
作 者: runjin
9 M( y6 C- S& P& Q5 G3 {时 间: 2009-04-03,23:446 r# }4 R1 ?9 ^% u) ]/ g; D: r
链 接: http://bbs.pediy.com/showthread.php?t=85368
9 K% C# H u. B# t1 z2 U" ?7 o& k) Q9 }
Hook Directx:在游戏中显示自己的文字和图形的方法
( j y' S! m! U0 a
6 G" }) h* T" G6 Q0 \+ J3 u这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.9 q) L' g4 K9 E1 |
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.2 k6 s/ w8 f, W, A0 X7 e
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.! z8 F" t9 o- G4 }
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令." a! P) f6 a5 d) n6 f! Y9 }. h
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
1 G+ B3 \: c3 z' S, w( U DWORD oldpro=0;$ P9 C5 _( O# N: z) i2 U2 f
memcpy(d3dcen5bytes,pC,5);& y N5 G9 A. t5 z8 R# ^2 I
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
/ ?, w0 p+ G5 S6 T0 C% h# R. j *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码! R+ K4 }* r$ _0 A" b% D
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
, V: Y3 q/ t1 t
' [+ e) `: \: k% U: O" M; F6 n+ d! x2 Q
' A1 i( n) W; B+ D; q2 V" U5 p这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
# R' c' ^6 j T; P6 bHRESULT CreateDevice(
/ f& u! w" e( m5 f4 Y# H/ E UINT Adapter,
6 e4 A4 p6 O5 P8 h) \# [ D3DDEVTYPE DeviceType,
% P: [) ^3 ?% ` {8 J5 ~; l- X HWND hFocusWindow,: J5 s( L" D8 U$ h
DWORD BehaviorFlags,' p# w1 |! G) t8 Z1 c+ s
D3DPRESENT_PARAMETERS * pPresentationParameters,, V6 I# l& ^4 \8 w' l
IDirect3DDevice9 ** ppReturnedDeviceInterface
! l4 @9 J+ J- Q+ U8 E);
% B5 |9 y6 k- a- V
1 ]- k' M" |( f v4 c4 [
! _! d: d. |: `# F4 I6 ^而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
! |, z6 q% z) \: e! \. Q+ yHRESULT _stdcall hookedCreateDevice(/ A9 |+ }6 \. B" O4 O; {0 g
LPDIRECT3D9 pDx9,8 R: M* E0 P( d7 A* H) X
UINT Adapter,
0 h" v2 J8 |9 f; ?: y! L2 M0 ` D3DDEVTYPE DeviceType,
$ F$ t4 K$ k9 V1 K! ` HWND hFocusWindow,* A- D* e$ C) \% Y& ^3 b
DWORD BehaviorFlags,
8 Z. Q2 ?1 n/ c% \) f0 S2 t0 n7 D D3DPRESENT_PARAMETERS * pPresentationParameters,
" _( o, D; y4 @) [+ \* O IDirect3DDevice9 ** ppReturnedDeviceInterface
2 B& z" p5 q: O7 S8 {3 }0 R9 f' x6 q: `/ S0 p
);) S; @" C. e0 d3 j0 a$ V* m; p
7 _5 y0 w0 J8 @
其中的LPDIRECT3D9 pDx9就是this指针.2 M3 r3 L3 V* G- d
/ ]3 @8 Y6 u# x: ]8 K5 khookedDirect3DCreate9的关键代码如下:
2 u4 L. |! E# F' X2 _- |. OpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针6 {7 ^+ S+ y2 g1 u/ [% _6 Z
+ o9 Q# f6 J0 a ^
DWORD oldpro=0;% ?7 e u% s4 x* u4 E1 y! ^# u
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节; P1 u5 r% S1 K+ |) r3 _4 e/ R3 l% s
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
: z" r# V, Q8 n& Z* f6 b4 @ *(BYTE*)pCdev=0xe9;
; C+ l3 N* \, J *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
2 B6 i. O- t6 M7 m+ G- i8 v' j( [8 N2 u7 f, }/ z
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
% {( E0 A8 {* r7 Z, GpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针! O+ L; X! X3 W) u6 u
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
/ {5 P% L, O' u' A/ g DWORD oldpro=0;. @+ V5 Q i2 y c
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);9 H, m4 ^0 i! ~) ^
*(BYTE*)pPre=0xe9;! R$ ]* b( o; ?7 N J. @) x( F
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;8 [ M% q) p5 ~+ N( u* `
9 Q4 J. l' R% ?3 f+ |/ {2 d
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
: J% A* U! E. {( [char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
, |- h/ |' s: l, B+ j DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
2 E- t& x0 J5 L* E1 x, o //在这里写入您的其它绘图代码* R# b! U" H2 r& a) x$ q& ~
. m6 a0 b, r& k% @0 M
. g! s5 m$ j) V: ^' k
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:8 ~8 J" ^4 |+ ]) q* U6 ~3 w8 g( x
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)9 ~9 M9 O" m+ _# a: R
{! g) A. b5 D) b8 ~% @
$ c" X* W5 [3 L- l& n% i if(m_pD3D && pDxdevice){
* m0 `: I% ]+ H- y5 v
: [- x2 J4 z+ | RECT myrect;
3 c5 }, ^. k6 \ myrect.top=150; //文本块的y坐标& l0 m' M. M9 {
myrect.left=0; //文本块的左坐标
* W6 f1 g6 g# }3 u myrect.right=500+myrect.left;! r( G# O* ^) W6 C
myrect.bottom=100+myrect.top;
9 b7 n; E' Z N* W, x( R K0 K1 X pDxdevice->BeginScene();//开始绘制: Z- `* Y% t4 G- y' |
4 r# g( h* t& P* i
D3DXFONT_DESCA lf;4 E" R! N6 k% q/ b6 A
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));8 a( ^" R+ e/ o- R4 w# F) d/ I5 Q
lf.Height = 24; //字体高度3 ]6 f+ A Q" R3 ^: a6 T
lf.Width = 12; // 字体宽度- e2 K, U9 ]$ Z% t. t
lf.Weight = 100;
e# _" ^9 V% _! ^ lf.Italic = false;3 l, l" ~: k5 n- ?" ]6 I/ f
lf.CharSet = DEFAULT_CHARSET;
7 ]( D. l/ q+ U- l- y strcpy(lf.FaceName, "Times New Roman"); // 字型+ [, W2 x# O3 ]0 c: f6 E
ID3DXFont* font=NULL;; q3 W4 i% q2 d+ r
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
) @1 s& g3 P' ` F5 [ return false;
" x% D& G7 u. z5 W/ A: `7 _3 |; s/ g- s2 a( [5 N
font->DrawText(9 s' X' v: Y, B% j+ o
NULL,
$ X$ v5 d" k8 @ strText, // 要绘制的文本
: B. c; G2 a2 ? nbuf,
1 V+ g: y1 D% f, c &myrect, 3 h! {4 e- Z: D6 h4 Z
DT_TOP | DT_LEFT, // 字符居左显示
$ e1 z. J6 ]1 P5 {! u- ?* f9 a) e+ T D3DCOLOR_ARGB(255,255,255,0)); . _0 m E& S; k! C# Q# A% \2 E
3 I8 g2 C2 S( y) U& X& D pDxdevice->EndScene();//结束绘制
- a; y% P. S+ B }& y' N font->Release();//释放对象7 Z6 @! H. k C& V' n. q, p$ U
}
- I7 ` V) s; T5 [) ]. v4 z* E) E return true;
. m" P/ {, r$ ^# e}" b1 V8 }( b5 y% X
& E) E! a* i# h4 h* Z- v3 E效果图:- _- J% T0 R! g5 {* o* y
. O, t% u! h0 k
+ P4 o; n& g0 w' [5 V7 d. ]- I
3 b5 q$ a b6 w4 a/ \
- W+ d- l* B) X8 g& @1 ^$ b) K ]
/ N2 m5 w- A, P; T! D$ u/ t1 Q& H, L" }6 U6 Y$ g: c. `
后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.
2 X4 a$ \* J9 R J. R! T9 P
3 o+ R$ W7 Q! [0 m! V& @ q
5 H' z+ i# E ^& I9 H7 y9 Y5 w: c |