在看雪找来的文章,和代理DLL的原理基本是一样的。
; v9 {/ u9 h8 @' {
/ y- H# ]$ S2 a; v) y6 {! C- i原文
\" p4 z5 K4 g& H' K& f+ d# ghttp://bbs.pediy.com/showthread.php?t=85368 k! G4 y, p% f8 i; F: L
$ N! T1 {4 q( R2 b- O
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.. k, L" k( A6 `
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了. a, X! @9 d3 P" c7 m
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.2 o9 f) X4 X3 [! Q
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.0 u( H. w; a' O: d* [
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址5 V& C' [7 {$ c7 {+ u' T
DWORD oldpro=0;
7 x: v$ Y1 p" S memcpy(d3dcen5bytes,pC,5);/ R4 y1 @+ k6 [$ q
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);3 q D/ |1 z9 h9 R3 m
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码$ w+ o" y6 x! P% K% y$ p( m7 i* o
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
) J) V( \$ ^2 `" i& R
: w/ F( \! H2 p+ H
4 K8 ^1 I+ f5 A8 W2 f2 q4 Q这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
) h {6 {) f) [( A3 J7 _HRESULT CreateDevice(
& g/ x8 d) h- C3 B+ Y; f: s UINT Adapter,
7 q C* G" P+ o- p& u D3DDEVTYPE DeviceType,
5 B& U9 U3 i; `3 a" N. R/ c! z* `( A HWND hFocusWindow,
9 l% o& Z6 ^' |1 @: Y m DWORD BehaviorFlags,7 r, m& |$ i" {, V+ h
D3DPRESENT_PARAMETERS * pPresentationParameters,
2 [, B$ s% C. P ]* k IDirect3DDevice9 ** ppReturnedDeviceInterface0 \" u2 H9 P! f# C
);
3 ^1 y( J7 W! P5 E$ t+ [. A& m" I# ]( F6 P: m5 i! N. q0 z
A. s, z; Y" ]/ T" r8 x5 p# e
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
) \8 D0 t# F3 @0 lHRESULT _stdcall hookedCreateDevice(
' |8 M7 o5 h8 ?* A! Z8 N LPDIRECT3D9 pDx9,
1 V4 J( d% D; B+ f% l UINT Adapter,- X8 z% |+ F8 n0 b
D3DDEVTYPE DeviceType,
& N2 {0 `% Y% x. f7 c HWND hFocusWindow,4 R, I9 g: I3 `
DWORD BehaviorFlags,
9 t, ^9 t" Y0 E% _# X D3DPRESENT_PARAMETERS * pPresentationParameters,
; y. T) Z, n( ?* t# A IDirect3DDevice9 ** ppReturnedDeviceInterface
" a# c1 T/ Q: H; ~' p2 O3 P n$ K8 _3 w! B% [
);
3 x8 q; I6 |0 C1 ^% ]; X$ e- W8 @* J1 s( v% W
其中的LPDIRECT3D9 pDx9就是this指针.! K; Z4 [5 z# i3 q1 U
: `. v C7 D7 f7 j; S) uhookedDirect3DCreate9的关键代码如下:
% n7 R$ ~1 N6 S+ ?7 UpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
. Q4 A, A7 \6 {- H0 V& I6 o2 }( n! g B+ h# I; k! m
DWORD oldpro=0;, l+ f7 r- p# W: b
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节2 P" E8 ]2 |( U$ `
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);: o* L. p' \ w4 `" o6 x
*(BYTE*)pCdev=0xe9;' ], M1 e4 p% [/ H" J
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
# _* L+ P! I) L
- o* H; X- x( ?2 T" d2 _/ d5 c' S3 `在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:/ W" ~0 M8 d% d4 J2 j& ~0 D3 X* r3 \
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
% w1 ~+ B. L+ I) K6 z9 s9 w8 c memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节% q. x$ [; C v' H
DWORD oldpro=0;
0 R# |( \: A# O8 g" J: C VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
6 I6 ?# x) g7 H( r# D *(BYTE*)pPre=0xe9;
) D4 Z' I8 b( P$ R *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;' N% V' E- ^7 ?2 `
1 ?& J9 G2 x7 g8 k- {- u z实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
9 }$ Y6 r3 O2 d# Q; o* P) Tchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";' G1 B9 }9 S5 C' g( Y# J, Q6 x
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本. N0 K; P. Y- k9 v1 l6 t: m
//在这里写入您的其它绘图代码# x* u! z% `3 @7 t% g2 z- E! {* Q
+ P u! a5 r: f" R
7 f, Q$ V! M l, Z9 v
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:& X4 W& i: A' e
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
9 ~6 |$ L2 n: T- y2 ~{0 D2 A5 l7 X) ?" R
( w S( J- Z. X$ H# m0 O
if(m_pD3D && pDxdevice){
* W7 v+ H# a5 K0 C6 `
6 ] J* e7 b$ R( [2 s RECT myrect;/ y E" [3 C, o8 \+ J
myrect.top=150; //文本块的y坐标( q1 {: O- o# u* `# A5 l
myrect.left=0; //文本块的左坐标
& \7 x& Q9 \% Z" I* b+ A; \- n myrect.right=500+myrect.left;9 R/ V1 i r# G, U+ _2 B0 G p
myrect.bottom=100+myrect.top;
$ w0 X" T, b# r6 G9 H, }. ` pDxdevice->BeginScene();//开始绘制4 E+ y: v( N. \
/ T/ G. t; F5 F$ Q
D3DXFONT_DESCA lf;
3 |, n8 b& q0 g: E ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
* S" I$ H. ~3 Z( T% o lf.Height = 24; //字体高度
2 P; l3 r$ }1 x5 {0 R3 C7 p N lf.Width = 12; // 字体宽度
- c# y4 b+ J: u" d lf.Weight = 100; 2 N: o7 f6 q$ P; q$ l- G) \5 C3 V
lf.Italic = false;. d x. _! n ?; x4 q* c1 V
lf.CharSet = DEFAULT_CHARSET; x9 {8 T5 E* B# G5 h4 M( V
strcpy(lf.FaceName, "Times New Roman"); // 字型
) Y# I1 a2 y& I& v. d+ f% ] ID3DXFont* font=NULL;% i& `* N7 N/ [6 r% R
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象( z( z# V6 g; ~4 C r; p
return false;7 {) p! C; |9 {- Q
3 R5 }; p' n) p4 D3 a! y$ F' q0 V font->DrawText(
, N! q6 m; \2 L/ @ NULL,- f# `' G% z; l5 P/ y
strText, // 要绘制的文本
4 b! L6 _1 d% ?. l+ P+ v: X nbuf, ; M: [; ?4 R' h
&myrect,
6 M, @8 G8 {! o7 A DT_TOP | DT_LEFT, // 字符居左显示0 h0 y$ K2 ^6 c9 L
D3DCOLOR_ARGB(255,255,255,0));
0 `" |; Y' c+ t `
5 K. H; l1 ^7 u pDxdevice->EndScene();//结束绘制
+ W( o7 g r2 K font->Release();//释放对象( ~0 t& N$ X! J5 p0 o& p0 ]' C; ]
}
/ L1 F8 x8 z- r5 Z& s return true;# D7 |9 u8 q& Q: o: j9 y
}
& ~4 S5 H, z$ f
8 J1 ]6 i/ A) Q1 q源代码:
- L$ O; E% D& cHookDx.rar
# H+ B- D+ ~: t2 K! g9 W
+ w8 l2 ]- Y+ @3 p f后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险. |