冒险解谜游戏中文网 ChinaAVG
标题:
【代理DLL汉化研究】Hook Directx:在游戏中显示自己的文字和图形的方法
[打印本页]
作者:
shane007
时间:
2009-8-28 21:13
标题:
【代理DLL汉化研究】Hook Directx:在游戏中显示自己的文字和图形的方法
在看雪找来的文章,和代理DLL的原理基本是一样的。
0 j5 ]& `1 t% F: p: o
- b" D: U4 o. R' w
原文
1 F+ w7 n) P, P/ c. E' z
http://bbs.pediy.com/showthread.php?t=85368
# D* e4 V+ X0 x! G* K8 F; \
( o6 Y, o- |) {' ^+ v g
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
! |) }( d! \" l: s: Y. T
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
, j9 K7 ]1 W/ X T
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
" _5 p' Y& A1 J, N! C# m
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
3 E' @! o6 h8 e a- y( U
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
& l) h& ?" }9 |
DWORD oldpro=0;
6 l! a" L4 ?- W! ?% \+ o+ H
memcpy(d3dcen5bytes,pC,5);
, [: o9 ], d+ I( s& Q6 m+ C
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
$ e8 @9 X, W# M0 J( x2 e
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
! x7 H" Y% z8 x$ Z! I( Z* H
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
# ]& ~ d; f7 M; J
& a4 ^0 ?/ s/ w+ j2 J# H
; V3 O2 J& L3 n1 m7 h
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
' R; O+ g/ q4 V4 a. a9 j
HRESULT CreateDevice(
2 k# b$ y2 O% I% I$ |) o+ _; [- V
UINT Adapter,
( w/ M7 Y7 f- ?, m7 \# d
D3DDEVTYPE DeviceType,
# [% j. B) Q. j. G! ?" J* q* q
HWND hFocusWindow,
) g2 ` u3 [0 x5 W4 @; a/ q& G
DWORD BehaviorFlags,
2 x- {8 R& A5 ?3 M
D3DPRESENT_PARAMETERS * pPresentationParameters,
4 @ T- B' C6 `5 r+ ~+ Y$ v e+ r
IDirect3DDevice9 ** ppReturnedDeviceInterface
) t/ l7 t9 D& Z/ J+ W/ K
);
5 _; i# X3 X4 b
8 F& l; X" S5 w" u# {9 i
( t3 e, p: c8 N4 h
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
- X& ?/ k) n4 J3 P4 D
HRESULT _stdcall hookedCreateDevice(
l8 T6 h V' h6 L
LPDIRECT3D9 pDx9,
4 z" j- u$ J- H, j2 \0 g
UINT Adapter,
0 u7 j D) `8 v+ h
D3DDEVTYPE DeviceType,
! p8 _1 {$ N2 N- j5 P( {5 u, u
HWND hFocusWindow,
- P$ O; ` U1 X4 r" b9 i
DWORD BehaviorFlags,
/ t# _0 a. ` t p2 C6 w
D3DPRESENT_PARAMETERS * pPresentationParameters,
' Y+ X- C( ~. H- f8 K
IDirect3DDevice9 ** ppReturnedDeviceInterface
/ m( d: n7 A0 g6 L2 z$ N
3 k* M- v9 i) w
);
! S$ ~5 K5 r5 L
1 D7 n2 m# {- E, g
其中的LPDIRECT3D9 pDx9就是this指针.
/ |+ c$ f- H2 o8 X( H# z
3 ]5 P0 z& F& w A
hookedDirect3DCreate9的关键代码如下:
3 o& W, M/ n3 J }0 O) N( r
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
" }$ o- v: y0 u* m
! ]3 a. O: {0 L; M0 l
DWORD oldpro=0;
" O4 [, s, ~3 D: ?
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
$ H3 s' ?; e. m: a9 r
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
0 H+ k c* R, ?( R+ B- O
*(BYTE*)pCdev=0xe9;
V# L7 l; R5 ]9 r7 E) T. l
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
# |& |+ [/ H9 i+ U1 d
7 |! K5 U# C5 J
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
9 p/ O; y8 f2 F. H; C( l E
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
: Q* r1 W8 D! n7 ?" \$ p y
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
" H$ S3 o& U! D; s6 G
DWORD oldpro=0;
2 e) {% z, z4 z
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
0 _; b3 y" j4 J! n1 u0 Q7 x; }
*(BYTE*)pPre=0xe9;
" B, V F; s* i. [0 g. Q
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
( j# v* v, r# ~4 \
! F6 o# J; f" c: U9 E% d5 B; F
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
9 s8 p3 C& x# D; v2 [0 o
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:
[email protected]
";
! l+ p. y8 e7 L' E" k
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
6 j6 v N' c& r0 F3 ~
//在这里写入您的其它绘图代码
* P; L. E5 @% Q. j
* g. n* ^; L& X5 ^; Q2 F3 M" m. @
* B5 ^" s+ j4 f+ [+ U2 X7 M+ N
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
- |$ \0 o4 l6 A0 S" L
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
* b: J3 @; A: Y& w0 C! M# P/ y9 W
{
9 H; R% r, I6 w$ Z+ k4 [# e
; D! w; `# y T3 ~: F
if(m_pD3D && pDxdevice){
) z) F9 A9 @/ a* N6 l
1 a5 ^$ I$ H8 t: o) [4 v8 `: G8 U/ K2 ~
RECT myrect;
5 i0 F: h3 m5 A% `1 J' |& W1 D
myrect.top=150; //文本块的y坐标
$ \0 d8 E1 f" q$ Q% O; N
myrect.left=0; //文本块的左坐标
% b2 f5 A7 C- L- X1 \
myrect.right=500+myrect.left;
& L# U' Y+ f5 G( \. s3 u; D) W
myrect.bottom=100+myrect.top;
$ l* w1 `" W, b5 K
pDxdevice->BeginScene();//开始绘制
# F* d$ y3 x, n/ E% m4 g
7 F- x1 S9 |8 R$ S
D3DXFONT_DESCA lf;
* P+ ]: a/ Q7 q& n& L# [( @2 N
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
0 G1 L/ ?( W( e3 `
lf.Height = 24; //字体高度
1 `6 L+ P+ |1 m0 ~
lf.Width = 12; // 字体宽度
; G3 D- F8 K4 M/ q' u/ k2 D6 M) g
lf.Weight = 100;
0 j) P0 w" K7 m+ e
lf.Italic = false;
# J$ W7 Q, v7 X' V. v$ v7 D
lf.CharSet = DEFAULT_CHARSET;
3 e4 R9 a* E( X3 h4 Q
strcpy(lf.FaceName, "Times New Roman"); // 字型
; G9 S$ D& \0 `( o5 g0 w! g- B
ID3DXFont* font=NULL;
* b7 M a1 c6 J
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
7 A" x9 m& h$ d2 \2 Y: ?, L
return false;
! c8 B$ Y9 N8 o' h7 ~# ^8 _
! [3 d1 ]: w% X4 {' X8 R! V
font->DrawText(
' l8 O6 V* }# I1 }
NULL,
4 t0 L6 p5 Z: r" f" j
strText, // 要绘制的文本
$ e8 M5 t' h2 X
nbuf,
+ B% D( T% t; l
&myrect,
2 o& E8 y9 i: \( f+ \9 B6 [% t1 x
DT_TOP | DT_LEFT, // 字符居左显示
2 h4 u. u) K6 B1 Q: j# ^6 D
D3DCOLOR_ARGB(255,255,255,0));
7 u% b. D, x0 W P6 \
9 ]) a& H! W V& T0 M# i
pDxdevice->EndScene();//结束绘制
- g! Z+ k3 ] s3 F1 p
font->Release();//释放对象
8 h! T% J* }( O/ ?5 [
}
1 K2 D) t8 ^! S f- W+ t4 o; _3 X
return true;
3 \* R4 j5 h+ [7 i+ f1 W
}
0 s! l. J2 u) I% U9 s0 V! h/ N
5 @* |1 w) O6 U
源代码:
% m7 h! V o/ S& U5 f1 ~5 e: y
HookDx.rar
7 T9 y+ k1 ], M h8 O) h
2 D( \9 B& l( ~9 G: ^' N) m
后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2