在看雪找来的文章,和代理DLL的原理基本是一样的。9 Y. h' S9 v2 ^+ \1 l
7 R: z& u; }+ i0 D原文
2 C; U- x4 r& D) k5 jhttp://bbs.pediy.com/showthread.php?t=85368: Q- s3 U8 o( K- Y# S
& b9 P* @$ R; n这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
, T W; }1 \, [ v5 X+ c/ W$ ~其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了., `( _* y# C3 S* p
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
" n+ L5 ` B# \& x3 W' K! g首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.4 _* _. M9 w: y5 N, |! ?* n2 ]) ^
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址: v( Z+ t2 c7 W9 n2 q
DWORD oldpro=0;
; ~7 U6 a8 E2 i5 L# ^ memcpy(d3dcen5bytes,pC,5);7 D" j, S" \' ~6 z; N7 ?/ o4 ~
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
) ~& d( t0 C1 B( y *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
4 X* u, t; R2 f K" ^% t/ L2 ]) { *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
6 O- o4 C: m, B# E/ y3 l
8 t' y. e5 z: O4 d1 Q/ t b- e- i$ k$ D& ~) e
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
! ^6 @, x- l5 i9 FHRESULT CreateDevice(& h7 }# [4 u+ y" A Z3 i) I
UINT Adapter,5 E( X$ [/ ^4 L4 b- W) n% d
D3DDEVTYPE DeviceType,
% q7 W' e2 |8 U6 }2 t. m9 S HWND hFocusWindow,
6 X0 _& M4 c. [- {. b7 B2 }! x; K* Q DWORD BehaviorFlags,4 N0 I. H* O/ a( k ^" S
D3DPRESENT_PARAMETERS * pPresentationParameters,/ q9 |" D4 ~! {/ h& ?" @; ], l. `
IDirect3DDevice9 ** ppReturnedDeviceInterface
+ F) J5 p6 \0 [3 c+ H$ ~* h);% X- f. A: Q; v
& S2 [" d+ p& f5 R8 q/ b6 e) B# d4 Y9 {
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
7 x& `4 [& b0 ]6 p8 P* uHRESULT _stdcall hookedCreateDevice(9 m) q# L2 }% `+ V( v
LPDIRECT3D9 pDx9,- y8 e. Z) g) s6 L3 g' Q0 F2 ~
UINT Adapter,0 o3 C0 R! y( v7 i- o/ b1 o
D3DDEVTYPE DeviceType," F' `; @6 f, q( f3 @
HWND hFocusWindow,
9 {7 \1 H$ Z! Q5 ^ DWORD BehaviorFlags,
g# M$ k m' l3 k' ^# \' f- b' ~) d D3DPRESENT_PARAMETERS * pPresentationParameters,
, {) N, A8 C V+ H1 s! _ IDirect3DDevice9 ** ppReturnedDeviceInterface+ h7 ]3 O8 Y8 v6 T
; O6 A+ ~4 c/ e4 d( d );
8 t' u5 q; l7 ~
9 S7 i- ?7 m: r, F* t其中的LPDIRECT3D9 pDx9就是this指针.5 o% F5 |( R! u; `8 n! z+ V
6 f* H, X% @; p% T$ h/ @
hookedDirect3DCreate9的关键代码如下:6 a( p. J$ L5 G2 r, {6 w; G
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
' f5 ?. }. ?% q6 T! H2 x0 i, Z( T: t
9 W+ @2 L& H8 a4 `5 [" ~- J DWORD oldpro=0;2 s3 G7 W+ d. F3 {5 w
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
. Q( b$ x7 C3 {& e/ X VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);2 T& }1 {6 O$ @. X5 G" L; ^+ x
*(BYTE*)pCdev=0xe9;
) H: }! ]8 {* j7 w/ k4 [5 l *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
$ c2 R0 a v% [# ]3 p/ f4 f# H
' H2 Z" @. I6 |# }9 D在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
# {* F7 ]% o) p/ g, [' m) b' ?* N! T9 I$ cpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针9 O. ?! R/ v, l0 _0 }4 _4 z
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
" M6 b L4 d( I4 K# [ DWORD oldpro=0;
7 t t; j9 d+ s+ V. k VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
- Y6 _( D5 k* i* r) { *(BYTE*)pPre=0xe9;
4 n; g! \* W* g9 a *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5; C+ ^9 U1 f. r) a/ f7 }4 i9 x% [
% l# `6 j T$ u$ t$ c2 d9 j( c
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
1 o+ s1 I( Z% W3 O- Q/ s# \char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";; S& w0 R) k* f& _2 R# x. N3 }
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
& z Z* Q' Q! N& z //在这里写入您的其它绘图代码
# b& y7 ^9 g. c% ^ p! e4 V( q
3 m E% g" P+ J* F5 Y$ S Z* N- f) b, \# C/ v3 N; E/ B
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:) w ~' }5 a+ ?; \
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
/ j0 u# v. s7 Y' |( p{
8 d8 m% q3 c7 q/ V, {. T$ Z* J; z( Y# Y( R( |: p
if(m_pD3D && pDxdevice){: d. @5 B# K+ o: J' k+ n
4 m5 u- n& G. J2 Z RECT myrect;9 j+ {3 K. Z9 X( d) E
myrect.top=150; //文本块的y坐标
c4 G9 H9 [6 t7 W7 K2 l7 S7 e" p myrect.left=0; //文本块的左坐标- _$ [4 `7 H0 j7 t- p" j
myrect.right=500+myrect.left;( {) I) U- {' r* \: R
myrect.bottom=100+myrect.top;& S0 X9 @' p! U+ E. \+ `; T
pDxdevice->BeginScene();//开始绘制
9 d2 b& ~- ^% k* C
* P% ?; |" ~; L% h D3DXFONT_DESCA lf;
. K& q% h- G0 v4 q: ? ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
8 ~; F2 E. F) p" {/ t; Y2 k lf.Height = 24; //字体高度
; x/ ^$ u. ^1 s* T& @ lf.Width = 12; // 字体宽度' d- R0 E3 L" q& M' ]) Q$ e
lf.Weight = 100; ( ?& X* e. E6 }3 N
lf.Italic = false;
v3 {$ K) d% i/ Z+ c! X+ L: l lf.CharSet = DEFAULT_CHARSET;
3 _$ l3 Y/ }6 b. I strcpy(lf.FaceName, "Times New Roman"); // 字型
4 C! a+ Y! F1 E ID3DXFont* font=NULL;! d# y- q7 ^9 u2 ]4 _
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
" I# }' s0 ?7 U; x1 M3 h5 o return false;
" x( r* w! Q3 M8 U" ]& U$ E3 M" @2 c' ?; o6 z
font->DrawText(6 Q6 n2 R8 a+ l0 U9 B( w2 I( R" P
NULL,
0 T j; I) ^4 J! y- Y2 \8 A- z strText, // 要绘制的文本" L* M( n% m' {! h6 i4 t& u1 F
nbuf,
9 V; s3 w) c0 V) n% `5 y( K" Q &myrect,
! @5 o- g e. n% t3 _2 V7 }2 g DT_TOP | DT_LEFT, // 字符居左显示/ j5 [( i/ X) g: D; f+ r
D3DCOLOR_ARGB(255,255,255,0)); 8 ^" u& {- ^3 @
/ S5 S2 D- U9 x2 M/ ^0 O. j6 A5 `
pDxdevice->EndScene();//结束绘制
) ]* q& y3 v& |$ Q5 Y font->Release();//释放对象
( K9 n6 v) ]1 z( Y5 ` }
* K/ a1 \6 C; q0 S return true;% a6 Z0 d4 q# Y L0 ~5 ^" L
}
5 `6 E6 _2 T0 N+ d: q' c4 G5 S! ]* Q, E- f$ O7 j& R! Y6 C1 h5 c) a
源代码:
8 i" J1 D: e5 c6 bHookDx.rar
/ l6 t3 c" y% t- }- i- P: g7 E- V! l9 y/ ?' i9 F
后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险. |