在看雪找来的文章,和代理DLL的原理基本是一样的。
7 D/ a5 v2 t+ i) Q
+ K: O8 y5 x" f- f, p原文4 C$ N. @ U3 ]+ J2 O. H
http://bbs.pediy.com/showthread.php?t=85368
5 l' D% D2 a% C8 A6 h
, A2 h# @, ?8 ?9 ^- c这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
1 E1 ?) ^& `# _# r. F' _3 u' |- O其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
; n7 |* X2 d+ M- u1 D# C! S* N还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.+ k& M* D# i3 \9 t7 z
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
0 d3 A9 s! t' m; c1 w+ d+ j2 K: x! A ^9 i pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
7 s, j2 x2 m3 g5 _: @ DWORD oldpro=0;1 Q( h; P2 S5 |
memcpy(d3dcen5bytes,pC,5);
- H0 k- M6 A, ?' w VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);: D! ~3 G0 b/ I+ S8 U2 \
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
3 M/ H3 z# ^- T) y4 @) D *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5# S+ k' {- ?0 G7 v
/ Q- z/ x0 ?2 i* V& h( e/ G
8 `" Q1 _- E' d3 L这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:1 ]0 k0 R1 ]" r
HRESULT CreateDevice(
, d' F. B/ h3 }( Y! g* G UINT Adapter,) B$ ?8 R# Q1 l) [. o
D3DDEVTYPE DeviceType,
0 A0 j* V. F: O, F7 Q+ Z2 Z/ K HWND hFocusWindow,
: r! K& _& ]( t; T4 P# h. t4 k DWORD BehaviorFlags,
; s) L! m" u9 | N \7 R5 U7 I D3DPRESENT_PARAMETERS * pPresentationParameters,& i8 W7 e4 n) e# u5 W' f2 |
IDirect3DDevice9 ** ppReturnedDeviceInterface
1 k( O- L3 J8 X4 K& G2 {);- X4 ]9 h) {5 O) {
! h; \, ?0 Z! e* Z8 c
$ a! o# M5 X, y( X& \
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
; C$ F {" y3 W* I- AHRESULT _stdcall hookedCreateDevice(6 r2 Z( \0 b+ m$ ^3 v: g3 z
LPDIRECT3D9 pDx9,
+ H6 m/ o: ]7 t4 }+ {& k UINT Adapter,
! P9 A: I& W$ L& n8 q/ f D3DDEVTYPE DeviceType,
/ k. |6 G* K8 r$ e8 j HWND hFocusWindow,: D9 V; V6 c: `* z# [9 y
DWORD BehaviorFlags,
2 ], N( c, S1 `6 ^% h) s+ _ D3DPRESENT_PARAMETERS * pPresentationParameters,( K @) ^+ f) g" G+ e: |% _- Q4 O
IDirect3DDevice9 ** ppReturnedDeviceInterface4 D5 V3 Q) e! e' d$ f2 }" T$ w4 G% v
- |) P& T- x' |8 H3 H& @; ?+ b3 l( t );; K! v2 R6 @8 j L. C) k) J1 s
# M2 B8 O" E# u1 T其中的LPDIRECT3D9 pDx9就是this指针.2 |: ?0 u3 P% b( E! e, H
) x& o* \0 K3 g* \. j
hookedDirect3DCreate9的关键代码如下:
5 T- |' g1 o7 BpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针) `4 J! n8 v3 ~5 H' J! _0 I
+ V$ n6 u! s( a) \
DWORD oldpro=0;( B$ _8 v- x j
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
$ @' O3 J- @, i0 Z9 z: r VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
1 C0 f' K, @6 N, g1 a *(BYTE*)pCdev=0xe9;
- N1 p2 S: l+ l* o: D2 }3 | *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;/ o6 O) N" B) Y) ~- V! a2 e A9 D
! M8 y* l/ j1 x' B; {在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:3 o8 E5 d5 i6 f4 H
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
9 A, \/ X# D3 T3 { f! {+ v- t memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
- R) g4 M$ Y$ G4 ~ DWORD oldpro=0;% u7 g1 T& p w% R
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);$ k- @8 k6 L/ V# L- N, u
*(BYTE*)pPre=0xe9;
1 m. @! ~2 W! k, i' [$ \# l *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;8 C/ T) e4 b5 j" f d! a
7 p8 z/ [+ G# _
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
7 n: H3 t" h( J* T4 Xchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";+ a% n; v7 y$ s3 O1 d
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
* G9 W: E( _( P g, q& p& b9 w //在这里写入您的其它绘图代码3 m4 R2 [$ G3 y5 G1 |- R
; ?, ?( S3 E2 G% s9 u
3 E7 I$ |. `$ i4 _7 C( i1 B$ Y5 @再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
% m g$ v: n* |) Y/ Q* BBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)2 r |8 v& j7 C& X$ R
{
8 N2 R. o3 I7 `& a2 t
r4 Z) t& M: S, p if(m_pD3D && pDxdevice){
! ] R" m0 {+ \0 }/ G
9 Y' z6 u" M: v; w RECT myrect;
* e* m8 X2 n) _# o/ v3 t myrect.top=150; //文本块的y坐标
! W+ p' A, e# B1 x7 a myrect.left=0; //文本块的左坐标3 ^: {) R. T" x) R8 J+ E1 W" [
myrect.right=500+myrect.left;
$ n& \& l- u7 f8 K myrect.bottom=100+myrect.top;7 B0 y2 ~6 e' e+ z1 |
pDxdevice->BeginScene();//开始绘制
. q) X7 u2 o8 U) b
6 ?* k0 M1 m. H# v D3DXFONT_DESCA lf;( p) f9 s0 n2 f `* q/ a; Y
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));/ U/ @+ c7 q2 t. i/ n& M' d
lf.Height = 24; //字体高度
( V! A7 |3 P2 o: y) H lf.Width = 12; // 字体宽度& T6 g& V( i4 w/ w6 F4 Q
lf.Weight = 100;
$ @5 b0 q( ~4 V/ `+ ^ lf.Italic = false;/ N% T6 A+ K4 F0 `2 X( H
lf.CharSet = DEFAULT_CHARSET;
8 w# [7 n1 Z0 _1 R, H strcpy(lf.FaceName, "Times New Roman"); // 字型
# d1 Y9 T$ E1 B/ ~5 O) F ID3DXFont* font=NULL;5 O- ?3 r, e1 P& J+ a+ v9 `
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象) g u% w5 u, M0 A; q
return false;
G1 |. C. I3 R8 F! q: q
2 M1 S/ J* |- w" f; L font->DrawText(4 @2 U5 U M& ] F
NULL,
& j& B) W) z7 n9 y0 ? b& s strText, // 要绘制的文本
2 _$ o9 M" |# t" [ nbuf,
7 I3 D/ e1 c. p$ M. f &myrect,
4 K0 G9 D2 Z) O$ ^2 { DT_TOP | DT_LEFT, // 字符居左显示
7 A( j7 u% q% ]% R8 r D3DCOLOR_ARGB(255,255,255,0)); 8 ^/ s" p! P% O) i+ _
- c: O# W H* c3 n
pDxdevice->EndScene();//结束绘制$ V0 x f6 \; C' d: `
font->Release();//释放对象
/ z# i- J! b) b }; r. H/ X; d# q) M; V: o
return true;( _! D, c/ T4 v7 T/ ^/ _4 |0 U
}+ X7 \! Q; D$ v* L p' `% [/ a
1 j- s- D# l7 Q3 n0 ?. O源代码: i7 A0 X+ H+ A
HookDx.rar0 P# ?, N8 P7 E0 O' e8 ?
9 S2 ~0 Z/ }! F2 b3 N, M' `/ B8 |9 t后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险. |