冒险解谜游戏中文网 ChinaAVG

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

作者: shane007    时间: 2009-8-28 21:13
标题: 【代理DLL汉化研究】Hook Directx:在游戏中显示自己的文字和图形的方法
在看雪找来的文章,和代理DLL的原理基本是一样的。: o* [2 J4 u% n+ v
, X, B- @8 t4 y
原文
( C2 x7 o# @$ Y8 P( z; {/ d7 chttp://bbs.pediy.com/showthread.php?t=85368& `9 m' {( G: m: U: T! C
& e' A: y9 A2 [* [
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
$ U4 t. O3 y# u0 V. G其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.6 y$ }( {' s! }. D! Y
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
# t# U: @( O2 G  l首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
) a8 n  q0 S0 h' g" i' u    pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
/ m2 z; I' M" \8 I) Y    DWORD oldpro=0;- W/ |, K- j' ^$ K( R1 b" p9 D* f
    memcpy(d3dcen5bytes,pC,5);6 Q( ?/ ?0 L8 h' S2 {/ D
    VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);6 i+ T) Z' R/ y& R8 @& d3 Z4 b
    *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码8 u( V! M* k$ L2 U6 `2 B) b; S
    *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5( t- i; y: c8 z0 G' l' {  E
: ]# e. G% @7 g( _- n3 V, B8 Q
/ t$ e7 V7 O/ Y( ]' E& j
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
( h7 ], K4 v, S: @) }9 g' }HRESULT CreateDevice(
) v2 v7 H/ E7 D- g# d  UINT Adapter,
- [+ l: @  u$ O" `8 s, z. M5 `  D3DDEVTYPE DeviceType,0 ~. Q8 n# {8 p: h# @, m6 |
  HWND hFocusWindow,# c' s0 H1 F4 N! @; D! J
  DWORD BehaviorFlags,
8 w7 Z5 b3 A5 e4 g7 x! o  D3DPRESENT_PARAMETERS * pPresentationParameters,; j' W9 `2 O% Q
  IDirect3DDevice9 ** ppReturnedDeviceInterface
% q; k$ |9 _# C" J);% _, M8 f0 i  l. c
/ l1 B- s0 O2 l/ ?; Y& P# w

, J/ _, ]* Z$ T+ {( V" L; M而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:- @) `, y+ h$ m
HRESULT _stdcall hookedCreateDevice(
4 I7 T, \# B3 H& c1 l, Y                                  LPDIRECT3D9 pDx9,
  P# W2 j1 i3 b1 W7 Q                                  UINT Adapter,
# Z7 q! N: Z) M                                  D3DDEVTYPE DeviceType,9 ^" f  Q8 J( T+ a$ O7 [
                                  HWND hFocusWindow,
$ {+ w- H7 ~7 ?, n/ ^' Q" V                                  DWORD BehaviorFlags,. L2 ~* F  o* U8 R6 b4 M- {
                                  D3DPRESENT_PARAMETERS * pPresentationParameters,
% `& m  O2 }& `& K                                  IDirect3DDevice9 ** ppReturnedDeviceInterface
" z# S4 v+ [2 L% T' D: O* c5 l" ]: r" ~4 ?. m& Y
                                  );/ u4 l# B0 j4 g4 }4 ?; Y

  T2 r( Y* {; X8 c& K1 W其中的LPDIRECT3D9 pDx9就是this指针.
: S0 O7 f) e7 z* e, A: D* A; b) d- m6 n3 N' K$ |3 C
hookedDirect3DCreate9的关键代码如下:
9 x5 C- E" n1 K' w* G, \pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
: }0 i9 @5 T4 z1 c1 H9 O% t' x. ~0 [9 q' V3 D- X
        DWORD oldpro=0;
; W1 k5 C/ G  m' A        memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节: ]( D6 l! T+ V, m+ ]
        VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);" L% e8 L: n* j1 v
        *(BYTE*)pCdev=0xe9;
; W/ `* y6 }' A& g        *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
: K& i1 R7 X( p+ h
' Y  x+ e" P- Q% M' r( Y5 K在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:" Q3 d9 A7 |% L" E% S
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针* b  m* v+ F; |8 U
        memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
4 G& \9 }. C* m" C( @- X        DWORD oldpro=0;
, P* [) R( S2 ^* A) F        VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
; \) d: H. l, G0 X  E        *(BYTE*)pPre=0xe9;% _( R' L3 h8 P& B, e- c
        *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
' b+ l, X( |9 k1 M. t2 y
. e2 d4 C' n, b; o( e/ M/ H/ H实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:8 C8 I% [1 o0 X  X( e2 {( t% D; C
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
% @2 b" x4 T4 B/ g! @7 v$ k            DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本. w: P* q+ R* e
            //在这里写入您的其它绘图代码1 H$ U$ C2 ^6 a: E1 z8 n2 b

( e5 r, h) c( G; O4 q5 A0 _6 ^0 k7 n3 a: M' `) Y
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:% {+ J' H: {5 w* f
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
1 N" O7 D7 b& }0 |6 z{. ?3 A( x: h# g! q

+ ?7 U: u8 Y! J    if(m_pD3D && pDxdevice){
5 k' U% P" v0 q6 c$ A  A8 ]5 p" a  D" X: _# f* B, A2 i! I
        RECT myrect;3 ]4 P& `4 |- P' L! w/ w
        myrect.top=150;  //文本块的y坐标
  ]* M7 I: h  {- j        myrect.left=0; //文本块的左坐标
+ `! N& x1 q! ^& }        myrect.right=500+myrect.left;
: J; R8 ]% U4 n        myrect.bottom=100+myrect.top;3 v! H6 v7 C" A% a: K. K- k( N( w
        pDxdevice->BeginScene();//开始绘制
