API HOOKWord文档下载推荐.docx
《API HOOKWord文档下载推荐.docx》由会员分享,可在线阅读,更多相关《API HOOKWord文档下载推荐.docx(139页珍藏版)》请在冰豆网上搜索。
过打补丁的方法实现的。
如果用户以前曾经研究过APIHOOK,也许听说过导入地址表补丁的概念。
它的
很多优点使得这种WindowsAPIHOOK的方法成为最体面、最常见的方法之一。
这种
方法的基础主要是基于这样的事实,即所有的32位Windows可执行文件或者动态链
接库都是基于PE(PortableExecutable)文件格式构建的。
这种方法构建的文件由多个块
组成,这些块称为节。
每个节中都包含了特定的内容。
比如说,.text节保存了可执行
应用程序的代码,而.rsrc节则是为了保存程序中使用的各种资源,比如对话框、菜单、
位图、工具栏等。
在Windows可执行文件的所有节中,.idata节对于实现API拦截尤其有用。
在这
个节中包含了一个导入地址表,它保存着所有与可执行代码引用的导入函数名称的偏
移量。
当Windows加载一个可执行文件到内存中的时候,它就会使用正确的导入函数
地址来修正这个偏移量。
也许用户会问,Windows为什么会不厌其烦地去修正导入地
址表呢?
这是因为所有的可执行文件和动态链接库在加载到内存中时都需要重新定
位。
如果在可执行代码中预先规定好导入函数的目标调用地址,这往往是不可能的。
为了确保这些调用能够成功地连接到它们的目标,Windows很有必要在可执行文件映
像加载到内存中时,对导入函数的每一个调用进行修补。
很明显,这个初始化的过程
是一个耗时的过程,加载的可执行文件依赖的动态链接库越多,这一点表现的就越明
显。
如果用户的程序加载时间格外长,那么应该考虑把那些不必要的隐式加载移动到
动态加载中来。
Windows的设计者并非那么鼠目寸光,他们在地址问题上表现得特别聪明。
在当
前的Windows可执行文件或者动态链接库实现中,所有对导函数的调用都是借助于导
入地址表通过一个间接的跳转(JMP)实现的。
很明显,Windows导入地址表这种重新定向的机制为API函数的拦截提供了方
便。
为了实现拦截,用户可以用自己函数的入口地址覆盖这个导入地址表项,这样函
数调用时,首先加载的应该是用户提供的函数。
另外一种办法是对函数的实现进行修补。
这种方法主要是通过替换函数实现的前
几个字节实现的,每一个函数实现时,无非一对参数入栈指令,可以把这些指令的前
几个字节保存到一个地址中,然后用一个跳转指令替换这些字节。
当目标函数被调用
的时候,首先执行的是跳转指令,直接把控制交给了用户提供的替换函数。
下面的图6-1是这种实现的机理。
在上面的图6-1中,函数拦截时,在函数的入口地址处放置了一个Jmp指令,把
控制权直接转移到了替换函数LoggingFunction。
而在LoggingFunction函数中如果需
要实现原来函数的功能,则可以通过callStub指令实现。
而Stub函数地址则以原来保
存的几个字节和一个跳转指令组成。
很明显这种方法对拦截的函数有一定的限制,即
这个函数入口地址处代码不能少于5个字节。
当然这也不是什么条件,一般情况下,
使用5个字节是很难实现一个函数的,即便勉强实现了,拦截这么一个几乎没有任何
功能的函数没有任何意义。
这种方法实现的典型代表是微软提供的Detours,一个通用
的API函数拦截库。
解决了函数拦截问题,问题还没有结束,用户还需要考虑进程边界的问题,即如
何让自己的拦截代码运行在正确的地址空间。
在回答这个问题之前,用户最好对
Windows内存管理的架构有所了解。
前面提过,所有的32位Windows应用程序都运行在一个相对独立的私有地址空
间。
任务切换时,Windows会更新页表,把一个新进程的线性地址空间映射到物理内
存中,即进程内存环境。
这样除了共享内存区域保持不变外,原来进程的内容就会被
一个新进程映射完全刷新,再也找不到它的踪影。
在Windows9x环境下,4GB的线性地址空间被划分成几个不同的内存区域,这
些区域都是为原来预定的目的保留的。
MS-DOS和16位的全局堆的前面一部分占用
了最底部的4MB空间,紧挨着的区域(4MB~2GB)用于Windows9x加载每一个进程代
码、数据和动态链接库。
由于每一个进程的物理地址都是惟一的,因此Windows9x
必须确保当一个指定的进程被激活时,整个页面表对应(4MB~2GB)的被映射到该进程
的物理内存页。
这种切换机制造就了所有进程共享(4MB~2GB)同一线性内存区域,但
不是相同的物理地址。
这就好像这个区域是该物理地址空间的一个窗口,这个窗口能
够根据当前进程的不同显示它对应的不同视图。
总之,进程内存的表现形式只是它磁
盘映像的一个副本。
从2GB到3GB的内存区域,归16位全局堆高位部分所有,同时
这个区域还用于实现内存共享文件,寄存Windows使用的系统动态链接库(比如
USER32、GDI32和KERNEL32),这些内容能够被所有正在运行的进程共享。
这个区
域对于API函数拦截来说,尤其有用,因为它对于所有活动的地址空间而言都是可见
的,这里面的数据和代码能够被所有的进程来存取。
对于WindowsNT/2000,内存分布情况大不一样,很少有文档披露一种方法能够
把动态链接库加载到所有进程都能够共享的区域。
惟一的一种方法是要求目标进程能
够把监视的动态链接库注入到它所在的地址空间。
最简单而且最不常用的方法是通过
注册表实现:
HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\
Windows\AppInit_Dlls。
这个键会导致Windows把动态链接库加载到系统中每一个进程的地址空间。
不幸
的是,这种技术只适用于那些链接到User32.Dll的进程,如果是一个控制台应用程序,
没有使用User32中实现的输出函数,这种方法将不起作用。
关于打破进程边界,实现
代码注入的方法在前面的第3章已经谈到很多,钩子函数、创建远程线程等都可以实
现动态链接库代码的注入。
另外在什么时间注入也是一个值得考究的问题,错误的注入时间会错失拦截的良
机,特别是对于系统范围内拦截的情况。
最好的完成时机应该是监视对CreateProcess
函数的调用。
在替换CreateProcess函数实现时,可以使用CREATE_SUSPENDED作
为创建标志替换调用,这样能够保证进程启动时处于挂起状态。
在挂起阶段,拦截程
序可以完成动态链接库注入,然后使用ResumeThread函数重新激活线程运行。
当然还有其他方法能够实现进程的执行,但是它们都是异步实现的,这些方法并
不适合用于判断何时注入动态链接库。
关于进程创建监视的代码前面第3章已经作了非常详尽的介绍。
比如,在Windows9x
环境下,当一个新的进程创建的时候,VWin32会发送一个CREATE_PROCESS消息,
可以在一个虚拟设备驱动程序中拦截这个消息,然后通知给Win32应用程序。
对于
WindowsNT/2000可以在WDM驱动程序中使用PsSetCreateProcessNotifyRoutine函数
创建一个进程创建的回调函数。
在众多的APIHOOK应用中,用户最关心的莫过于对网络活动的检测了,这些程
序包括防病毒程序、个人防火墙和Internet基于内容过滤的程序。
由于大部分的网络
程序都是基于Winsock函数实现的,所以对Socket函数的拦截,备受用户的青睐。
对Socket函数的拦截并不需要实现一个完整的Winsock函数集。
Winsock2提供了一
种LSP(UnravelingtheMysteriesofWritingaWinsock2LayeredServiceProvider)机制,
这种机制允许用户以链状的方式挂接上一层的Socket函数。
这种挂接的机制和上面
提到的APIHOOK方法有着很大的不同,但是实现替换函数的方法却有着异曲同工
之妙。
用户可以访问
LayeredService/LayeredService.htm链接获得这方面的信息。
另外一种监视网络活动的方法是编写上层NDIS驱动程序,通过编写中间层的驱动
程序或者挂接NDIS接口,不仅可以监视TCP/IP通信,还可以监视通过网卡的任何其他
数据。
NuMega的VToolsD(
提供了一个HookTDI的例子程序,微软也提供了一个NDis中间驱动的例子程序
(
针对浏览器访问的链接的跟踪,通过API拦截往往效率不高。
这时用户可以使用
InternetExplorer4.0以上版本提供的BrowserHelperObjects(
Mind/0598/browhelp.htm)。
对于NetscapeNavigator浏览器,用户可以采用DDE机制。
这
方面的例子可以参照
程序。
有关NetscapeDDE的文档可以参考
communicator/DDE/index.htm。
另外一个需要拦截的是图形设备操作了。
显然监视所有GDI函数远不是一个理想
的解决方案,Windows9x/2000提供了一种机制,允许应用程序在函数操作到达视频驱
动之前拦截这些函数调用。
用户可以参考
msaapndx_5h2j.htm提供的SetDDIHook例子。
使用APIHOOK方面还包括对文件系统的监视。
下面的资源提供了有关文件监视
实现的例子和文档:
●Windows95文件系统
●WindowsNT文件系统
●Windows9x/NT和2000环境下文件监视
●E4M(
实现磁盘活动监视
除了上面类型的钩子之外,还有一种中断钩子。
在风光不再的DOS时代,编写
TSR(中断驻留)程序曾经是一种时尚。
在Windows系统下,中断仍然扮演着十分重要的角色,它主要用作连接内核程序
和用户态应用的门户。
如果用户希望挂接Windows的中断,可以参考下面资源:
●《UndocumentedWindowsNt》
●《MonitoringNTDebugServices》2000年2月的WindowsDeveloperJournal
杂志
●NTSpy(http:
//cmp.phys.msu.su/ntclub/index.htm)通过挂接Int2EH中断实现的
NT系统调用监视程序
一般的APIHook方法只适用于动态链接库的输出函数,对于COM的接口方法往
往无能为力。
这对于数目众多、功能强大的COM接口方法来说,不能不说是一种遗憾。
幸运的是DmitriLeman在1999年7月份的WDJ杂志上提出了一种COM接口监视的方
法,另外微软提供的Detours开发包也提供了对COM拦截的支持,但是它只是在提供
的PowerPoint文档中对COM接口方法拦截进行介绍,却没有提供一个样例程序。
16位应用程序以后不会有更多的表现机会,但是一些程序可能仍然需要了解拦截
的方法。
如果用户需要了解拦截16位Windows函数的方法,可以参考1994年1月的
MSJ杂志中“HookandMonitorAny16-bitWindowsFunctionWithOurProcHookDLL”
一文。
如果用户曾经使用QuickView工具或者Depends查看ntdll.dll文件,就会发现这
个库输出了大量以NT打头的函数。
这些函数只是一些函数转发代理,其最终的函数
实现是通过调用Int2Eh中断借助于WindowsNT真正的内核实现的。
许多kernel32.dll
输出的函数只是把控制转向给ntdll库的函数代理。
比如说用户调用Kernel32中的
CreateFile函数,实际上被重新定向调用NTCreateFile函数,最终被提交给WindowsNT
的内核作进一步的处理。
这种特殊的设计为驱动程序挂接这些接口提供了很好的机制。
这方面的信息可以参考:
●《UndocumentedWindowsNT》
●《WindowsNT/2000NativeAPIReference》
●Regmon注册表函数调用的监视
//cmp.phys.msu.su/ntclub/index.htm)
●《TracingNTKernel-ModeCalls》2000年4月WDJ杂志
APIHOOK实现技术复杂,涉及面广。
总结起来,对Windows的API函数的拦截,
不外乎两种方法。
第一种是JeffreyRichter在《Windows核心编程》中提到的修改exe
文件的模块输入节,这种方法很安全,比较复杂,但是有一定的局限性。
对于有些exe
文件,没有Dll的输入符号的列表,有可能出现拦截不到的情况。
比如用户采用这种方
法在InternetExplorer中拦截Winsock函数,InternetExplorer没有直接使用Winsock库
函数,它依赖的WiniNet虽然使用了这个库,或者它通过动态加载这个库文件,但是却
拦截不到。
第二种方法就是常用的JMPXXX的方法,虽然很古老,却很简单实用。
对于第二种方法,在Windows9x下,因为进入Ring0级的方法很多,有LDT、
IDT、VxD等,很容易在内存中动态修改代码,而且屡试不爽。
但在WindowsNT内
核系统中,这些方法都不能用,尽管采用WDM也可以实现同样的目的,但是比较复
杂。
很多情况下都是采用WindowsNT提供的内存管理函数实现。
这些函数包括
VirtualProtectEx、WriteProcessMemeory和ReadProcessMemeory。
有了它们,用户就可
以在内存中动态修改代码了。
例6-1APIHOOK实现。
HHOOKg_hHook;
HINSTANCEg_hinstDll;
FARPROCpfMessageBoxA;
intWINAPIMyMessageBoxA(HWNDhWnd,LPCTSTRlpText,LPCTSTR
lpCaption,UINTuType);
BYTEOldMessageBoxACode[5],NewMessageBoxACode[5];
HMODULEhModule;
DWORDdwIdOld,dwIdNew;
BOOLbHook=false;
VoidHookOn();
VoidHookOff();
BOOLinit();
LRESULTWINAPIMessageHook(intnCode,WPARAMwParam,LPARAMlParam);
BOOLAPIENTRYDllMain(HANDLEhModule,
DWORDul_reason_for_call,
LPVOIDlpReserved)
{
switch(ul_reason_for_call)
caseDLL_PROCESS_ATTACH:
if(!
init())
MessageBoxA(NULL,"
Init"
"
ERROR"
MB_OK);
return(false);
}
caseDLL_THREAD_ATTACH:
caseDLL_THREAD_DETACH:
caseDLL_PROCESS_DETACH:
if(bHook)UnintallHook();
break;
returnTRUE;
//这个钩子是为了让系统实现映射动态链接库,实现进程代码注入。
LRESULTWINAPIHook(intnCode,WPARAMwParam,LPARAMlParam)//空的钩子函数
return(CallNextHookEx(g_hHook,nCode,wParam,lParam));
HOOKAPI2_APIBOOLInstallHook()//输出安装空的钩子函数
g_hinstDll=LoadLibrary("
ApiHook.dll"
);
//这里假定当前程序输出的动态链接库名称为ApiHook.dll
g_hHook=SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)
MessageHook,g_hinstDll,0);
g_hHook)
SETERROR"
return(true);
HOOKAPI2_APIBOOLUninstallHook()//输出卸载钩子函数
return(UnhookWindowsHookEx(g_hHook));
BOOLinit()//初始化得到MessageBoxA的地址,并生成JmpXXX(MyMessageBoxA)的
跳转指令
hModule=LoadLibrary("
user32.dll"
pfMessageBoxA=GetProcAddress(hModule,"
MessageBoxA"
if(pfMessageBoxA==NULL)returnfalse;
_asm
Leaedi,OldMessageBoxACode
Movesi,pfMessageBoxA
cld
movsd
movsb
NewMessageBoxACode[0]=0xe9;
//jmpMyMessageBoxA的相对地址的指令
Leaeax,MyMessageBoxA
Movebx,pfMessageBoxA
Subeax,ebx
Subeax,5
Movdwordptr[NewMessageBoxACode+1],eax
dwIdNew=GetCurrentProcessId();
//得到所属进程的ID
dwIdOld=dwIdNew;
HookOn();
//开始拦截
intWINAPIMyMessageBoxA(HWNDhWnd,LPCTSTRlpText,LPCTSTRlpCaption,
UINTuType)//首先关闭拦截,然后才能调用被拦截的API函数
intnReturn=0;
HookOff();
nReturn=MessageBoxA(hWnd,"
Hook"
lpCaption,uType);
return(nReturn);
voidHookOn()
HANDLEhProc;
hProc=OpenProcess(PROCESS_ALL_ACCESS,0,dwIdOld);
//得到所属进程的句柄
VirtualProtectEx(hProc,pfMessageBoxA,5,PAGE_READWRITE,&
dwIdOld);
//修
改所属进程中MessageBoxA的前5个字节的属性为可写
WriteProcessMemory(hProc,pfMessageBoxA,NewMessageBoxACode,5,0);
//将
所属进程中MessageBoxA的前5个字节改为JMP到MyMessageBoxA
VirtualProtectEx(hProc,pfMessageBoxA,5,dwIdOld,&
//修改所
属进程中MessageBoxA的前5个字节的属性为原来的属性
bHook=true;
voidHookOff()//将所属进程中JMPMyMessageBoxA的代码改为JmpMessageBoxA
HANDLEhP