冒险解谜游戏中文网 ChinaAVG

标题: 【代理DLL汉化研究】Hook Directx:在游戏中显示自己的文字和图形的方法 [打印本页]

作者: shane007    时间: 2009-8-28 21:13
标题: 【代理DLL汉化研究】Hook Directx:在游戏中显示自己的文字和图形的方法
在看雪找来的文章,和代理DLL的原理基本是一样的。
4 J$ x3 R9 h8 F: O/ O& q# ~' l3 |& \& o! S
原文, \4 W# l! V5 i
http://bbs.pediy.com/showthread.php?t=85368( d- f/ G) @# Z* ?
, ~# ~2 p# |( W  ]) E. c
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.5 L/ l; J' |1 z! V
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
; d: J3 ?8 Y( }  u% n3 m! |( J; }/ N还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
) j: j6 ^) D# U$ C首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
( q0 t, V( _; v9 o7 _5 S' E    pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
& p6 i- D. S) l( c2 O) A+ Y6 O6 P2 K    DWORD oldpro=0;
: E, h: s  [# G- x( L    memcpy(d3dcen5bytes,pC,5);7 m5 x0 }8 L$ d* j
    VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);9 f4 R- O) H8 O5 P( i4 g' j- @$ A
    *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
# D8 N, u! o$ M/ f) h6 \    *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
9 d6 [# ]" L4 ?4 V" a! N+ Z5 y* m  \& Z) c; C8 Z

/ W- s) ]' o* L0 t% a4 i这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:# \9 f( c/ R( h( f( Q
HRESULT CreateDevice(
1 ]9 g( _, Z; A8 e% N( T0 ~* F) M* W* R9 x  UINT Adapter,
& J% \5 d; h9 u. ^  D3DDEVTYPE DeviceType,9 ]* p# j. x5 J
  HWND hFocusWindow,# p+ d( L2 A0 j1 y1 A8 O
  DWORD BehaviorFlags,
+ J* |! r! |1 [6 P2 \5 T) d  D3DPRESENT_PARAMETERS * pPresentationParameters,
9 `( U- G" H8 b! v" _  IDirect3DDevice9 ** ppReturnedDeviceInterface$ H2 H0 v( E+ s0 o4 V5 k
);
# L+ y# @/ N9 l; a7 o  P0 \* E7 t! G
$ P1 r: h; L, s9 ?
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:" ]4 u( z% c+ W! N7 ^
HRESULT _stdcall hookedCreateDevice(
: S/ g! F# d. ^- v0 M                                  LPDIRECT3D9 pDx9,4 I% D$ n" Y  a* v# z0 U
                                  UINT Adapter,
; S! ~' l& g6 D* a+ L; w' E                                  D3DDEVTYPE DeviceType,# V* A$ g2 c# E* Z6 D
                                  HWND hFocusWindow,
, N! X# g% X) }/ s* c                                  DWORD BehaviorFlags,
, i/ h& O  V4 s& r/ X                                  D3DPRESENT_PARAMETERS * pPresentationParameters,6 I6 \( ~3 x& H4 F3 N% v4 G
                                  IDirect3DDevice9 ** ppReturnedDeviceInterface
5 [" r' X+ Q9 H( r1 }; v6 n" {
5 n& |0 J$ {0 ^$ }- E% E; d9 p& i                                  );
2 ^& P1 ^8 o( I3 f+ U) I& T
. G3 q1 B* z3 v其中的LPDIRECT3D9 pDx9就是this指针.
+ v2 a% R9 r2 Y1 c
3 F9 e. L! c* f( _hookedDirect3DCreate9的关键代码如下:" O6 Q: Z" ]! u; G, Q
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
. }/ D0 M9 t# |8 k5 S8 }) v4 h4 _; @
        DWORD oldpro=0;
6 }; ~+ r7 l9 F' O3 {  ?        memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节+ O% n' v$ ^% G- H
        VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
, B( N4 z. N8 U        *(BYTE*)pCdev=0xe9;
. ?, h% ?9 t. a3 v        *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;7 g- P5 v- h: }: Y! {
' S1 n9 b# M. F* u
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:: [; |" N& k0 o& ~! g5 d. R
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
: q2 y0 n" {1 _, m- g5 O8 c9 e        memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节! z$ r$ H* m# S3 M& Y
        DWORD oldpro=0;
5 r9 d. F9 ~8 f) w/ G6 e) u& A        VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);4 T9 F# v4 @! {: z. P
        *(BYTE*)pPre=0xe9;
: }2 k  w) i3 h! F; H        *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
  }  n) D: w+ l$ w+ N* ^
5 a( Y* g/ ?6 R实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
4 V( {+ f3 j4 x+ ichar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";4 N, {! K3 k: M6 Q; j$ R! u
            DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
+ n1 M) A+ h- d8 _) A. l            //在这里写入您的其它绘图代码
  j- |* K# a; e8 O4 r3 y
) T/ C4 @1 T$ K9 f2 Q, E9 G4 ]) i) w& L
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
! |* |- L9 E) }0 F; ]" f4 SBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)3 k9 H, {" M2 e$ o0 I
{
( Z  R' T4 _% ]- B& o9 }
  `8 K' W0 u7 X) E    if(m_pD3D && pDxdevice){
  s7 d4 }0 ?& r2 w; v
