论文部分内容阅读
摘要:讲述了Windows API拦截的关键技术和方法,重点讲述了dll的注入技术和API拦截技术,并对各种技术进行了优缺点分析。
关键词:API;拦截
中图分类号:TP393文献标识码:A文章编号:1009-3044(2008)27-1920-03
Windows API Hooking Method
SHI Yong-lin,PAN Jin,LI Guo-Peng
(Department one of Xi’an Communication Institute,Xi’an 710016,China)
Abstract:This paper give a detail explanation of how to Intercepting Win32 API,its main concern is the way to inject dll into process’s space and API hooking.It also tell the strong and weak point of the different methods.
Key words:API;hooking
Windows下API(应用程序编程接口,实际上就是Windows系统调用)的拦截是个很有用的技术,现在很多商用的系统都用到了这种技术,如屏幕取词,内码转化,屏幕翻译,中文平台等等都涉及到了此项技术。比如大家熟悉的即时翻译软件,就是靠拦截TextOut()或ExtTextOut()这两个系统API调用实现的,在操作系统用这两个函数输出文本之前,通过拦截这两个调用,把传递给这两个调用的的英文替换成中文,然后再调用原来的API输出,从而达到即时翻译的目的。可以说,掌握了这种技术,从某种意义上讲就可以控制或改变操作系统或其他软件的功能,这也是许多游戏外挂等常用的技术。而且这种技术也被许多病毒和木马等利用,从而达到破坏系统和感染程序的目的,当然现在的反病毒软件也多用到此技术。
1 Windows API拦截技术概述
Windows API拦截的主要目的是在其他应用程序调用API之前将其拦截,由拦截者先处理传递的参数数据,然后决定是否再调用原来的API。比如API
BOOL TextOutA( HDC hdc, int nXStart,int nYStart,LPCTSTR lpString, int cbString);
在其他程序调用这个API之前,拦截程序可以先捕获这个调用,先对参数等进行处理,比如将cbString翻译为中文等,然后再调用原来的TextOutA进行文本输出,这样输出的文本就变成中文了。
API拦截的原理很简单,但是要实现可靠的拦截则需要很多工作要做,首先需要把替换被拦截API的代码注入到目标进程中,这些代码一般是以动态链接库(DLL)的形式存在的,然后修改目标进程执行代码,使其在调用被拦截API之前先调用我们的替代代码。这个过程的示意图如图1所示。
一般拦截程序至少需要两个部分,一个是管理服务器(Management server),它的主要工作是拦截DLL的注入工作,并管理DLL的工作状态,接收DLL发回的处理消息等;另一个是拦截DLL,它主要包含拦截代码以及代码注入和API拦截的一些辅助代码。
总结起来API拦截主要有以下三点工作要做。
1) 要决定拦截哪些进程的API,如果是拦截个别进程的API,那么只需要在这个进程中插入拦截DLL,否则则需要在系统的所有进程中插入拦截DLL。
2) 决定采用哪种DLL注入技术。DLL注入技术有多种,但最常用的就两种,一种是windows全局钩子技术,另外一种是利用CreateRemoteThread() API来进行。我们将在第二节进行重点阐述。
3) 决定采用哪种API拦截机制。这也有多种技术可用,可以在内核层(kernel level)中进行,也可以在用户层(user level)中进行。我们将在第三节对这个问题进行深入阐述。
2 DLL注入技术
代码注入技术分为动态代码注入技术和静态代码注入技术,动态代码注入技术就是在进程启动后或在进程启动时在进程的运行空间中注入代码的技术,而静态注入技术就是在PE格式的.exe文件中插入代码。静态注入技术是病毒感染文件的常用方法,在文献[1]中有详细的叙述。动态注入技术也分为直接代码注入技术和以dll形式的注入技术,直接代码注入技术是利用VirtualAllocEx和CreateRemoteThread两个API来进行的函数级代码注入技术,可以采用汇编的形式,这种方法在文献[1]中的进程隐藏一章中有详细的讲解,也可以采用高级语言如c语言的形式,这种方法有兴趣的可以参考文献[2]。很显然API拦截只能采用动态代码注入技术,由于直接代码注入技术很复杂,灵活性很差,所以一般采用dll形式的动态代码注入技术。
2.1 windows全局钩子dll注入技术
Windows钩子技术是windows提供的windows事件拦截技术,它可以使程序设计者在一些windows事件(如鼠标、键盘事件、窗口创建事件等)发送到特定窗口或线程之前捕获并处理它们。具体钩子的应用技术可以参考Microsoft msdn。
Windows全局钩子可以把一个dll注入到系统中所有的进程空间中,这些都是由操作系统自动完成的。主要操作过程如下:
1) 首先创建一个dll,在其中中输出注册钩子函数,如MouseProc和我们的拦截函数,如MyTextOutA等。
2) 用SetWindowsHookEx函数注册全局钩子。这时操作系统会自动把包含钩子函数的dll映射到所有正在运行的进程空间。
3) 当不需要钩子时,调用UnhookWindowsHookEx()函数卸载钩子,则系统会自动从所有进程空间中卸载钩子dll。
全局钩子的使用是很简单的,只是要注意全局变量共享等问题,要把dll的共享变量放到一个共享区中。
这种方法的优点是操作简单,性能稳定,因为dll的注入和卸载等工作都是由操作系统自动完成的。它的缺点是不能选择性的注入dll到特定的进程中,而且系统会调用我们的无用的钩子函数来处理消息,从而可能会较大的降低系统的性能。
2.2 利用CreateRemoteThread的dll注入技术
这种方法在[3]中有较详细的论述。它的原理也很简单,但实现起来较复杂。要在其他的进程中注入dll,就要求我们能在那个进程中调用LoadLibrary() API,但我们没有权限获得其他进程的执行控制权,幸好微软提供了一个函数CreateRemoteThread()可以在其他的进程中创建远程线程,而恰好线程函数的原型:
DWORD WINAPI ThreadProc(LPVOID lpParameter);
和LoadLibrary()的原型:
HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);
HMODULE和DWORD都是双字节,调用方式都是WINAPI,LPVOID和LPCTSTR都是双字节指针,所以函数原型是一样的。从而我们利用CreateRemoteThread()来欺骗操作系统,使其执行LoadLibrary() API,如下:
hThread = ::CreateRemoteThread(
hProcessForHooking, //要插入dll的进程句柄
NULL,
0,
pfnLoadLibrary,// LoadLibrary函数的地址
"C:\\HookTool.dll", //要注入的dll的全路径
0,
NULL);
可以看出,采用这种方法的一个主要困难是要监视进程的创建和关闭,这样才能确保在所有的目标进程中注入dll,有关这部分的内容可以参考[4]。LoadLibrary函数的地址因为其所在的Kernel32.DLL的映射地址在所有进程中是确定的,所以其值可以通过调用GetProcAddress()获得。
这种方法的优点是可以选择性的在特定进程中注入dll,而且不产生额外的开销,基本不降低系统的性能。缺点是实现起来较复杂,要监视系统进程的活动。
3 API拦截机制技术
API拦截也有多种技术可以采用,从编程层次上可以分为内核级的和用户级的。内核级的拦截技术灵活、可靠,但实现复杂,调试困难,有兴趣的可以参考文献[5]。用户级也有多种技术,如dll替换技术、debug技术、目标API代码修改和修改输入表等,这些方法中比较可靠,应用较广的是目标API代码修改和修改输入表两种技术。
3.1 目标API代码修改
这种技术说起来也不复杂,就是改变程序流程的技术。在CPU的指令里,有几条指令可以改变程序的流程:JMP,CALL,INT,RET, RETF,IRET等指令。理论上只要改变API入口和出口的任何机器码,都可以拦截API,但是实际实现起来要复杂很多,因为要处理好以下问题:
1) CPU指令长度问题,在32位系统里,一条JMP/CALL指令的长度是5个字节,因此你只有替换API里超过5个字节长度的机器码(或者替换几条指令长度加起来是5字节的指令),否则会影响被更改的小于5个字节的机器码后面的数条指令,甚至程序流程会被打乱,产生不可预料的后果;
2) 参数问题,为了访问原API的参数,你要通过EBP或ESP来引用参数,因此你要非常清楚你的拦截代码里此时的EBP/ESP的值是多少;
3) 上下文的问题,有些拦截代码不能执行某些操作,否则会破坏原API的上下文,原API就失效了;
举个例子,假如要拦截的API的指令如下:
:71A21AF4 55 push ebp
:71A21AF5 8BECmov ebp, esp
:71A21AF7 83EC10sub esp, 00000010
......
:71A21B64 E8C7F6FFFF call 71A21230//要修改的代码
......
而我们的替换函数的地址为0x8321A341,则我们可以将地址:71A21B64的代码修改为call 8321A341,在我们的拦截代码执行完后执行jmp 71A21230,再返回原来代码处执行。可以看出,这种方法比较难找这条5字节的CALL指令,计算相对地址也复杂。
这种方法实现起来相当困难,而且灵活性和可靠性都比较差。所以一般不采用这种方法。
3.2 修改输入表
通过修改目标进程的输入表来拦截API是一种可靠、简单而且容易实现的技术。这种技术在文献[6]中有详细的讲解。要实现这种方法需要熟悉PE文件的格式,这些内容可以从文献[1]中获取。具体的实现需要以下几个步骤:
1) 从IMAGE_OPTIONAL_HEADER32结构的第二个IMAGE_DATA_DIRECTORY结构中获得输入表的地址。
2) 根据要替换API所属dll的名字查找IMAGE_IMPORT_DESCRIPTOR表,找到dll对应的IMAGE_IMPORT_DESCRIPTOR结构体。
3) 根据API的名字从IMAGE_IMPORT_DESCRIPTOR结构体中OriginalFirstThunk指向的IMAGE_THUNK_DATA列表中查找相应的API在数组中的序号。然后根据查得的序号在FirstThunk指向的IMAGE_THUNK_DATA列表中相应位置找到目标API地址。
4) 用我们自己的替换函数的地址代替找到的API地址。
这种方法简单可靠,一般API拦截都采用这种技术。
4 总结
该文文讲述了Windows API拦截的关键技术和方法,重点讲述了拦截dll的注入方法和API拦截方法。但由于篇幅问题本文只是从整体上介绍了API拦截的一些关键技术,具体实现时还要很多问题需要考虑。
参考文献:
[1] 罗云彬.Windows环境下32位汇编语言程序设计[M].北京:电子工业出版社,2002.
[2] Ciro Sisman Pereira.Portable Executable (P.E.) Code Injection:Injecting an Entire C Compiled Application[EB/OL].http://www.codeproject.com.
[3] Jeffrey Ritcher.Load Your 32-bit DLL into Another Process’s Address Space Using INJLIB.MSJ May 1994.
[4] Ivo Ivanov.Detecting Windows NT/2K process execution[EB/OL].http://www.codeproject.com.
[5] Sven Schreiber.Undocumented Windows 2000 Secrets.
[6] Jeffrey Richter.Programming Application for MS Windows.
关键词:API;拦截
中图分类号:TP393文献标识码:A文章编号:1009-3044(2008)27-1920-03
Windows API Hooking Method
SHI Yong-lin,PAN Jin,LI Guo-Peng
(Department one of Xi’an Communication Institute,Xi’an 710016,China)
Abstract:This paper give a detail explanation of how to Intercepting Win32 API,its main concern is the way to inject dll into process’s space and API hooking.It also tell the strong and weak point of the different methods.
Key words:API;hooking
Windows下API(应用程序编程接口,实际上就是Windows系统调用)的拦截是个很有用的技术,现在很多商用的系统都用到了这种技术,如屏幕取词,内码转化,屏幕翻译,中文平台等等都涉及到了此项技术。比如大家熟悉的即时翻译软件,就是靠拦截TextOut()或ExtTextOut()这两个系统API调用实现的,在操作系统用这两个函数输出文本之前,通过拦截这两个调用,把传递给这两个调用的的英文替换成中文,然后再调用原来的API输出,从而达到即时翻译的目的。可以说,掌握了这种技术,从某种意义上讲就可以控制或改变操作系统或其他软件的功能,这也是许多游戏外挂等常用的技术。而且这种技术也被许多病毒和木马等利用,从而达到破坏系统和感染程序的目的,当然现在的反病毒软件也多用到此技术。
1 Windows API拦截技术概述
Windows API拦截的主要目的是在其他应用程序调用API之前将其拦截,由拦截者先处理传递的参数数据,然后决定是否再调用原来的API。比如API
BOOL TextOutA( HDC hdc, int nXStart,int nYStart,LPCTSTR lpString, int cbString);
在其他程序调用这个API之前,拦截程序可以先捕获这个调用,先对参数等进行处理,比如将cbString翻译为中文等,然后再调用原来的TextOutA进行文本输出,这样输出的文本就变成中文了。
API拦截的原理很简单,但是要实现可靠的拦截则需要很多工作要做,首先需要把替换被拦截API的代码注入到目标进程中,这些代码一般是以动态链接库(DLL)的形式存在的,然后修改目标进程执行代码,使其在调用被拦截API之前先调用我们的替代代码。这个过程的示意图如图1所示。