0 u$ R. W) t. C! Q  y) Q+ B" N
        D3DXFONT_DESCA lf;' |2 p2 _9 R1 x% ?9 N- A' E
        ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
4 g$ c: P& i' h7 @5 A  c% Z        lf.Height = 24; //字体高度
. z/ O2 V) o) H6 I        lf.Width = 12; // 字体宽度7 g$ F$ v& f$ u# d1 V# w* A
        lf.Weight = 100; ; D6 u  D3 W: z( I1 u! u; e
        lf.Italic = false;5 D- \- t+ u; I' m2 {' }
        lf.CharSet = DEFAULT_CHARSET;, ~  _" i8 c5 \( ?2 f
        strcpy(lf.FaceName, "Times New Roman"); // 字型/ Q$ T' t+ Q4 O& _/ h- R( S
        ID3DXFont* font=NULL;
* I9 {6 V* R9 ~* A6 O* Q: z        if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象# @3 N' I" F; y# E9 u
            return false;4 y  p6 o5 Z! |" M8 {
! g, j" z' h5 }+ T: u  r
        font->DrawText(7 I& C# T  u5 w3 x6 e
            NULL,
+ m! O; W3 a, n0 S            strText, // 要绘制的文本
$ j% r6 v" m/ v5 T            nbuf,
/ G% ^0 Z7 T6 B* E  P            &myrect,
2 a' B- h! X' A            DT_TOP | DT_LEFT, // 字符居左显示7 n7 u) F+ p5 D& M9 {: T
            D3DCOLOR_ARGB(255,255,255,0));
) X. Z+ v) E* V" H2 G5 h1 }# \! `" D/ |+ V- {
        pDxdevice->EndScene();//结束绘制
2 j; D( v1 _; o; z, {: a# H' Y        font->Release();//释放对象5 J" ]- y0 ~1 i% d$ u" e( E
    }$ h$ k1 A- [. b) o% h. D- M
    return true;8 [6 r' ?$ k. c( ^$ X) C) {
}: Q8 w- L  |% r1 \5 d- P
: L# `  d8 Q: h; N/ C3 P! f
源代码:/ k0 T% F+ X0 s# ?$ `, F4 t
HookDx.rar
+ V7 t$ T- f9 l6 ]
- v6 ^7 o6 w) H! s- e) [& q: E/ v后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.




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