深入理解PE结构中的导入表3_一些奇怪的导入表
struct IMAGE_IMPORT_TABLE{
union {
dword Characteristics;
dword OriginalFirstThunk; //RVA 指向INT结构体数组
} INTName;
dword TimeDateStamp; // 时间戳 0 if not bound
dword ForwarderChain; // -1 if no forwarders
dword Name; //RVA 指向导入的dll文件名称
dword FirstThunk; // RVA 指向IAT结构体数组
}
一、 FirstThunk指向的Thun_DATA(IAT)为0
如果FirstThunk指向的Thunk_DATA数据为0,那么操作系统判定为无效的dll,就不会为程序加载这个dll,并且OS操作系统将会解析下一个导入表结构。
OS加载dll的过程:
先找到导入表结构的位置,然后开始循环遍历导入表,先判断导入表的FirstThunk指向的IAT是否为0,如果不为0,则通过Name字段获取dll的名字,然后加载dll,然后通过INT获取函数名称,
然后通过LoadLibrary加载完dll,通过函数名称获取函数地址,然后填到FirstThunk指向的IAT中。
先判断导入表的FirstThunk指向的IAT是否为0,如果不0,则认为该dll无效,就直接循环读取下一个dll的导入表结构。
但是原版的OD、CFF等一些其他的编译、PE工具,不这样判断,那么这样就产生了和操作系统不一样的判断方式。

当FirstThunk指向的Thunk_DATA数据为0时候,一些工具会仍然对读取这个dll,按照原来的方式读取函数名称,获取函数的地址。

原版的OD就有这个问题,原版的od会继续导入无效的dll,并且读取函数名称,获取函数地址。同时会把函数名称显示出来,原版的od显示函数名称,设置读取的长度为260,因此当故意设置的函数名称超长的时候,
这个时候就会出现一种现象,OS不处理,因为OS对于无效的导入表(Thunk_DATA数据)不处理,OS对于超长的dll名称和超长函数名称也不处理。
但是原版的OD和CFF等工具还会处理,会分析显示无效的dll的函数名称等,这样就出现操作系统和工具行为上的不一致,这样就产生反调试的情况。
二、 FirstThunk指向的Thun_DATA(IAT)为非0,瞎填的不正确的其他值

这里我们把IAT表里填的内容,胡乱填,这个时候还是有效

因为,操作系统加载的时候,先检查当OriginalFirstThunk指向的INT表有效的时候,会优先使用INT表,不会检查IAT里面的具体值。
FirstThunk指向的ITA不为0(只要不是0,胡乱填都行),就从Name获取dll名称,LoadLibrary加载dll。然后从INT表获取函数名称,然后在获取函数地址,填进IAT表中。
三、 OriginalFirstThunk的值为0,注意和上边的区别,这里是OriginalFirstThunk里的RVA值为0,不是指向的THUNK_DATA.

这个时候,会认为是一个有效的dll。加载dll的过程,首先判断FirstThunk指向的值不为0,然后读取Name字段中的dll名称,然后LoadLibrary加载dll,准备读取函数名称,发现OriginalFirstThunk的RVA值为0,那么就读取FirstThunk中的值,把IAT表指向的位置作为函数的名称或者序号,然后使用LoadLibrary获取函数指针,然后原地把这个函数地址填充到IAT表中,这个时候导入表又有效果了。
利用od查看IAT表中的地址确实已被修改为函数地址。


因此只要记住三种情况:
1、导入表指向名称的RVA为0,这个时候IAT表里面的内容不能乱填,要以IAT表为准加载dll
2、当导入表指向的IAT表内容为0,那么这个dll无效,无法加载。
3、当导入表指向的IAT表内容为非0,那么这个以INT表为准加载dll。

浙公网安备 33010602011771号