写得很不错的文章,国人用英语写的,还有代码。8 r/ P1 }: N6 r1 |) O. x
Hooking Direct3d
" Z- {$ \" o, @3 ^/ bBy Jijun Wang2 o2 I- J1 X! G( Z# M/ [* P
Introduction
6 D s+ Q# r( E6 P8 i4 t1 P& k+ ?& fIn the field of hooking technologies, hooking COM based applications is a big challenging because you can’t know their member functions’ real addresses previously. There are many papers on the Internet that talk about how to hook (somebody call it hijack) an application. However, seldom of them mention how to hook COM. Since I have seen some people asked questions about this topic and nobody answered their questions clearly, I decide to write this paper to share my experience with the guys who are interested in hooking COM.
" Z% k1 H1 `# o; }( V' ?" O2 P, H1 p- i) j! m7 t; Z
In this paper, I use direct3d, which is widely used in current games, as an example of COM. At first I will briefly introduce the hooking technologies and explain why I select Detours to hook direct3d. Then more details and examples of Detours will be presented and I will discus virtual functions and find the way to hook direct3d.
9 Q0 ?0 ^4 E. ]) @/ KHooking Technologies% p/ @' B* b& K3 P+ z+ X" |
The basic idea of hooking is injecting your code into a piece of code. When the target code executes your code will be invoked. To do this, you need to at first attach your code into the target process. And then inject your code into the target process’ code. The ways to attach your code into another thread or process include
# \- g8 F% c) z, v) a) U8 l: c1) Register your DLL to the registry table.
4 _' j& a# v; b0 ? ]6 X2 ?6 fThis method registers your DLL to the key: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs and the dll will be loaded when user32.dll initializes itself. It is a safe method. But it only works for the applications that use user32.dll. And it’s not convenient to activate/deactivate your DLL.
+ B3 {& I- x5 h( ?; o: p2) System-wide Windows Hooks) }3 I; b( m- ~- R, |. K
This method use SetWindowsHookEx(),CallNextHookEx() and UnhookWindowsHookEx() winapis to monitor the system’s message traffic and attach your code into the processes that fit the hook filter. Since it is system-wide message monitor, the system’s performance will be significantly affected. . c+ T/ Z; w- Y
3) Create Remote Thread9 E+ U# z+ o8 k
This method use CreateRemoteThread()to inject your DLL into another thread (the to-be-hooked thread). It’s a very effective method. But there is one thing we need to consider. You must make sure you have enough privileges to access the to-be-hooked thread.
$ v1 ], j7 j4 X; ]2 o3 u, i
& m1 |8 J# X0 y" L) i H+ J# mTo inject your code into another process’ code, we could use:- E% C8 V/ y: {5 ^. ^8 G
1) Proxy DLL
: a9 Z a- N9 k( s# f- p. P* |This method writes a new DLL to replace the to-be-hooked DLL. The new DLL must have the same name, exported functions and variables as the old one. For example, the GLtrace is a replacement of the opengl32.dll. It is used to trace OpenGL. Although you could use function forward to reduce the time spent in rewriting functions, it’s a tedious work in some cases. 2 G( l b: M+ d1 O+ W
2) Altering of the IAT (Import Address Table)( q- M R6 z9 D2 ]
This is a wildly used hooking method. Windows use IAT to find the functions’ addresses. Through changing the data of IAT, you could use your own function to replace the to-be-hooked function. The method is robust and easy to implement. However, it only works for statically linked API calls. And sometimes you may miss API calls since you must patch every module in the target application.8 _6 d b. h! @$ A- A
3) Code Overwriting. H X% d# S2 |2 B$ h( _! S5 o( B
The basic idea is to overwrite the API’s binary code in memory. We could save and replace the first several codes of a function call to a JUMP code to jump to your codes and then go back to the original position and restore the saved codes. It’s very hard to implement. However, once you have mastered it, it’s really a good hooking method.0 b! N% G6 D5 X0 L: k5 J% R+ Z
' @$ m7 Y' z0 J7 f% e* KFor more details about hooking technologies you can read the wonderful paper “API hooking revealed” written by Ivo Ivanov and madshi’s discussion about hooking methods. There are some libraries or tools that wrap the hooking methods. "Detours" is a library developed by a Microsoft’s research term. It uses code overwriting technology. “madCodeHook” is a library built by madshi. It’s also based on code overwriting. “hookapi” is another tool can help you develop your hooking applications. Since Detours is free for research purpose and it’s stable and effective, I will use it to hook direct3d.
+ m) F4 o& w4 }; m5 X( \* f: R QDetours
: Z' X* c1 }# G“Detours” is originally built for change a standalone system to distributed system. It can inject your code into another win32 function. Some utilities that attach your codes to another process are also provided in this library. The typical case you may need Detours is that you need to modify an application’s behavior without knowing its source code. For more details please visit Detours’ web site: http://www.research.microsoft.com/sn/detours/
: y9 ?! ^: m/ X( @0 [; E: c5 j' p5 v# ~ K
As an example of how to use Detours, we will try to catch the function “SetTimer()”. Then change the elapse time to a big number. So for the applications use SetTimer(), you can steal time by hooking this function. The first step is to create TRAMPOLINE. It changes SetTimer()’s binary code and lets it jump to the Real_SetTimer() function.' i) c$ ~! ] R- G! ]- m8 V" K
% n0 A: M( M/ }) J
DETOUR_TRAMPOLINE( U% P/ h i7 Q4 x8 G X
UINT WINAPI Real_SetTimer(HWND hWnd, // handle of window for timer messages. i6 j# o; `7 v& A0 h
UINT nIDEvent, // timer identifier
5 i Q* P# L, `; j3 Q UINT uElapse, // time-out value
9 s1 m; H7 G. |0 x+ o( x$ E TIMERPROC lpTimerFunc), // address of timer procedure) x: z: ]8 V+ g+ m6 N+ z
SetTimer);# ] O3 t# _: Q9 C, E
! u% V7 S6 X8 U
Then we write our own SetTimer function.4 I3 E' p% A6 {
UINT WINAPI Mine_SetTimer(
( j; r9 {+ |' L) N HWND hWnd, // handle of window for timer messages$ T* k5 d, [; A
UINT nIDEvent, // timer identifier
7 N# F8 s+ i$ j UINT uElapse, // time-out value
8 g* b9 p; H+ q# N: f5 d! | TIMERPROC lpTimerFunc // address of timer procedure" N5 g* a& P0 L2 o4 r+ s8 [
)2 x- g- Y' ^: T; H0 @7 c2 O* t
{
6 q5 b4 {1 h6 R& ]' n+ W( w+ @6 G* u uElapse=10* uElapse;3 `. }6 F3 r( ]; U4 t V
return Real_SetTimer(hWnd,nIDEvent,uElapse,lpTimerFunc);
& q/ e2 A# O7 T1 }$ z3 A# W8 A}
% ~& q. U! I- e, w$ \1 u5 Y h* p1 m9 I: B: m. G
Then write the functions to intercept the Real_SetTimer() function to your own Mine_SetTimer() function and the function that removes the interception.+ }" P9 X/ ~+ {1 H; [3 l1 c
0 G3 Q& K/ x% v b
BOOL TrampolineWith(VOID)
2 D5 r2 m4 f: r0 F F7 q3 k{
2 [, O+ q" T6 L I. B0 f DetourFunctionWithTrampoline((PBYTE)Real_SetTimer,
' z. _7 F/ ]9 y4 L1 v( ?+ f# P (PBYTE)Mine_SetTimer);
$ u: @/ A2 f+ { O {% v6 U return TURE;
" ?- W: B3 |$ G3 M) D/ T* W H o}' _% P8 }% e- A" T0 b, Q
) W$ S6 C$ b) v
BOOL TrampolineRemove(VOID)
- y3 j* e( d' B8 ~( l s+ e{0 c$ N$ `. Y& W6 m) \* N
DetourRemove((PBYTE)Real_SetTimer,5 y5 [7 u, P; e) b
(PBYTE)Mine_SetTimer);
& E! x" d0 Z* Q) n2 q1 Q return TRUE;" y' [1 @ a& P% c' C# a. H, \
}% w+ G2 O8 R! ]' @7 L
# c, i1 G+ b3 {2 u* @; Y( }At last, we write the DLLmain function.+ D- d, G# h* L) P ~2 ?
/ Y2 g, K4 M) u6 PBOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, PVOID lpReserved); @0 l/ f7 e: y i7 U' t; K
{
; p# F. r: z& E; ` switch (dwReason) {9 S/ b! F8 T% J6 }
case DLL_PROCESS_ATTACH:
T7 x# c1 t( Z& J6 h; o: [ return TrampolineWith();8 |* |# I+ }# d
case DLL_PROCESS_DETACH:0 w9 K2 `2 N7 Q/ W8 Y9 v4 p* r
return TrampolineRemove();- ~% Z4 d9 B( T8 u+ R. j0 S' j
case DLL_THREAD_ATTACH:
$ A* b# h1 u5 k6 t return TrampolineWith();6 |- n, k. \6 s" T. Y
case DLL_THREAD_DETACH:
, C! |$ }) ~- k# v) K D" M+ v return TrampolineRemove();
n6 R$ f! q; O& R7 }: p7 O/ V }
* U- ?& ^8 ]9 D: Z- b; z; h' {" ~, Y( l return TRUE;
% x g# S9 O' a, u}
P' N: W }. F3 |8 S) m
% ]+ I4 w" S. V# q ]: A; @By including the “detours.h” file and linking the “detours.lib”, you can build this DLL (let’s say it is myTest.dll). Then you can write your own application to attach the DLL into another application, or use the “withdll.exe”, which is included in Detours 1.5’s samples, to attach it. If you place the DLL file, withdll.exe and winmine.exe in the same directory, then play the Mine game use command “withdll.exe –d:myTest.dll winmine.exe”, you will get a very high score.% u5 z2 g& P$ V1 B# a- F; r
5 `' t; P+ K5 t% o! P+ n8 K1 B5 ]This is the simplest case of using Detours. If you want to hook a member function of a class, you need to write your own class and your member function. You can find an example of hooking member function in Detours 1.5’s samples.
' E1 p% \7 ?$ {8 E' r# D. A5 V+ D
# V9 L8 P: C+ K; ]7 Z, j% Z% vTo hook direct3d, you may create a class, write your own member functions, for example CreateDevice(), Present(), and then detour direct3d’s member function to your own functions. Unfortunately this doesn’t work. The reason is that Directx is COM based, it uses virtual functions. When you detour the member functions, you will use the virtual function’s address not the real implement function’s address. So the key of hooking direct3d is finding out the real function address. To do this we need to know how virtual function works.
. E* ~: f( s( P) E' U0 CUnderstanding Virtual Functions and Hooking Direct3d
! _4 A+ D/ a+ D/ e6 mVirtual function is used for reusable and polymorphic purposes. It is runtime bound to the real implement function. For more details about how virtual functions work, please read Shivesh V.’s paper “Virtual Functions and their implementation in C”. ; e8 A; Y; q/ y. }( B, ^
1 H8 O/ q2 Y- V+ ]( D9 V3 E
Basically, after you call the Direct3DCreate8() function (let’s assume we use directx8), you will get a pointer to the created IDirect3D8 object. In the pointer is the address of the vtable, a structure that contains all the member functions’ addresses. The address in the vtable is the real implement function’s address. So if we want to build a benchmark application to test the frame update rate, we could do it in the following steps:/ M& G7 g3 S/ R3 m
1) Catch Direct3DCreate8() to get the IDirect3D8 object, T$ R( B4 D! I$ X- ~- {
2) Use the pointer of IDirect3D8 object, catch the CreateDevice() function to get the IDirect3DDevice8 object.0 h% J# R( [% u; M _
3) Use the pointer of IDirect3DDevice8 object, catch Present() function.' X% |9 w! f# ]; n$ v4 b; y
4) In the Present() function calculates the frame rate.( o0 ~6 x( g0 g6 Y; I( |2 Y
% ~$ W8 H# c9 N/ T" @
However, to catch the member function, there is a very important parameter we need to know. It’s the address data in the vtable that contain the to-be-hooked function’s address. There is no general way to know the offset in the vtable. For direct3d, you could use the d3d8.h file to figure out the offset. Or you can debug a direct3d application and get the offset by read the disassembly code. The following is a piece code of the directx 8.1 SDK’s Text3D sample code. The comments of the disassembly code are added by the author.
8 x% {% f3 B* `) t( T
3 O$ M" N1 R6 P4 f1 F+ o E873: // Create the device
6 v: z$ I9 z$ P7 a9 t874: hr = m_pD3D->CreateDevice( m_dwAdapter, pDeviceInfo->DeviceType,( \) L- H$ ~# B3 _
875: m_hWndFocus, pModeInfo->dwBehavior, &m_d3dpp,
: S) ^0 F2 p; i; g/ L! H876: &m_pd3dDevice );- e, m f7 ]' M& `7 o
00403114 mov eax,dword ptr [this]6 o8 f6 T5 L% R7 @- b
00403117 add eax,2A4A8h. g4 l; a$ Q8 j* @5 ~4 C0 M
0040311C push eax // push the sixth parameter &m_pd3dDevice
( g: b1 t% X+ a7 }8 ^: `0 e: y0040311D mov ecx,dword ptr [this]
; Q" s# }& G; f( P8 ^) U4 s00403120 add ecx,2A464h8 i1 i2 I! s6 w
00403126 push ecx //push the fifth parameter &m_d3dp, p2 K" I+ V! ^) p4 n* L
00403127 mov edx,dword ptr [pModeInfo]. k- j8 B9 ^5 n" ]" D
0040312A mov eax,dword ptr [edx+0Ch] Z; L: Q# `! Q0 V8 I- {
0040312D push eax //push the fourth parameter pModeInfo->dwBehavior7 K* [, @& ?3 ^4 K# g" L1 J
0040312E mov ecx,dword ptr [this]
$ V; o% Y3 o [00403131 mov edx,dword ptr [ecx+2A49Ch]
$ w2 h; q- ?- O00403137 push edx //push the third parameter m_hWndFocus8 v$ I+ ^ N- D- t* _
00403138 mov eax,dword ptr [pDeviceInfo]
5 g9 p( p# x% P/ V- G/ Y( i' D0040313B mov ecx,dword ptr [eax]
* l& z7 R7 z( w& ~% _0040313D push ecx //push the second parameter pDeviceInfo->DeviceType1 o9 ]* ?4 X' ~/ x+ s; Q& Z9 z/ x
0040313E mov edx,dword ptr [this]1 o3 G# H' r' p2 u: l
00403141 mov eax,dword ptr [edx+2A448h]
. o" [% |$ f, `. {0 h00403147 push eax //push the first parameter m_dwAdapter
. e5 K" b2 G" E, _00403148 mov ecx,dword ptr [this]/ E: I' u1 g( Q; P7 z$ E# j
0040314B mov edx,dword ptr [ecx+2A4A4h] //calculate the return address
9 h" S: T, x0 `6 V! E5 @) o: s3 A00403151 mov eax,dword ptr [this]
2 }* t% ?$ \ S00403154 mov ecx,dword ptr [eax+2A4A4h]
7 E7 f$ Y5 \! |1 Q) D0040315A mov eax,dword ptr [ecx] //calculate the vtable address. It’s the value / M( D% u/ o# `
//stored in ecx register.
6 J: e8 j" E: f/ {$ _# ~. j0040315C push edx //push the return address
4 F h7 e. I: D4 ^0040315D call dword ptr [eax+3Ch] //call the CreateDevice() function ; z$ @: L+ s9 J2 k
00403160 mov dword ptr ,eax
" z& `% X N% y( {
# ^9 s$ w: G' ~! A" nFrom the assembler code, it’s obvious that the offset is 0x3ch. You can do the same thing for the Present() function. And the offset happen is also 0x3ch.& W2 G+ U. a6 |+ @
+ o4 e# H) z; [9 b: {
The sample code can be found at http://usl.sis.pitt.edu/wjj/UTClient/direct3d8.zip. To compile it you need Directx 8.1 SDK. Please note that the method of display frame count is not every effective. If you remove the displaying code, you will find Detours has almost no effect to the frame rate.
: m) P3 v z/ A5 `# D4 a, g! tReference: @, Y! m6 w& F d! D& q1 x2 u
[1] “API hooking revealed”, Ivo Ivanov+ H2 n2 D X$ J
[2] “madCodeHook”, madshi
1 H4 X/ m( L2 s, R/ L[3] "Detours", Galen Hunt and Doug Brubacher. L/ p' w& x. R* [
[4] “Virtual Functions and their implementation in C”, Shivesh V.
2 F; ^- r/ y5 }- _1 d4 F0 t( m6 Z[5] “Pointers to C++ Member Functions”, Michael D. Crawford |