本帖最后由 shane007 于 2021-2-13 22:23 编辑
7 [. r; e$ U1 R2 L( G
8 l4 y4 n; H2 Y# vDirrectxHook 工具
* Z: U) U* p3 t" i
. X8 M$ G" S; B2 a2 U3 l标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法
) X# Q1 k# N- j8 P作 者: runjin: y$ C% R% m, |
时 间: 2009-04-03,23:44% Z2 x3 l5 l ], p
链 接: http://bbs.pediy.com/showthread.php?t=85368; w2 x) t$ w3 c2 o0 l, k
8 O0 f. _ j: p4 d: d7 O& U9 aHook Directx:在游戏中显示自己的文字和图形的方法
/ ?- y& c2 L+ Z$ u% V
9 |# ]6 F8 Y$ g# d* F这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
/ m- _- F0 a# Z其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了., j4 ?: X( f k- M1 m; m
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.3 L' V/ t8 i5 {1 [: V' W
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
2 K. m9 _& f! x3 _ pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址9 F* L/ u' G) ^. k
DWORD oldpro=0;$ R9 r, X- ]% F, w
memcpy(d3dcen5bytes,pC,5);; s6 _/ a" r+ f1 G. I3 y
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
) |1 u% n$ d% V& K *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码: h! s$ D3 r7 b) t% m
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5. s' L" f6 L2 V' K
! N: Y0 c) B4 \# i& Y+ T
8 Q% s/ K* ]" {* E, x这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
F( f+ s( j& xHRESULT CreateDevice(
/ {) f6 m( G7 F' B0 N; |- s UINT Adapter,
" ?9 y! u- ~$ X' j D3DDEVTYPE DeviceType,( l2 N& v: A" u
HWND hFocusWindow,
; o/ \/ Z" y) s0 E DWORD BehaviorFlags,
( u9 p) G1 E9 ~ D3DPRESENT_PARAMETERS * pPresentationParameters,
# i1 m: @; @2 U- w# Z- c) l5 K IDirect3DDevice9 ** ppReturnedDeviceInterface G c. h7 I4 C
);+ Z( t' z" Y% m/ |
* I9 Y0 f# I3 U8 t7 K/ u
. i5 K8 U' Y2 Z9 R( h) e/ r' l+ K- V而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
, {! j. r6 x o" [9 RHRESULT _stdcall hookedCreateDevice(/ T3 W4 t3 G# ^) a
LPDIRECT3D9 pDx9,* W( x* v ^' a* J* O/ q
UINT Adapter,
$ h# V1 A/ e5 m# x* y% i& M D3DDEVTYPE DeviceType,
9 K1 c6 b9 Y I0 }& M/ v/ F0 b( i HWND hFocusWindow,0 i, p, ]6 F) D6 H% s4 n
DWORD BehaviorFlags,, U) }0 l/ m E0 K8 O/ j7 M
D3DPRESENT_PARAMETERS * pPresentationParameters,; F0 s" `/ T. \
IDirect3DDevice9 ** ppReturnedDeviceInterface* ^* J& T+ E9 _5 x2 k$ B4 L9 w
) Z! m7 q/ z9 R5 A- v2 w );
" e% x5 }. u; r$ W4 z! ?" b- u; x+ F- T; H; r* C( A3 ~& q1 Q) g
其中的LPDIRECT3D9 pDx9就是this指针.! Q8 m" [1 `( ?. ]0 m' U
) V) l0 h1 S0 _) n Z' \0 K/ a
hookedDirect3DCreate9的关键代码如下:
! J0 S$ v5 H/ U' _3 c- Q, JpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
$ F& W E) C/ j5 a# Q# _ T2 x( P4 a# }
DWORD oldpro=0;
: E- V p2 A6 z- ]' l: L6 F memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
$ S) S4 {% j. N- N& Q: W VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);* n) V3 J+ ]" m& ]" f B
*(BYTE*)pCdev=0xe9;
7 W, O4 i( P( T+ a/ t# S3 ? *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
$ V$ v/ v5 X( o
, d) `0 b7 t2 l) } I8 C在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:% `/ p( L% o+ [& ?+ d+ v. U
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
8 p, W- }( h: v' Q memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节! ~- e& x2 Q% h. g+ o5 T( T7 R
DWORD oldpro=0;5 z& e- H, }( m; F" U1 g0 k* s3 x$ h
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
( A% _ M; m& }4 V1 h O *(BYTE*)pPre=0xe9;$ C# R {2 P# m
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
! L5 ^0 y# r& M5 l$ k( j* ~( X* Z$ Z7 o8 C1 {/ D/ o% k; H( o
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
; z' r; g5 c: ], F9 W& W) v* ]" j8 pchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
4 T/ o/ c5 v9 ^4 d# w" x DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本0 q2 h; _1 B" J
//在这里写入您的其它绘图代码( H7 q4 Z0 I; D8 L, K( R6 |
0 D) N% R( g% m- Z9 S. v% n: \
0 b4 c7 S0 d# z w8 D! h ?. t& U再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:7 t$ v& c; c4 i6 D, D
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)0 r1 j) m$ {* c( o5 a
{
9 P$ t* N0 a' C7 y* c$ u& d
{1 ^! U5 @, ^ if(m_pD3D && pDxdevice){- D' E: T0 T# {8 o
' j1 j6 {/ b/ |- O. i h F
RECT myrect;/ Z, w X1 _3 u+ t& t4 h& M. ~/ p
myrect.top=150; //文本块的y坐标& {' f" m. `9 d9 m: d7 ~( y
myrect.left=0; //文本块的左坐标, M6 {: v4 G4 g$ ?+ ?! d
myrect.right=500+myrect.left;
, J3 R' y+ f( w: t myrect.bottom=100+myrect.top;
, _( E! Y5 x, a pDxdevice->BeginScene();//开始绘制
& r# Y& M' T* ?$ x1 [$ E6 l( {7 I! m$ M9 c% k/ [8 i
D3DXFONT_DESCA lf;& M) L& K" p' S+ h' C2 Q
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
, A! P3 B1 Y" V! u) R* O lf.Height = 24; //字体高度# e0 M2 \1 B. v- _, p+ i
lf.Width = 12; // 字体宽度
6 c& ?2 E& @7 X- k8 L2 _( Q0 o1 v lf.Weight = 100;
' ?; _0 c+ H4 Z, i" M |% h lf.Italic = false;
% E7 ]! x* i, o* j8 M lf.CharSet = DEFAULT_CHARSET;+ B4 J1 E1 H6 L- R2 c" U
strcpy(lf.FaceName, "Times New Roman"); // 字型
! D+ Y$ _. j. g% w' [ ID3DXFont* font=NULL;/ d$ X- d+ n( f: Y
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
% j* m6 k' @: \: `: m6 I% ~9 @; G0 q/ P return false;7 w& V7 I1 d& Y1 t
/ u& l( D' ?9 g0 y0 G( r
font->DrawText(9 W0 v# x+ O( ]* y8 H
NULL,
: U1 H* G( j4 R, o$ q$ b9 T strText, // 要绘制的文本 Z+ E9 c: j+ R5 n' k2 L. H, P
nbuf,
- g6 p, ]5 u8 f& P8 I$ Q1 t) A &myrect,
) }* L2 ^ Q0 j [: @ W) l DT_TOP | DT_LEFT, // 字符居左显示
3 J% i# n2 L8 N/ _) I6 b! R D3DCOLOR_ARGB(255,255,255,0)); ( q1 k& I8 _2 x
* t! \4 J, h' j+ H
pDxdevice->EndScene();//结束绘制- f$ g5 h3 Q% ~: ~: d: v
font->Release();//释放对象
" z) n0 g Y) [$ e- U }, F( ~$ M6 V1 N
return true;
4 ^* _" ^# H0 i6 h$ a. K( N' R- p}) z" C5 ^' z; }/ }& W6 p8 N
1 O+ d0 `- `1 _
效果图:; U6 ^" ~% e% _; T* r* y4 n1 k
( B+ I) _; ?9 n; \6 a) T& S) G3 W2 k
6 c" t, R6 _) q. E8 P
% {7 f3 S# ~& E% o9 A0 o& @/ j8 U: X8 E/ _2 E0 B
1 ~- k9 Z: S* k; H6 Q7 w% R后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.
0 d W. K3 m* E X4 z
6 m6 ?. y. C- D2 z# }! ?
6 d+ g3 F& M3 X" R |