IAT(Import Address Table)Hook是一种针对Windows操作系统的API Hooking 技术,用于修改应用程序对动态链接库(DLL)中导入函数的调用。IAT是一个数据结构,其中包含了应用程序在运行时使用的导入函数的地址。
IAT Hook的原理是通过修改IAT中的函数指针,将原本要调用的函数指向另一个自定义的函数。这样,在应用程序执行时,当调用被钩子的函数时,实际上会执行自定义的函数。通过IAT Hook,我们可以拦截和修改应用程序的函数调用,以实现一些自定义的行为,比如记录日志、修改函数参数或返回值等。
IAT Hook的步骤通常包括以下几个步骤:
- 获取目标函数的地址:通过遍历模块的导入表,找到目标函数在DLL中的地址。
- 保存原始函数地址:将目标函数的地址保存下来,以便后续恢复。
- 修改IAT表项:将目标函数在IAT中对应的函数指针修改为自定义函数的地址。
- 实现自定义函数:编写自定义的函数,该函数会在被钩子函数被调用时执行。
- 调用原始函数:在自定义函数中,可以选择是否调用原始的被钩子函数。
该技术常用于实现一些系统级的功能,例如API监控、函数跟踪、代码注入等,接下来笔者将具体分析IAT Hook
的实现原理,并编写一个DLL
注入文件,实现IAT Hook
替换MessageBox
弹窗的功能。
分析导入表结构
在早些年系统中运行的都是DOS
应用,所以DOS
头结构就是在那个年代产生的,那时候还没有PE
结构的概念,不过软件行业发展到今天DOS
头部分的功能已经无意义了,但为了最大的兼容性微软还是保留了DOS
文件头,有些软件在识别程序是不是可执行文件的时候通常会读取PE
文件的前两个字节来判断是不是MZ。
上图就是PE文件中的DOS部分,典型的DOS开头ASCII
字符串MZ
幻数,MZ是Mark Zbikowski
的缩写,Mark Zbikowski
是MS-DOS
的主要开发者之一,很显然这个人给微软做出了巨大的贡献。
在DOS格式部分我们只需要关注标红部分,标红部分是一个偏移值000000F8h
该偏移值指向了PE文件中的标绿部分00004550
指向PE字符串的位置,此外标黄部分为DOS提示信息,当我们在DOS模式下执行一个可执行文件时会弹出This program cannot be run in DOS mode.
提示信息。
上图中在PE字符串开头位置向后偏移1字节,就能看到黄色的014C
此处代表的是机器类别的十六进制表示形式,在向后偏移1个字节是紫色的0006
代表的是程序中的区段数,继续向后偏移1字节会看到蓝色的5DB93874
此处是一个时间戳,代表的是自1970年1月1日
至当前时间的总秒数,继续向后可看到灰色的000C
此处代表的是链接器的具体版本。
上图中我们以PE字符串为单位向后偏移36字节,即可看到文件偏移为120处的内容,此处的内容是我们要重点研究的对象。
在文件FOA偏移为120
的位置,可以看到标红色的地址0001121C
此处代表的是程序装入内存后的入口点(虚拟地址),而紧随其后的橙色部分00001000
就是代码段的基址,其后的粉色部分是数据段基址,在数据基址向后偏移1字节可看到紫色的00400000
此处就是程序的建议装入地址,如果编译器没有开启基址随机化的话,此处默认就是00400000
,开启随机化后建议装入地址与实际地址将不符合。
继续向下文件FOA偏移为130
的位置,第一处浅蓝色部分00001000
为区段之间的对齐值,深蓝色部分00002000
为文件对其值。
上面只简单的介绍了PE结构的基本内容,在PE结构的开头我们知道了区段的数量是6
个,接着我们可以在PE字符串向下偏移244
个字节的位置就能够找到区段块,区块内容如下:
上图可以看到,我分别用不同的颜色标注了这六个不同的区段,区段的开头一般以.xxx
为标识符其所对应的机器码是2E
,其中每个区块分别占用40
个字节的存储空间。
我们以.text
节为例子,解释下不同块的含义,第一处绿色的位置就是区段名称该名称总长度限制在8
字节以内,第二处深红色标签为虚拟大小,第三处深紫色标签为虚拟偏移,第四处蓝色标签为实际大小,第五处绿色标签为区段的属性,其它的节区属性与此相同,此处就不再赘述了。
接着继续看一下导入表,导出表,基址重定位表,IAT表,这些表位于PE字符串向后偏移116个字节的位置,如下我已经将重要的字段备注了颜色:
首先第一处浅红色部分就是导出表的地址与大小,默认情况下只有DLL文件才会导出函数所以此处为零,第二处深红色位置为导入表地址而后面的黄色部分则为导入表的大小,继续向下第三处浅蓝色部分则为资源表地址与大小,第四处棕色部分就是基址重定位表的地址,默认情况下只有DLL文件才会重定位,最下方的蓝色部分是IAT
表的地址,后面的黄色为IAT
表的大小。
此时我们重点关注一下导入表RVA地址 0001A1E0
我们通过该地址计算一下导入表对应到文件中的位置。
计算公式:FOA = 导入RVA表地址 - 虚拟偏移 + 实际偏移 = > 0001A1E0 - 11000 + 400 = 95E0
通过计算可得知,导入表位置对应到文件中的位置是0x95E0
,我们直接跟随过去但此时你会惊奇的发现这里全部都是0,这是因为Windows
装载器在加载时会动态的获取第三方函数的地址并自动的填充到这些位置处,我们并没有运行EXE文件所以也就不会填充,为了方便演示,我们将程序拖入x64dbg
让其运行起来,然后来看一个重要的结构。
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
union
{
DWORD Characteristics;
DWORD OriginalFirstThunk; // 指向导入表名称的RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 默认为0(非重点)
DWORD ForwarderChain; // 默认为0(非重点)
DWORD Name; // 指向DLL名字的RVA
DWORD FirstThunk; // 导入地址表IAT的RVA
} IMAGE_IMPORT_DESCRIPTOR;
该IMAGE_IMPORT_DESCRIPTOR
导入表结构的大小为4*5 = 20
个字节的空间,导入表结构结束的位置通常会通过使用一串连续的4*5
个0
表示结束,接下来我们将从后向前逐一分析这个数据结构所对应到程序中的位置。
通过上面对导入表的分析我们知道了导入表RVA地址为 0001A1E0
此时我们还知道ImageBase
地址是00400000
两个地址相加即可得到导入表的虚拟VA地址0041a1e0
,此时我们可以直接通过x64dbg
的数据窗口定位到0041a1e0
可看到如下地址组合,结合IMAGE_IMPORT_DESCRIPTOR
结构来分析。
如上所示,可以看到该程序一共有3个导入结构分别是红紫黄色部分,最后是一串零结尾的字符串,标志着导入表的结束,我们以第1段红色部分为例,最后一个地址偏移0001A15C
对应的就是导入表中的FirstThunk
字段,我们将其加上ImageBase
地址,定位过去发现该地址刚好是LoadIconW
的函数地址,那么我们有理由相信紧随其后的地址应该是下一个外部函数的地址,而事实也正是如此。