5 U6 }% t& R% }6 b        RECT myrect;' k' M5 {2 M4 I  \9 c3 E
        myrect.top=150;  //文本块的y坐标
+ J" ~8 K5 g$ `5 e& `3 D3 F        myrect.left=0; //文本块的左坐标3 g+ @& b7 b# G4 P# k5 R
        myrect.right=500+myrect.left;! N0 w3 u- T) d2 H
        myrect.bottom=100+myrect.top;
$ [2 p/ t' V5 k, @: f. E( \( U( k        pDxdevice->BeginScene();//开始绘制
1 a# Z# k8 n" `
. k* N, b, i3 ~        D3DXFONT_DESCA lf;8 x- B: U8 _$ K+ E% ?8 Z. O
        ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
/ x- P3 W+ U" f) `: J  S( ^2 u1 ]0 c        lf.Height = 24; //字体高度! t* P' H$ W- Z* e) }- [
        lf.Width = 12; // 字体宽度
. I. O! ]. m; `7 i        lf.Weight = 100; 9 ~/ g$ ]9 e0 `
        lf.Italic = false;
2 Q9 {) d7 R0 |5 L' @  v- G* ~        lf.CharSet = DEFAULT_CHARSET;
% F2 w4 N) c, M# g* X7 m        strcpy(lf.FaceName, "Times New Roman"); // 字型+ e$ u. s7 j% |
        ID3DXFont* font=NULL;
$ j) f) s  X7 H; ~+ E: i        if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
/ d7 Y$ R. _+ z5 y% h. Z5 g; H& ~            return false;$ L4 F2 X; y1 X  \8 y- ^
/ L) W! w5 f6 {2 @& S+ J+ @  S
        font->DrawText(
8 I& R0 ?( `/ [/ Q0 m7 x' B" t            NULL,
& T; V( a! i0 I, V, n            strText, // 要绘制的文本
- W" V) h* [  u* G            nbuf,
) k% r0 f, E! ]% E            &myrect,
* Y* C# w4 C1 v, R" Y            DT_TOP | DT_LEFT, // 字符居左显示
* A- _, D) a7 m/ \& ^2 ]/ w" O: \            D3DCOLOR_ARGB(255,255,255,0));
- n$ k) R& j# a4 T
+ S. E# C% v/ c; |        pDxdevice->EndScene();//结束绘制9 C. E* L- n6 s5 I7 Z1 i
        font->Release();//释放对象
, j  [( T4 z6 d/ W4 v9 }' S    }
6 \' w( k' B# P+ g% H, V) q) [    return true;
1 n- Q% q" X& F}
) Y$ d9 O9 C- t+ [4 V
' w' p# j+ `# |. `3 a2 e% N源代码:5 G5 T" v3 x2 {- `" X, u
HookDx.rar
+ W$ {# M8 {0 Z0 H, d
; L0 d9 f! Z9 |* z) `后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2