本帖最后由 shane007 于 2021-2-13 22:23 编辑
+ k' n7 G5 @6 q1 S7 T5 u* w, J0 S( O9 ^4 _
DirrectxHook 工具+ H& k) x9 H+ G/ _. w
" D5 c& Q& J- ?8 a. a$ z标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法
4 d( |+ @6 b& _9 `, _3 [- w作 者: runjin
6 v6 Y$ R5 [! N+ }时 间: 2009-04-03,23:44, }( i& n# w9 q+ D! x$ @/ z
链 接: http://bbs.pediy.com/showthread.php?t=85368/ h7 n. }( w+ s/ W* X! V
+ ^! u; p" g" t7 p# bHook Directx:在游戏中显示自己的文字和图形的方法8 y9 d( R9 C2 T
5 |, }' J+ {) g8 X; \) u. O这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.$ n( J4 p, g7 ]7 G% }+ @" t
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
3 `3 N3 @! p$ A% W# T: M2 o! D还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.: _8 l3 Z1 ?! M3 w
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.5 Z- ]0 w& @1 F% H6 k# N4 @
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
/ p+ N- L, [/ u. d DWORD oldpro=0;
/ X/ L/ G4 _* | memcpy(d3dcen5bytes,pC,5);
; ^1 v' p6 o# X$ H VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
/ f& {4 B; [8 [7 ~ *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
' ~8 |% a4 a, i* F( ^ *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5 {" A8 X0 ]% B0 W
5 c3 J2 Q$ B9 `) i7 O* c1 y7 g2 I/ F) D
2 j& t+ e E7 I2 ~这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:* s" G F6 \- v7 y1 e$ M
HRESULT CreateDevice(
& w7 P: w( ?: ^: v& K( u1 V UINT Adapter,: E4 i7 |/ c! U1 H0 y
D3DDEVTYPE DeviceType,7 k' L9 Q+ M! J4 d: y: t
HWND hFocusWindow,) y; Z3 L: C# N, |) F
DWORD BehaviorFlags,
9 N/ w" n$ P/ D4 k. S. X D3DPRESENT_PARAMETERS * pPresentationParameters,1 X; |0 ?0 R" }' y9 e; f
IDirect3DDevice9 ** ppReturnedDeviceInterface/ m( N# b6 s- I. i% r/ [7 e) q2 d
);
" h3 P+ J- `( W$ R7 r p, L' _' i8 f {
! d1 z" Q) H: o* i+ Q
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:/ J, p& Q/ `7 |2 g* s C, i4 K5 M, z, x
HRESULT _stdcall hookedCreateDevice( _7 @+ u2 Y+ @& D0 t' ?' R2 B* t
LPDIRECT3D9 pDx9,. [8 M2 h" a; a; x2 Z$ e8 d2 x- Q) G7 Q
UINT Adapter,+ J9 E; w" }5 S# Y- e$ l/ E
D3DDEVTYPE DeviceType,
% n( l; N0 a6 ?, J HWND hFocusWindow,2 k4 h3 g( g5 b7 a
DWORD BehaviorFlags,) d; R: x6 y1 A! ]. B4 o8 F
D3DPRESENT_PARAMETERS * pPresentationParameters,
m+ w' ]5 }3 u IDirect3DDevice9 ** ppReturnedDeviceInterface
$ Q/ p( I4 D! Y! j
; B& @0 I* u& J" G6 T$ l( V );" u. K5 v# x- |. [; H- _' z* S3 W6 c
' I9 g0 q1 |9 n3 D" y其中的LPDIRECT3D9 pDx9就是this指针.( h3 u& B7 f! o4 S' l4 r
0 U& b$ n. c" B- P) S* v
hookedDirect3DCreate9的关键代码如下:- }0 w' \$ c7 _2 z+ ]' g3 |" B7 U/ k! J: _
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针. j4 b3 s1 p5 _
4 |# [2 j' ~4 n4 ^: q( F DWORD oldpro=0;
- ~3 X1 R8 L4 _% p( J; Y) B6 W memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节- {: v5 _/ _ G
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);( ^- _/ V& o/ |% |+ k4 P& A: C- a1 O
*(BYTE*)pCdev=0xe9;; S$ @/ H0 `& s6 ^! J9 b
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;0 b7 }2 c) `5 a* V8 u4 |
* ?" e, D0 v6 x在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
+ N b# e) {5 m# \pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针" I9 w$ b; s1 h% ~ R0 p
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节# S' W/ g$ p+ H
DWORD oldpro=0;
: W6 p, I- K3 X, U. u0 { VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);9 e8 x, H S, S) L4 ]' i, m# A
*(BYTE*)pPre=0xe9;
4 z, d: n# L* V# k3 ~5 b" s *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
: ^5 B$ i! `% ?) P }/ w( Q# h
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:6 M& |( z9 o5 a0 x% K4 M* v
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
+ v4 D t* B6 X2 [ t# Q2 p DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本% x0 g! z& R- J3 _2 i
//在这里写入您的其它绘图代码
; i/ H0 A7 p# v* o. F
* F9 \4 f, s1 |
) T9 b- d/ O/ [* p9 {& k- q再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:# T% d6 p5 I/ J; X6 }0 E* q- u
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
' F( J5 l, T2 R5 K6 s{
u# e% H! m+ ~0 T2 D( b: i* {
3 i: ?* y6 S9 E if(m_pD3D && pDxdevice){
# F1 Q3 _7 t" D
$ X/ I& Z& M# C& B RECT myrect;; M* p, a; u9 I! `% m; a/ W
myrect.top=150; //文本块的y坐标9 x+ d: B1 c7 M. P
myrect.left=0; //文本块的左坐标- W. R- E$ T& r0 u7 I
myrect.right=500+myrect.left; m4 d a7 }5 H/ B B! I8 `, S
myrect.bottom=100+myrect.top;
' U1 D6 i9 T/ j pDxdevice->BeginScene();//开始绘制
( R. M- U _/ @3 l" v- P
; ]4 ?% @* b/ E1 F$ r4 K D3DXFONT_DESCA lf;6 ?2 C/ J, y; M) W- T: S. f/ i9 R
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA)); A, Z' p% f/ H# X1 h2 @, e
lf.Height = 24; //字体高度1 u0 R! ^& u |
lf.Width = 12; // 字体宽度
: A0 A0 W2 C- q9 t lf.Weight = 100;
4 X8 `- w9 a) L2 z) i, Z* N, h lf.Italic = false;
8 d3 v3 l! s! J* b8 f6 Y3 ` lf.CharSet = DEFAULT_CHARSET;! W" o# {3 s2 b
strcpy(lf.FaceName, "Times New Roman"); // 字型" M: k5 K) K1 R$ N1 J' a$ Y! `5 D# n
ID3DXFont* font=NULL;
$ V$ }! R+ i3 a1 g+ }/ b1 b9 b0 U5 I if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
# u2 M7 A2 I; g* h* {) ? return false;
9 r/ a! E% p% P! ]
' ?& Q0 u/ {9 g) | font->DrawText(9 E' B+ h3 ?' I6 J. H* T: I) _$ b
NULL,
1 d, \) G, `' }: Z1 R strText, // 要绘制的文本
, B4 M Y" K6 j6 ^, Q! L nbuf,
( L. D5 L/ D+ z7 Q) C$ ^ &myrect,
8 ?) C w9 ]2 w! a DT_TOP | DT_LEFT, // 字符居左显示7 K6 M7 o6 ?& y6 |7 ^
D3DCOLOR_ARGB(255,255,255,0));
" N8 I4 }. f; M7 c" ~
' P5 C2 L7 }) |+ V9 }3 \6 J, W pDxdevice->EndScene();//结束绘制
4 F# U: I9 ^4 i2 h U font->Release();//释放对象
* w& l g1 Z( v% Y4 A2 O! U. a }
: d2 X2 l3 F, C& q! n/ l+ J return true;
+ F9 r8 J* X5 [# w! v8 t}4 b' Z, d7 w- ?
+ w3 B+ N q6 y
效果图:
& y9 V- O1 f2 a * _! ~) f1 }& B2 r" Z) J
8 U* T7 n) q0 u6 o) z& O& T: v4 ^4 v" w; D7 f2 a
" j+ _) [; T4 F; e6 c: S
' a8 K5 J+ ^: t3 {0 [0 I
! v! S/ R6 V6 _2 O( o后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.' P1 S- }% u' z% J
/ |/ o, ]$ e1 d! D' Q3 Y8 F
" a' A: t* `) H- V* B |