本帖最后由 shane007 于 2021-2-13 22:23 编辑 + F* x% Z$ O f# t; k, n6 E& g+ B
9 x7 a* [, a8 s/ g, b
DirrectxHook 工具2 }; s! j E5 F1 K
1 S3 _/ }1 F5 a _
标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法9 d/ e& o0 j9 M2 }) U2 G
作 者: runjin
2 U0 q- {$ V* b" q时 间: 2009-04-03,23:44
3 @' T- D$ N3 e0 q链 接: http://bbs.pediy.com/showthread.php?t=85368
6 f$ L) r7 j% t- n* E- b6 g
% U- e) m+ E+ {( \9 p) {4 u; `Hook Directx:在游戏中显示自己的文字和图形的方法4 q4 A. W7 k3 y [" |
/ A8 U/ y9 K+ g$ ^% ]6 q/ F6 \
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.* y8 `+ g- Y9 d8 u
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.8 Y( D2 _& N; q( M0 I1 y! c4 i
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明./ z" Z$ e+ r- Z. X1 j
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.) I/ x* z$ J, x7 h: Z9 \8 Y0 B
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
( x' ?3 k$ l) {0 Z DWORD oldpro=0;
p) p: E( B( N memcpy(d3dcen5bytes,pC,5);2 |5 X5 e, F5 t9 z# o9 N
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);+ b9 A5 ]5 E- T- o
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
0 J' ~ m8 F: O5 c *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
- |0 ]9 j; P1 ]' I' y* u6 w# q$ {9 ^( L
a. X( @+ a# y6 X
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
8 {; J2 T( d r3 Z& e. iHRESULT CreateDevice(* _0 D+ _% ]+ q0 g8 X* m
UINT Adapter,
: ]4 s: n% U6 d$ f3 ~- L T. Q Z D3DDEVTYPE DeviceType,
( F) _& i: ?+ @) I+ C HWND hFocusWindow,
' s0 A/ X7 U' A5 t1 w DWORD BehaviorFlags,
! }7 F3 P( `' F+ Z5 Z% R D3DPRESENT_PARAMETERS * pPresentationParameters,
' \3 t/ o) h: M4 @- |! _/ P) W IDirect3DDevice9 ** ppReturnedDeviceInterface
6 A* W# r) L0 [);
# Q1 X H9 C8 X6 A2 O- g
" d2 O7 j* o2 P) N: d, m
# k/ H- o9 o3 {! x. |6 u而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:7 f+ f" i( c) s) D# ], k
HRESULT _stdcall hookedCreateDevice(
# D- c0 ~( }' W' a2 L: Z LPDIRECT3D9 pDx9,
1 W' E+ b' ]4 L0 A& v3 k UINT Adapter,
; m; E- W0 v- _. e- @ D3DDEVTYPE DeviceType,
' w( {" I" G3 \: B# }& o HWND hFocusWindow,4 d) j% S# X2 i" e
DWORD BehaviorFlags,' U1 `- i3 S. ]6 ^0 x
D3DPRESENT_PARAMETERS * pPresentationParameters,9 @, m/ T1 x6 e4 O5 v$ V; p
IDirect3DDevice9 ** ppReturnedDeviceInterface' L6 W! K5 U4 Y% d
8 D* q! k5 ?" R9 M5 _2 N) T! q5 \ );: b) o M* g. ?) i% z# M
1 T/ G+ U' A4 V, [+ y/ H1 }4 y其中的LPDIRECT3D9 pDx9就是this指针.6 R4 L) q, R3 B
& Q Z; E, S$ g8 L* a" f% Z6 r
hookedDirect3DCreate9的关键代码如下:
& N7 J0 t( X, m: w$ Z' w% ^pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针$ ?* Y: \0 C6 s
; O( S+ T3 f( F# V
DWORD oldpro=0;
+ E$ s7 d' j5 ~: ^0 H$ Q+ P memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节7 O- m" c) @+ X
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
) ]- l: A% o+ ^* Q( {+ V+ I( S *(BYTE*)pCdev=0xe9;
; [7 T. h8 R' U% o" `# P6 e *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
G6 o! x' {/ Z' y* o9 z$ O
1 X" A: I7 T3 [ @9 I在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:; P) q( }) {1 T3 V
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针8 c! a! l& m' U ?; b
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节5 j" J3 r# r4 o6 u
DWORD oldpro=0;) q2 u) M; H% w" J
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
* j5 a$ e& f+ x2 W *(BYTE*)pPre=0xe9;5 i7 _, h8 u a
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
& G! r3 o* d2 y
4 {7 {, E/ o6 J% l% p9 k- X: T+ g实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:. z& E' \' q" Y/ K, M# p" W! u
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";2 N+ L P' P, |) M" }3 H
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
5 V$ b! m5 J# b! S //在这里写入您的其它绘图代码9 K0 Z$ _: X* d* a
* L) V- G, ?) ?1 P
+ ^9 i3 w; u- P3 Q! l4 y再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
3 U/ Y2 R2 U$ n; O' @1 GBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)* `: l7 Q2 h% z+ V2 T+ ~
{
+ _! V. w0 G) t1 d4 p: R& S
7 K% _9 q: z% ~+ \* G if(m_pD3D && pDxdevice){" Z1 r, r& R5 Q" L$ }6 T0 N
# ~+ I% L% e# [' w. }* |
RECT myrect;# S/ Y& \5 U9 b
myrect.top=150; //文本块的y坐标
+ ^+ J3 Q/ }9 U0 m* V3 }! F/ o myrect.left=0; //文本块的左坐标& l) ]) j$ ]' A; K/ `6 c0 T
myrect.right=500+myrect.left;( g' x6 L% h+ G5 L/ k
myrect.bottom=100+myrect.top;
& u' w3 h! l' Y pDxdevice->BeginScene();//开始绘制
' F) V8 l/ z8 X- C1 I7 N' O, H* w# d% }
" Q, \( M7 V+ m: u D3DXFONT_DESCA lf;5 _6 F- r9 r- _/ {
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));$ v, f( ^0 H! c/ I
lf.Height = 24; //字体高度
- n0 y2 ]* f5 ^, I! ~0 z lf.Width = 12; // 字体宽度
& e- W7 [2 H# W$ A; [7 n lf.Weight = 100; 3 b1 I) ]3 U" Z3 P
lf.Italic = false;0 N$ e6 [4 b# k0 p
lf.CharSet = DEFAULT_CHARSET;
5 |, t% ^2 t/ T( @ D7 W4 u strcpy(lf.FaceName, "Times New Roman"); // 字型2 A% P; I1 E( V) ]# K" f4 Z
ID3DXFont* font=NULL;, N d, f( g! K3 \, i* I. x
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
8 I9 s q6 ~# C) c1 \ return false;
3 \/ H9 Z9 F0 h# l! E; c7 j2 u" x: u' j! R
font->DrawText(
7 \! m- o% d4 z4 L* o NULL,
* T5 d. ] X$ s* d strText, // 要绘制的文本* _" l5 Y. m7 n2 N2 {: Z
nbuf, . U4 T6 z2 t/ Y: E3 a. w5 X/ m
&myrect, % `6 X. h5 r, V2 e! s' @! Z% o
DT_TOP | DT_LEFT, // 字符居左显示
. N! ?. b3 @$ S! G$ R3 u" C D3DCOLOR_ARGB(255,255,255,0));
( l6 s6 z# W1 J8 L7 Q
5 M. e1 ]! m/ @7 E* r7 l. F pDxdevice->EndScene();//结束绘制$ j# {" W: ]3 o Y, u* z+ Y
font->Release();//释放对象# D1 |/ C+ i! [& s, ]9 n8 d' m
}
$ v$ H. l1 d: q4 S' q return true;, \) v' o4 P/ V) V
}" O- t9 F, ^0 U7 q
4 M; S. ~% @% ^4 M9 c效果图:+ e" m5 M, k' J' Y& [
6 Y& T' M* H1 f+ i0 f/ N5 k n
9 @3 q& I: P8 P/ Y; T7 ?4 p3 S
+ p, x. H" {) j! v/ ~) \, D 5 r, d3 O( u- T/ D- q6 x
0 e& |) r( y0 N" [; O* o
: u1 C# v I/ ~6 T% y" q: D/ M+ K后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.
\9 I# g {- ]! j
" \% J, j, T8 z; S9 \; E
2 C& ~6 {0 [% n |