一般拦截程序至少需要两个部分,一个是管理服务器(Management server),它的主要工作是拦截DLL的注入工作,并管理DLL的工作状态,接收DLL发回的处理消息等;另一个是拦截DLL,它主要包含拦截代码以及代码注入和API拦截的一些辅助代码。
总结起来API拦截主要有以下三点工作要做。
1) 要决定拦截哪些进程的API,如果是拦截个别进程的API,那么只需要在这个进程中插入拦截DLL,否则则需要在系统的所有进程中插入拦截DLL。
2) 决定采用哪种DLL注入技术。DLL注入技术有多种,但最常用的就两种,一种是windows全局钩子技术,另外一种是利用CreateRemoteThread() API来进行。我们将在第二节进行重点阐述。
3) 决定采用哪种API拦截机制。这也有多种技术可用,可以在内核层(kernel level)中进行,也可以在用户层(user level)中进行。我们将在第三节对这个问题进行深入阐述。
2 DLL注入技术
代码注入技术分为动态代码注入技术和静态代码注入技术,动态代码注入技术就是在进程启动后或在进程启动时在进程的运行空间中注入代码的技术,而静态注入技术就是在PE格式的.exe文件中插入代码。静态注入技术是病毒感染文件的常用方法,在文献[1]中有详细的叙述。动态注入技术也分为直接代码注入技术和以dll形式的注入技术,直接代码注入技术是利用VirtualAllocEx和CreateRemoteThread两个API来进行的函数级代码注入技术,可以采用汇编的形式,这种方法在文献[1]中的进程隐藏一章中有详细的讲解,也可以采用高级语言如c语言的形式,这种方法有兴趣的可以参考文献[2]。很显然API拦截只能采用动态代码注入技术,由于直接代码注入技术很复杂,灵活性很差,所以一般采用dll形式的动态代码注入技术。
2.1 windows全局钩子dll注入技术
Windows钩子技术是windows提供的windows事件拦截技术,它可以使程序设计者在一些windows事件(如鼠标、键盘事件、窗口创建事件等)发送到特定窗口或线程之前捕获并处理它们。具体钩子的应用技术可以参考Microsoft msdn。
Windows全局钩子可以把一个dll注入到系统中所有的进程空间中,这些都是由操作系统自动完成的。主要操作过程如下:
1) 首先创建一个dll,在其中中输出注册钩子函数,如MouseProc和我们的拦截函数,如MyTextOutA等。
2) 用SetWindowsHookEx函数注册全局钩子。这时操作系统会自动把包含钩子函数的dll映射到所有正在运行的进程空间。
3) 当不需要钩子时,调用UnhookWindowsHookEx()函数卸载钩子,则系统会自动从所有进程空间中卸载钩子dll。
全局钩子的使用是很简单的,只是要注意全局变量共享等问题,要把dll的共享变量放到一个共享区中。
这种方法的优点是操作简单,性能稳定,因为dll的注入和卸载等工作都是由操作系统自动完成的。它的缺点是不能选择性的注入dll到特定的进程中,而且系统会调用我们的无用的钩子函数来处理消息,从而可能会较大的降低系统的性能。
2.2 利用CreateRemoteThread的dll注入技术
这种方法在[3]中有较详细的论述。它的原理也很简单,但实现起来较复杂。要在其他的进程中注入dll,就要求我们能在那个进程中调用LoadLibrary() API,但我们没有权限获得其他进程的执行控制权,幸好微软提供了一个函数CreateRemoteThread()可以在其他的进程中创建远程线程,而恰好线程函数的原型:
DWORD WINAPI ThreadProc(LPVOID lpParameter);
和LoadLibrary()的原型:
HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);
HMODULE和DWORD都是双字节,调用方式都是WINAPI,LPVOID和LPCTSTR都是双字节指针,所以函数原型是一样的。从而我们利用CreateRemoteThread()来欺骗操作系统,使其执行LoadLibrary() API,如下:
hThread = ::CreateRemoteThread(
hProcessForHooking, //要插入dll的进程句柄
NULL,
0,
pfnLoadLibrary,// LoadLibrary函数的地址
"C:\\HookTool.dll", //要注入的dll的全路径
0,
NULL);
可以看出,采用这种方法的一个主要困难是要监视进程的创建和关闭,这样才能确保在所有的目标进程中注入dll,有关这部分的内容可以参考[4]。LoadLibrary函数的地址因为其所在的Kernel32.DLL的映射地址在所有进程中是确定的,所以其值可以通过调用GetProcAddress()获得。
这种方法的优点是可以选择性的在特定进程中注入dll,而且不产生额外的开销,基本不降低系统的性能。缺点是实现起来较复杂,要监视系统进程的活动。
3 API拦截机制技术
API拦截也有多种技术可以采用,从编程层次上可以分为内核级的和用户级的。内核级的拦截技术灵活、可靠,但实现复杂,调试困难,有兴趣的可以参考文献[5]。用户级也有多种技术,如dll替换技术、debug技术、目标API代码修改和修改输入表等,这些方法中比较可靠,应用较广的是目标API代码修改和修改输入表两种技术。
3.1 目标API代码修改
这种技术说起来也不复杂,就是改变程序流程的技术。在CPU的指令里,有几条指令可以改变程序的流程:JMP,CALL,INT,RET, RETF,IRET等指令。理论上只要改变API入口和出口的任何机器码,都可以拦截API,但是实际实现起来要复杂很多,因为要处理好以下问题:
1) CPU指令长度问题,在32位系统里,一条JMP/CALL指令的长度是5个字节,因此你只有替换API里超过5个字节长度的机器码(或者替换几条指令长度加起来是5字节的指令),否则会影响被更改的小于5个字节的机器码后面的数条指令,甚至程序流程会被打乱,产生不可预料的后果;
2) 参数问题,为了访问原API的参数,你要通过EBP或ESP来引用参数,因此你要非常清楚你的拦截代码里此时的EBP/ESP的值是多少;
3) 上下文的问题,有些拦截代码不能执行某些操作,否则会破坏原API的上下文,原API就失效了;
举个例子,假如要拦截的API的指令如下:
:71A21AF4 55 push ebp
:71A21AF5 8BECmov ebp, esp
:71A21AF7 83EC10sub esp, 00000010
......
:71A21B64 E8C7F6FFFF call 71A21230//要修改的代码
......
而我们的替换函数的地址为0x8321A341,则我们可以将地址:71A21B64的代码修改为call 8321A341,在我们的拦截代码执行完后执行jmp 71A21230,再返回原来代码处执行。可以看出,这种方法比较难找这条5字节的CALL指令,计算相对地址也复杂。
这种方法实现起来相当困难,而且灵活性和可靠性都比较差。所以一般不采用这种方法。
3.2 修改输入表
通过修改目标进程的输入表来拦截API是一种可靠、简单而且容易实现的技术。这种技术在文献[6]中有详细的讲解。要实现这种方法需要熟悉PE文件的格式,这些内容可以从文献[1]中获取。具体的实现需要以下几个步骤:
1) 从IMAGE_OPTIONAL_HEADER32结构的第二个IMAGE_DATA_DIRECTORY结构中获得输入表的地址。
2) 根据要替换API所属dll的名字查找IMAGE_IMPORT_DESCRIPTOR表,找到dll对应的IMAGE_IMPORT_DESCRIPTOR结构体。
3) 根据API的名字从IMAGE_IMPORT_DESCRIPTOR结构体中OriginalFirstThunk指向的IMAGE_THUNK_DATA列表中查找相应的API在数组中的序号。然后根据查得的序号在FirstThunk指向的IMAGE_THUNK_DATA列表中相应位置找到目标API地址。
4) 用我们自己的替换函数的地址代替找到的API地址。
这种方法简单可靠,一般API拦截都采用这种技术。
4 总结
该文文讲述了Windows API拦截的关键技术和方法,重点讲述了拦截dll的注入方法和API拦截方法。但由于篇幅问题本文只是从整体上介绍了API拦截的一些关键技术,具体实现时还要很多问题需要考虑。
参考文献:
[1] 罗云彬.Windows环境下32位汇编语言程序设计[M].北京:电子工业出版社,2002.
[2] Ciro Sisman Pereira.Portable Executable (P.E.) Code Injection:Injecting an Entire C Compiled Application[EB/OL].http://www.codeproject.com.
[3] Jeffrey Ritcher.Load Your 32-bit DLL into Another Process’s Address Space Using INJLIB.MSJ May 1994.
[4] Ivo Ivanov.Detecting Windows NT/2K process execution[EB/OL].http://www.codeproject.com.
[5] Sven Schreiber.Undocumented Windows 2000 Secrets.
[6] Jeffrey Richter.Programming Application for MS Windows.