本帖最后由 shane007 于 2021-2-13 22:23 编辑
/ x5 Q2 v9 c3 V7 z- s6 W7 m( v9 K$ Y5 c
; z+ }# t, C5 T9 y; RDirrectxHook 工具
) v+ W; e$ o# ?' s Z- q& Z$ N& v8 h0 L* c: N) c9 D
标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法
2 R4 H9 ~5 i# [' h' R* @# ]+ `作 者: runjin
3 ~: O4 L& V! {; V/ N# F时 间: 2009-04-03,23:44; ]7 Y( d3 }& o! T5 X. s& d% T! F/ ^
链 接: http://bbs.pediy.com/showthread.php?t=85368$ {' _0 Z0 A) f2 t) s, N
. _# x) a. [' a' `' N3 K2 eHook Directx:在游戏中显示自己的文字和图形的方法6 @% o: v- \: y; b f8 T
! e& H; U$ {- z! _2 d, N这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
1 k5 S1 X/ t' y9 k其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
4 W, U: h; X' x8 g" ~% z& f还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.& Y5 b5 o: G' `& \( W1 b* q% v
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
2 } x& _1 J: e$ ]9 ^ pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
7 u8 _, m- m+ y5 H& A- W DWORD oldpro=0;
3 @) z; [2 b+ B- c/ e/ R3 s memcpy(d3dcen5bytes,pC,5);
8 U) d @4 N' k0 x3 r* v8 y VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);' d& V6 ^& a* v" ^( ^
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
1 r* t9 d/ r" e2 W *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5% U1 a Y' Y, S# R
" v4 _6 L8 u% V
; x' j( h" K& V9 Q这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
( y- T6 k, q6 `/ I7 }HRESULT CreateDevice(5 S3 W# v# A: S7 ]" u9 \& ] |, S1 S
UINT Adapter,# D, i4 L5 |0 O1 E3 ^9 C
D3DDEVTYPE DeviceType,
8 y4 ~( S% ^* f1 B: \% ] HWND hFocusWindow,0 x. R: {. k8 z |. B
DWORD BehaviorFlags,, o; }: A4 W- x5 j% m0 p6 p$ @
D3DPRESENT_PARAMETERS * pPresentationParameters,2 l6 @$ k. F) p) b
IDirect3DDevice9 ** ppReturnedDeviceInterface
4 Q, U$ w/ h1 s. P5 A; z);. k F- m! K3 ], E3 t* E+ o# M
. N! ?* p8 X. Q: q4 S, q) E
4 `* X+ D) j9 M' R; O6 Z3 E而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
; U; K% @" z; `, eHRESULT _stdcall hookedCreateDevice(! N) g* ~. {! [$ m0 A2 {
LPDIRECT3D9 pDx9,+ h! D, Z# P/ }, ^3 S
UINT Adapter,& B' Q! c$ s& |3 ^0 r7 T( k1 k
D3DDEVTYPE DeviceType,
6 E, d+ r# q+ ]2 |6 F HWND hFocusWindow,9 A0 e1 q' j6 U9 m
DWORD BehaviorFlags,
: J' t9 u9 [ K2 G1 Q7 s# w D3DPRESENT_PARAMETERS * pPresentationParameters,
# h/ Q; [- U' r& S IDirect3DDevice9 ** ppReturnedDeviceInterface) E4 W0 c( c! v
& o! @! ~3 s8 X2 E' E( Y# l; P4 `# Z
);) d: H* D, ?$ ]% k8 C( | Z! ?( y
j9 ^2 s/ D3 N其中的LPDIRECT3D9 pDx9就是this指针.9 m, I; ~7 {9 g
: Z8 m- |6 `8 V
hookedDirect3DCreate9的关键代码如下:- s X1 K6 y q) Z1 J8 _- D. E( Y
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针4 s' Y, y n4 x
4 w2 @+ a {5 q6 G& _( q! Q
DWORD oldpro=0;6 o4 J2 s. W2 t; a
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节: i+ @1 r3 l$ W( g
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
" {8 T9 `9 O3 k: b' A/ W, y8 V# ?, [; O *(BYTE*)pCdev=0xe9;
0 `" Y% l4 B; d+ F. d *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
- e: l/ j. x1 @3 B3 C4 O2 n% e& Y" l9 Q, t6 g* p
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:7 y' M g$ ~; H& g' l C" t* d: }% M5 i
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
4 @ |* ?; f0 |# B4 M5 T' H memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
! _# v6 j$ a, `- j% c& F( U DWORD oldpro=0;
* t, I5 Z$ }9 f$ f2 x. K+ a+ f( V VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
/ Q& F! R0 {# k9 ]- B$ X5 g7 v *(BYTE*)pPre=0xe9;
# j( W* P# E+ g+ @5 q *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;. \: J8 `' b8 S( j% e0 M6 s7 c
, o+ n; [0 ~. Z, v4 i
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:4 g% y' |1 s/ F6 ~& Y
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";* x7 U3 x* A8 D8 K
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本) o2 O& f: X$ k! i1 ]
//在这里写入您的其它绘图代码
5 V. n2 a! Q9 g" H1 W8 T
1 s/ Z; d- \% u! p/ }! u) g; R3 C' `* O3 H- v8 B! P
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数: _/ Y2 `" I& B, S
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)! v; ^7 d. b# S. X v% ^: `
{
! \0 z+ Q9 b% ~3 p, G9 K: C
$ i: D! t2 f" V if(m_pD3D && pDxdevice){
4 s, r5 o. v0 h' m, ^+ B# P ]# z4 n) }& d
RECT myrect;- _; ]$ l- Q) h1 J; j
myrect.top=150; //文本块的y坐标
2 g2 m& }! ?! P) B3 V$ K myrect.left=0; //文本块的左坐标
& x4 D! W+ C% e. J b3 ^( v myrect.right=500+myrect.left;
; Z. f- o$ `/ z3 h3 }. j) r) y6 z3 ~1 z myrect.bottom=100+myrect.top;3 K1 h! x, l8 Z# u; |
pDxdevice->BeginScene();//开始绘制
3 l7 t7 U4 |% p
' m+ B, O. \! K3 i7 p4 r/ j3 a D3DXFONT_DESCA lf;6 Z1 K& f* x2 f0 }* L
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));6 q+ C- H, b. F# h, e# d& c$ t# N5 \
lf.Height = 24; //字体高度+ S- O0 z2 D4 Z6 w. d( ]
lf.Width = 12; // 字体宽度
* R" K1 ]* _# z. C9 f% K lf.Weight = 100;
4 _; I; h8 Y3 B- c lf.Italic = false;
1 e* v& X' r: O0 H* c+ R lf.CharSet = DEFAULT_CHARSET;
* m! S( J9 Q7 \! F# @ strcpy(lf.FaceName, "Times New Roman"); // 字型% B2 x% g: S) k" a. H; v
ID3DXFont* font=NULL;: u' B( R( f/ [* u$ e1 X6 D+ Z6 y
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
. g! j% u! k4 Q& z return false;
0 R+ E9 V+ Q \3 @3 [- S; _& C9 `0 k3 f8 \$ h" V4 _: k1 s
font->DrawText(& p, ?6 A* `" Z
NULL,
/ M3 N: V( R( k7 O% r/ @2 }) ? strText, // 要绘制的文本
$ s4 p8 @# |& g$ a4 G4 E2 p X5 m0 L nbuf,
9 P; f( R4 E( b &myrect, 8 B8 s$ l+ G2 H5 H7 u/ C+ ]
DT_TOP | DT_LEFT, // 字符居左显示
" }8 h) Y3 g8 A" S D3DCOLOR_ARGB(255,255,255,0)); % B4 F0 M1 b4 q! E" M
) N/ |% D4 b" n# I" Z
pDxdevice->EndScene();//结束绘制: n$ j& G9 ^6 {* q# m
font->Release();//释放对象
4 V4 U/ g) u( v- g1 ` }
9 _- d9 h# u0 H* R c2 ` return true;
9 }% [7 M6 h( L8 ^}% [+ w3 u" b W0 C
V4 {8 d9 T# @4 k1 s3 o; Y
效果图:4 c# s3 k6 }, D' z
) X6 A1 W1 i1 Y% f
/ E h9 N9 }9 \) r7 w& N! s7 y6 z, G0 M9 x* Y! y/ v
+ R/ Q* ~$ T/ _% B9 u& h
0 H$ `( G; D. m
: J$ a2 h2 D* T后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.
: w* Z8 f' @, v- _
% T$ B% L* r6 D) A ?9 |$ c D7 f
7 \2 ~9 _5 l; H% I- M4 K# y! C: g |