冒险解谜游戏中文网 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' zhttp://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 DHRESULT _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 L1 D7 n2 m# {- E, g
其中的LPDIRECT3D9 pDx9就是this指针.
/ |+ c$ f- H2 o8 X( H# z3 ]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 d7 |! 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/ N5 @* |1 w) O6 U
源代码:% m7 h! V  o/ S& U5 f1 ~5 e: y
HookDx.rar7 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