一.关于API Hook
API Hook可以通过Hook指定的消息和API来实现进程隐藏,文件隐藏,端口隐藏等。
通过拦截进程API来隐藏进程,通过拦截文件读写API来隐藏文件,通过拦截网络相关API可以隐藏端口。如果这些Hook都针对的是用户模式下的API,那么这就属于内核Hook;如果这些Hook针对的是内核模式下的Hook,这就属于是用户Hook。
举个例子:如果现在我想隐藏进程,通常情况,用户态进程使用CreateToolhelp32Snapshot等API获取进程列表,那么我就Hook这个枚举相关进程的API函数,那么当调用这些API函数的时候,系统会直接通知我们自己的Hook函数而不是这些枚举进程函数,也就是这些函数被拦截了。当执行我们自己的Hook函数的时候我们可以选择性地显示出正在运行的进程,从而最终实现进程的隐藏。
当然了这只是用户态下其中一种通过API Hook实现的进程隐藏方式。一般情况下,在用户态实现进程监控最终都会调用函数ZwQuerySystemInformation。因此,在系统上所有进程中挂钩模块Ntdll.dll中的该函数就能实现进程隐藏。这种方法不再依赖预先对目标进程所使用的API函数信息的获取,适应性更强。它几乎是用户态上最有效的进程隐藏方式。
API HOOK 顾名思义是挂钩API函数,拦截,控制某些API函数的调用,用于改变API执行结果的技术。需要注意的是API HOOK技术与windows下的Messages Hook技术是不同的,在原理上就完全不一样。消息钩子是截获程序中的消息或事件的。 API钩子是拦截目标程序中调用的函数,替换或者修改调用函数的功能。
Hook 技术按照实现原理来分的话可以分为两种:API HOOK和Message HOOK 。
按照作用范围来分可以分为全局HOOk和局部HOOK
API HOOK按照权限来分可以分为内核钩子和用户钩子,分别作用在Ring 0和Ring 3 。
当一个API函数被拦截后我们可以让目标程序执行我们事先准备好的代码,hook API后,执行API函数的流程就变了,也就是我们构造的代码替换了原来的API函数,最后是否要调用原来deAPIng由我们构造的代码来决定。这里判断是否要执行的因素是当前调用API的进程是否为我们编写的木马进程,若是就调用我们自己的构造代码,否则调用指定的API函数。
API HOOk技术在木马编写中用的非常广泛,因为它可是很好地实现木马的隐藏性。例如我们Hook住API函数CreateProcess,改变它执行的流程,那么我们创建的程序可以不在任务管理器中出现,较好地实现了隐藏。
二.API Hook 的实现方式
根据实现的方法可以分为:Inline Hook (内联Hook),IAT Hook(导入表Hook),替换Windows消息处理函数实现Hook等等。
(1)Inline Hook
程序在编译链接后成了二进制代码,我们可以找到需要Hook的函数的地址,然后把这个函数在内存中的二进制代码改为一个JMP指令,令其跳转到执行我们自己构造的函数。
貌似有点难以理解,来看看详细的原理解释:
函数一般都存在于DLL中,当DLL中某个函数被调用后,其所在的DLL将会被映射到进程地址空间中。我们可以通过DLL这个模块找到我们需要Hook的函数的地址。然后在内存中改变其地址,使跳转到我们制定的位置。
如果还不理解,那么来看看一个实例的分析:
我们现在需要Hook 函数CreateFile,这个函数存在于Kernel32.DLL文件中。首先我们必须要知道这个函数在进程中的地址,然后修改这个函数的首地址为JMP MyProc指令。
而MyProc函数可以是API函数,也可以是我们自己构造的函数,如果是我们自己构造的函数,那么我们有两种方法把我们的函数注入进目标进程,那就是通过远程线程注入的两种方法。
(2)IAT Hook
这种Hook技术是通过分析目标程序PE结构,替换目标API在IAT中的地址为钩子函数的地址来实现。
要了解这种Hook技术,首先需要知道IAT和PE文件结构。
PE文件时Windows下可执行文件和DLL等文件的一种规范。实际上,exe文件在磁盘中的映射就是PE格式,可以通过PE工具查看exe文件来进行研究。
当程序被加载的时候,windows加载函数会定位所有的导入数据和代码,则样的实际做法就是将DLL文件映射到进程的地址空间中,实际上这个映射的过程就是通过PE文件的头部信息来实施的,因为PE文件头部中存储了所有需要导入的DLL的模块名称以及导入函数。
实际上IAT存储了进程中所有的导入的DLL和其对应的导入函数的信息,要找到某个导入函数的地址,那么必须要定位到导入表。因为导入表额存储位置就在PE文件头中,所以了解PE文件死非常有必要的。
PE文件的真正头部紧接Dos头,在Dos头的最后一个字段e_lfanew指向的是PE头的地址,在PE Header的结构IMAGE_NT_HEADER中,有一个结构IMAGE_OPTIONAL_HEADER,这个结构中有一个IMAGE_DATA_DIRECTORY(数据目录)类型的数组,其中第二个元素就是存储的导入表的相对虚拟地址和大小,通过这个可以定位到导入表的地址,从而找到对应的函数的地址。
导入表的和结构为IAMGE_IMPORT_DESCRIPTOR ,它的第一个参数OriginFirstThunk和最后一个参数FirstThunk分别指向的是同一种结构IMAGE_THUNK_DATA,但是因为这个结构体中是一个联合结构,所以根据这个DWORD类型的值的不同所表示的意义也不同。OriginFirstThunk指向的这个结构表示的是一个导入序号;而FirstThunk指向的这个结构表示的是函数的名字,这时DWORD的值表示的是一个RVA,并指向个IMAGE_IMPORT_BY_NAME结构。
在装载PE文件的时候,装载器会遍历OriginFirstThunk指向的IMAGE_THUNK_DATA数组,找到每个IAMGE_THUNK_DATA结构中函数所对应的地址,然后加载器用函数真正的入口地址来代替FirstThunk指向的数组,这个地址数组我们称之为IAT(导入地址表)。
介绍了原理,那么现在实现起来就较简单了,我们只需要修改IAT表即可。
那么我们的钩子函数从何而来呢?依然是两种方法可以获得,第一种是API函数;另一种是我们自己构造的函数,而我们构造的函数可以通过直接注入代码到目标进程或通过注入DLL而来导入我们的函数进入目标进程。
PE结构与IAT
导入表是PE文件结构中的一个表结构,它是PE头结构IAMGE_NT_HEADER中的可扩展头结构IMAGE_OPTONAL_HEADER中的结构,它的类型为IMAGE_DATA_DIRECTORY的一个数组,其中导入表是这个数组中的第二个元素。记录的是该目录的相对虚拟地址的起始值和大小。
在可执行文件中使用其他的DLL中的可执行代码或数据时成为导入。当PE文件被加载时,windows加载器会定位所有的导入函数或数据,做法就是将DLL映射到进程的地址空间。当然了,这个映射的过程就是通过PE文件头中的导入表的信息来进行操作的,因为导入表中存放了使用DLL的模块的名称以及导入的函数。
在开始讲解IAT HOOK之前我们很有必要了解导入表的结构,因为IAT HOOk就是通过修改导入表中的参数来进行HOOk 的。
实际上,导入表就是一系列IMAGE_IMPORT_DESCRIPTOR结构组成的,结构的数量取决于程序使用DLL的数目,每一个结构对应一个DLL文件。
导入表的结构定义如下:
Typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
Union
{
DWORD Charatorics ;
DWORD OriginalFirstThunk ;
} ;
DWORD TimeDataStamp ;
DWORD ForwarderChain ;
DWORD Name ;
DWORD FirstThunk ;
} IMAGE_IMPORT_DESCRIPTOR ;
其中有几个字段比较重要:
OriginFirstThunk:该字段指向了导入表的RVA,该表是一个IMAGE_THUNK_DATA的结构体数组。
Name:该字段为DLL名称的指针,该指针也为一个RVA。
FirstThunk:该字段包含了导入地址表(IAT)的RVA,IAT是一个IMAGE_THUNK_DATA的结构体数组。
IMAGE_THUNK_DATA的结构定义如下:
Typedef struct _IMAGE_THUNK_DATA
{
Union
{
PBYTE ForwarderString ;
PWORD Function ;
DWORD Ordinal ;
PIMAGE_IMPORT_BY_NAME AddressOfData ;
} u1 ;
} IMAGE_THUNK_DATA32 ;
这个结构中是一个联合体,那么该结构变只有一个成员变量,该结构体实际上是一个DWORDl 类型的变量。
当IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式导入,而这时第31位被看做为一个导入序号。当IMAGE_THUNK_DATA值的最高位为0时表示函数以函数名字字符串的方式导入,这时DWORD的值表示一个RVA,并指向一个IMAGE_IMPORT_BY_NAME结构。其结构定义如下:
Typedef struct _IMAGE_IMPORT_BY_NAME
{
WORD Hint ;
BYTE Name[1] ;
} IMAGE _IMPORT_BY_NAME , *PIMAGE_IMPORT_BY_NAME ;
参数说明:
(1)Hint :该字段表示该函数在其DLL中的导入表中的符号。
(2)Name : 该字段表示导入函数的函数名,导入函数是以一个ASCII编码的字符串,并以NULL结尾。
图中INT表示的是导入名字表(Import Name Table );而IAT表示的是导入地址表(Import Address Table )。前者记录的是函数名字,后者记录的是函数的地址。
PE 装载器首先搜索 OriginalFirstThunk ,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME 结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,因此我们称为输入地址表(IAT)。所以,当我们的 PE 文件装载内存后准备执行时,刚刚的图就会转化为下图:
好了,导入表介绍完了,下面就是我们最重要的东西了——IAT HOOK。
实际上到这里我们的思路已经很清晰了,只要修改IAT中函数的入口点地址为我们构造的函数的地址,那么程序调用此API时实际上调用的就是我们自己构造的函数。既然要修改IAT中的内容,那么就必须要定位IAT的地址。
上面的过程可以描述为:对于一个PE文件映像,从偏移位置0开始就是Dos Header,在Dos头中我们知道有一个指针e_lfanew指向真正的PE文件头,通过PE文件头可以得到数据目录,而数据目录的第二个元素就是存储的导入表的信息,通过导入表的信息我们可以定位到导入表,其结构为IMAGE_IMPORT_DESCRIPTOR,再通过遍历所有的IMAGE_IMPORT_DESCRIPTOR结构可以得到所有的DLL文件名,通过遍历每个结构的FirstThunk成员可以得到每个函数的地址,通过遍历每个结构的OriginalFirstThunk所指向的IMAGE_IMPORT_BY_NAME可以得到每个函数的导出函数名。
(3)替换Windows消息处理函数实现Hook
在这种方法中,仍然需要使用WriteProcessMemory和CreateRemoteThread函数把挂钩代码注入到目标进程的地址空间中。向远程进程中注入相关的变量和函数。这种方法主要是通过函数SetWindowLong来替换原来的消息处理函数。SetWidowLong还可以为指定的窗口设置其他的信息。
最主要的就是把新的回调函数和其需要的数据注入到指定的进程,并最终能够使用这个回调函数达到Hook的效果。
这些工作都将通过创建的一个远程线程来处理。
因为篇幅有限,在后续的博文中会陆续给出相应的Demo。
转载自原文链接, 如需删除请联系管理员。
原文链接:Hook技术之API拦截(API Hook),转载请注明来源!