PE结构:导入表中的双桥结构

导入表的知识曾经整理过,网址如下;现再对其中的双桥结构进行整理。
《PE文件:导入表》:https://blog.csdn.net/weixin_43742894/article/details/105155489

导入表结构:

struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics; 
        DWORD   OriginalFirstThunk; //指向INT    桥1    
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;         //时间戳
    DWORD   ForwarderChain;         
    DWORD   Name;				   //dll名称
    DWORD   FirstThunk;            //指向IAT    桥2
} IMAGE_IMPORT_DESCRIPTOR;

OriginalFirstThunk
+0000h,双字。因为它是指向另外数据结构的通路,因此简称为桥1.该字段指向一个包含一系列结构的数组。
指向的数组中的每个结构定义了一个导入函数的信息,最后以一个全0的结构作为结束。指向的数组中每一项为一个结构,此结构的名称是IMAGE_THUNK_DATA。该结构实际上只是一个双字,但在不同的时刻却拥有不同的解释。
该字段有两种解释:

双字最高位为0,表示导入符号是一个数值,该数值是一个RVA。

双字最高位为1,表示导入符号是一个名称。

FirstThunk

+0010h,双字。与OriginalFirstThunk相同,它指向的连接表定义了对Name1这个动态链接库引入的所有导入函数,简称桥2。

双桥结构:
其中OriginalFirstThunk和FirstThunk指向相同的结构体_IMAGE_THUNK_DATA。这俩个结构体又分别指向同一个结构体IMAGE_THUNK_DATA,他们俩个又同时指向IMAGE_IMPORT_BY_NAME,这也就是双桥结构。

struct _IMAGE_THUNK_DATA32{
    union {
        DWORD ForwarderString; //指向一个转向者字符串的RVA
        DWORD Function ;       //被输入的函数的内存地址
        DWORD Ordinal ;        //被输入的API的序数值
       DWORD  AddressOfData ;  //高位为0则指向IMAGE_IMPORT_BY_NAME 结构体二
    }u1;
}IMAGE_THUNK_DATA32;

这个结构的重要成员有:

Function:输入函数的内存地址。
Ordinal :被输入的API的序数值。
AddressOfData:指向IMAGE_IMPORT_BY_NAME

typedef struct _IMAGE_IMPORT_BY_NAME{
    WORD Hint;      //序号
    BYTE NAME[1];   //函数名
}IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

Hint:

+0000h,双字。函数的编号,在DLL中对每个函数都进行了编号,访问函数时可以通过名称访问,也可以通过编号访问。
Name1:

+0004h,大小不确定。函数名字字符串的具体内容,以“\0”作为字符串结束标志。

在文件中尽管通过桥2和桥1指向的数据值相同,但其实存储的位置却是不同的。桥1指向的INT与桥2指向的IAT内容完全一样,但INT和IAT却存储在文件的不同位置。

双桥结构图示:
在这里插入图片描述
桥1和桥2最终指向了一个目的地,都指向了引入函数的IMAGE_IMPORT_BY_NAME描述部分。而从桥2到目的地的过程中,还经历了另外一个很重要的结构IAT。

INT: 桥1,最高位为0,这是一个RVA,表明函数是以字符串类型的函数名导入的先将RVA转换为FOA,值为0x00000654,从文件的该位置开始读取双字,知道去除的双字为“0”结束。每一个双字都 是结构IMAGE_THUNK_DATA。

差异:
在内存中,桥1可以让你找到调用的函数名称或函数的索引编号,桥2却可以帮助你找到该函数指令代码在内存空间的地址。
导入表与IAT的关系如下:
当PE被加载进虚拟地址空间以后,IAT的内容会被操作系统更改为函数的VA。这个修改最终导致通向“值-名称”描述的桥2发生断裂,如
在这里插入图片描述
当桥2发生断裂后,如果没有桥1作为参照(因为桥1和桥2维护了两个一一对应的函数RVA),我们就无法重新找到该地址到底是调用了那个函数。这就是为什么在导入表数据结构中存在两个桥的原因,也是为什么单桥导入表结构中无法实施绑定的原因。

双桥断裂的过程:
在PE加载的时候,双桥结构会断裂,IAT 会被PE加载器重写,PE加载器先搜索INT,PE加载器迭代搜索INT数组中的每个指针,找出 INT所指向的IMAGE_IMPORT_BY_NAME结构中的函数在内存中的真正的地址,并把它替代原来IAT中的值。
也就是当程序加载到内存以后,导入表部分发生变化的值正是IMAGE_IMPORT_DESCRIPTOR结构中的FirstThunk字段指向的函数指针表内容。
这些内容已经不是指向函数名的指针了,而是指向了虚拟内存中该函数的可执行代码的地址所以其含义也由原来的函数指针更改为函数的入口地址。现在看来,所有的这些值最终都指向了同一片连续的区域,从而形成了我们常说的IAT。

总结:

1.在内存中,桥1可以让你找到调用的函数名称或函数的索引编号;桥2却可以帮助你找到该函数指令代码在内存空间的地址。
2.IAT原本指向函数名的指针,双桥结构断裂后,指向了虚拟内存中该函数的可执行代码的地址。
3.为什么要有INT和IAT俩个桥:当桥2发生断裂后,如果没有桥1作为参照(因为桥1和桥2维护了两个一一对应的函数RVA),我们就无法重新找到该地址到底是调用了那个函数。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章