C/C++ HOOK API(原理深入剖析之-LoadLibraryA)
这篇文章就来谈谈平常很常见的HOOK技术,这里呢。写得比较简单,方法很多。只讲原理!希望大鸟们别吐我口水哈 - -。好!切入正题。首先是概念吧。什么是钩子(HOOK)?钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
这上面只是一个概念,对它有所了解而已。上面主要应用在Windows消息处理机制里面的一个解释。这里我只是单纯的谈谈拦截我们常用的LoadLibraryA加载这个函数。让我们的程序或者目标程序在调用这个函数加载链接库的时候,先执行我们自己写的函数,然后在进行正常加载。通俗的说就是a----->b.我们在中间加上一个c。 a-------->c----->b让他先执行c然后再执行b。这里的c就是我们自己的函数了。
呵呵,概念说得差不多了,开始行动写代码撒:
#include <iostream>
#include <Windows.h>
using namespace std;
#pragma warning( disable: 4309 )
#pragma warning( disable: 4311 )
typedef HMODULE ( WINAPI *HOOKAPI )( IN LPCSTR );
#define MYHOOKMETHOD ( __fun ) HMODULE WINAPI __fun
#define DECLARE_REGISTER ( __0bj, __lawfunc, __newfunc ) Inline_Hook< HOOKAPI, 1 > __Obj( __lawfunc, __newfunc )
struct __InlineHOOK_Base
{
DWORD _argsBytes;
void* _lawFunc;
void* _newFunc;
char_lawByteCode;
char_newByteCode;
bool unhook ( void )
{
// It's hooked.
if ( memcmp( _newByteCode, _lawFunc, 16 ) == 0 )
{
DWORD dwOldFlag;
VirtualProtect( _lawFunc, 8, PAGE_EXECUTE_READWRITE, &dwOldFlag );
memcpy( _lawFunc, _lawByteCode, 16 );
VirtualProtect( _lawFunc, 8, dwOldFlag, &dwOldFlag );
return true;
}
return false;
}
bool hook ( void )
{
// It's saved.
if ( memcmp( _lawByteCode, _lawFunc, 16 ) == 0 )
{
DWORD dwOldFlag;
VirtualProtect( _lawFunc, 8, PAGE_EXECUTE_READWRITE, &dwOldFlag );
memcpy( _lawFunc, _newByteCode, 16 );
VirtualProtect( _lawFunc, 8, dwOldFlag, &dwOldFlag );
return true;
}
return false;
}
__InlineHOOK_Base( void* lawfun, void* newfun, DWORD args );
};
void __declspec( naked ) __Inline_Hook_Func ( void )
{
__asm
{
push ebp// save maybe usefull register.
push ebx
push esi
push ecx
call __InlineHOOK_Base::unhook // first, remove the hook in order to call the normal function.
test eax, eax // check the remove was successful
jz __return
__getargnum:
mov eax, dword ptr // esp just is ecx, also is __InlineHOOK_Base's this pointer.
mov ecx, dword ptr // get first 4 bytes, that is params total size.
shr ecx, 2 // get params num, equal with __InlineHOOK_Base::_argsBytes / sizeof( DWORD )
test ecx, ecx // check whether there are params.
jz __callfunc // no param
__pushargs:
mov edx, esp // __InlineHOOK_Base's this pointer.
add edx, 14h // navigate to first call ret addr.
add edx, dword ptr; // add params size.
push dword ptr; // push the dll file name pointer.
loop __pushargs
__callfunc:
call // call my function .
mov ecx, dword ptr// get __InlineHOOK_Base's this pointer.
push edx // save my function return value.
push eax
call __InlineHOOK_Base::hook // rehook.
pop eax // get saved return value, provided to my superiors to use
pop edx
__return:
pop ecx
pop esi
pop ebx
pop ebp
ret
}
}
__InlineHOOK_Base ::__ InlineHOOK_Base ( void* lawfun, void* newfun, DWORD args )
: _lawFunc( lawfun ), _newFunc( newfun ), _argsBytes( args * 4 )
{
_newByteCode[ 0 ] = 0xB9; // mov ecx, ...
( DWORD& )_newByteCode[ 1 ] = ( DWORD )this;
_newByteCode[ 5 ] = 0xB8; // mov eax, ...
( DWORD& )_newByteCode[ 6 ] = ( DWORD )__Inline_Hook_Func;
( WORD& )_newByteCode[ 10 ] = 0xD0FF; // call eax
_newByteCode[ 12 ] = 0x000000C3; // ret
if ( args > 0 )
{
_newByteCode[ 12 ] = 0xC2; // ret ...
( WORD& )_newByteCode[ 13 ] = ( WORD )_argsBytes;
_newByteCode[ 15 ] = 0;
}
memcpy( _lawByteCode, _lawFunc, 16 ); // save
}
template< typename _function, DWORD args >
struct Inline_Hook : __InlineHOOK_Base
{
Inline_Hook( _function lawfun, _function newfun )
:__InlineHOOK_Base( lawfun, newfun, args ) { hook(); }
~Inline_Hook( void ){ unhook(); }
};
MYHOOKMETHOD ( myLoadLibrary )( LPCSTR lpcStrFileName )
{
::MessageBox( NULL, lpcStrFileName, "LoadLibrary Name", MB_OK | MB_ICONINFORMATION );
return LoadLibraryA( lpcStrFileName );
}
DECLARE_REGISTER ( __inline_hook , LoadLibraryA , myLoadLibrary );
int main ( void )
{
HMODULE hIntstance = LoadLibraryA ( "d3d9.dll" );
return 0;
}上面这个程序是我写的一个测试。原理很简单,也就是在调用LoadLibraryA
加载动态链接库之前,先把LoadLibraryA的前16个代码字节给替换成我们自己的HOOK拦截代码,原理跟我之前的一篇Shell Code原理类似!改变了前16个字节后,这时就是已经HOOK了的LoadLibraryA了。然后在程序调用这个函数,进入后。将先调用我们自己写的函数。这里我们自己的函数是myLoadLibrary。这里面我就随便写了个测试。弹一个MessageBox显示DLL的名称!然后再执行正常的LoadLibraryA。看到这里,或许大家会产生两个疑问。
1.为什么替换的是16个字节?
2.在调用了我们的函数后,再调用正常的LoadLibraryA。这里的LoadLibraryA不是已经被我们给替换了吗?怎么正常呢?
首先,第一个问题。这里就得看上方蓝色的函数__InlineHOOK_Base
了。先是这个结构体:
DWORD _argsBytes; // 参数所占的字节数
void* _lawFunc; // 指向老的Hook前的LoadLibraryA函数的一个指针
void* _newFunc; // 指向我们自己的中间函数的指针
char_lawByteCode; // 保存正常的LoadLibraryA前16个代码字节,用于UnHook,不然怎么还原呢。呵呵!
char_newByteCode; // 我们替换给LoadLibraryA的16个代码字节,用于Hook,不然怎么执行我们自己的函数呢,呵呵!
所以在我们调用LoadLibraryA之前会调用__InlineHOOK_Base构造函数。因为是全局对象。如:
DECLARE_REGISTER ( __inline_hook , LoadLibraryA , myLoadLibrary );
_newByteCode[ 0 ] = 0xB9; // mov ecx, ...
( DWORD& )_newByteCode[ 1 ] = ( DWORD )this;
_newByteCode[ 5 ] = 0xB8; // mov eax, ...
( DWORD& )_newByteCode[ 6 ] = ( DWORD )__Inline_Hook_Func;
( WORD& )_newByteCode[ 10 ] = 0xD0FF; // call eax
_newByteCode[ 12 ] = 0x000000C3; // ret
if ( args > 0 )
{
_newByteCode[ 12 ] = 0xC2; // ret ...
( WORD& )_newByteCode[ 13 ] = ( WORD )_argsBytes;
_newByteCode[ 15 ] = 0;
}
上面这段代码的功能就是将语句转化成字节码,存到_newByteCode数组中,然后在HOOK的时候会拷贝到正常的LoadLibraryA中。将其前16字节替换成这里的。为什么是16字节,原因含简单,那就是这里的字节码就只用得到15个。哈哈!将上面的字节码翻译成C++就是:
__Inline_Hook_Func();//一句!
其他的就是为了将this保存到ECX中,返回如果有参数,且这个LoadLibraryA只有一个参数,在我们替换的字节码中手工给保持堆栈平衡,就会ret _argBytes这么字节数!调用外面就不用ADD ESP了保持堆栈平衡了!这也是LoadLibraryA原先的返回方式!这里这些指令的用法和为什么把this保存到ECX中,就不多说了!我的SHELL CODE那篇文章里有提到!
memcpy( _lawByteCode, _lawFunc, 16 ); // 保存正常的字节码,用于还原!
这样把我们要替换进去的字节码给准备好了,下一步就是拷贝进去的过程了。Inline_Hook 构造函数是调用了HOOK函数的。再看HOOK函数的实现是:
// It's saved.
if ( memcmp( _lawByteCode, _lawFunc, 16 ) == 0 )// 看看是否保存了,否则还原不了没办法unhook。
{
DWORD dwOldFlag;
VirtualProtect( _lawFunc, 8, PAGE_EXECUTE_READWRITE, &dwOldFlag );//这个函数就不用说了,呵呵,查资料吧!
memcpy( _lawFunc, _newByteCode, 16 ); // 拷贝我们的HOOK代码进LoadLibraryA中!
VirtualProtect( _lawFunc, 8, dwOldFlag, &dwOldFlag );
return true; // 拷贝成功!
}unhook原理类似,也就是将正常的拷贝进去!
好了!到了现在,功能函数都差不多了,现在就差__Inline_Hook_Func
这个函数的实现了!
这个函数全是汇编。嘿嘿!我用的英文注释了的哈。大致能看明白,英语太差了!
这里只说它的功能:
1.这个函数的调用代码字节我们已经拷贝到了LoadLibraryA中。如果我们调用LoadLibraryA( "XXXX.DLL" );将会调用__Inline_Hook_Func
这个函数。这个函数的前缀是不是很奇怪?大鸟别笑。呵呵!我前面的文章也提到了这些前缀的意思。这里不用多说。现在看看我们拷贝进LoadLibraryA后,大家可以看看LoadLibraryA的字节码的前后对比:
替换前:
7C801D7B
8B FF
mov edi,edi
7C801D7D
55
push ebp
7C801D7E
8B EC
mov ebp,esp
7C801D80
83 7D 08 00
cmp dword ptr ,0
7C801D84
53
push ebx
7C801D85
56
push esi
7C801D86
74 14
je 7C801D9C
7C801D88
68 60 E1
80 7C
push 7C80E160h
7C801D8D FF 75 08 push dword ptr
7C801D90 FF 15 AC 13 80 7C call dword ptr ds:
7C801D96 85 C0 test eax,eax
7C801D98 59 pop ecx
7C801D99 59 pop ecx
7C801D9A 74 12 je 7C801DAE
7C801D9C 6A 00 push 0
7C801D9E 6A 00 push 0
7C801DA0 FF 75 08 push dword ptr
7C801DA3 E8 AB FF FF FF call 7C801D53
7C801DA8 5E pop esi
7C801DA9 5B pop ebx
7C801DAA 5D pop ebp
7C801DAB C2 04 00
ret 4
上面红色的字节码就是将要被替换的。 蓝色的ret 4可以看出开始的疑问,为什么是ret 4.
替换后:
7C801D7B
B9 E0 A5 42 00
mov ecx,offset
__Obj
(42A5E0h)
7C801D80
B8 BC 12 41 00
mov eax,offset
__Inline_Hook_Func
(4112BCh)
7C801D85
FF D0
call eax
7C801D87
C2 04 00
ret 4
7C801D8A
00
80 7C FF 75 08 add byte ptr ,al
7C801D90 FF 15 AC 13 80 7C call dword ptr ds:
7C801D96 85 C0 test eax,eax
7C801D98 59 pop ecx
7C801D99 59 pop ecx
7C801D9A 74 12 je 7C801DAE
7C801D9C 6A 00 push 0
7C801D9E 6A 00 push 0
7C801DA0 FF 75 08 push dword ptr
7C801DA3 E8 AB FF FF FF call 7C801D53
7C801DA8 5E pop esi
7C801DA9 5B pop ebx
7C801DAA 5D pop ebp
7C801DAB C2 04 00
ret 4
上面的字节码,相信一看就明白了!呵呵!替换后就会在0x7C801D87这里返回了,下面的代码就作废了! - -
2.
__Inline_Hook_Func
这个函数在进入之后,先是unhook,因为下面要调用myLoadLibrary函数。肯定要正常的LoadLibraryA函数咯。之后就是一系列的参数准备。知道调用了myLoadLibrary函数,弹出了对话框。之后回到
__Inline_Hook_Func
函数。保存返回值(句柄)。之后再hook掉LoadLibraryA函数。让第二次还能进来先调用我们的函数。之后就是返回值给上层主调函数了!
上面的2问题也就解开了。那就是因为
__Inline_Hook_Func一开始就给我们unhook了。
myLoadLibrary函数使用的一直都是正常的
LoadLibraryA
函数!
其他:
这里只是讲了原理,练下手可以。呵呵!一般稍微有水平一点的程序是检测了这个
LoadLibraryA函数是否被修改的。技术这东西就是你有招我也有招!呵呵!
好了。这篇文章终于写完了,累!有什么不对的地方还望各位批评!在此感谢。。。
来自:http://blog.csdn.net/masefee/article/details/4566121
页:
[1]