shuiyu 发表于 2018-1-21 15:26:21

《解密系列-系统篇》第八讲:PE结构详解8

本帖最后由 shuiyu 于 2018-1-21 15:30 编辑

越努力,越幸运。欢迎大家来看我的笔记{:10_297:},不对的请各位大佬指正,谢谢{:10_254:}

具体详情可到查看:http://blog.csdn.net/shuiyu486/article/details/79116713

一、输入表结构
(1)回顾一下,在 PE文件头的 IMAGE_OPTIONAL_HEADER 结构中的 DataDirectory(数据目录表) 的第二个成员就是指向输入表的。而输入表是以一个 IMAGE_IMPORT_DESCRIPTOR(简称IID) 的数组组成。每个被 PE文件链接进来的 DLL文件都分别对应一个 IID数组结构。在这个 IID数组中,并没有指出有多少个项(就是没有明确指明有多少个链接文件),但它最后是以一个全为NULL(0) 的 IID 作为结束的标志。

(2)
IMAGE_IMPORT_DESCRIPTOR(IID) 结构定义如下:(占5个DWORD,即20个字节)
IMAGE_IMPORT_DESCRIPTOR STRUCT
    union
      Characteristics             DWORD   ?
      OriginalFirstThunk      DWORD   ?
    ends
    TimeDateStamp                DWORD   ?
    ForwarderChain                DWORD   ?
    Name                              DWORD   ?
    FirstThunk                        DWORD   ?
IMAGE_IMPORT_DESCRIPTOR ENDS
(3)重要的成员介绍:
第一个成员是一个union结构(联合类型或者说是共用体),在下面两个任选一个(就是说这两个元素不能同时存在),一般是选第二个(即:OriginalFirstThunk)。
Characteristics    DWORD   ?
OriginalFirstThunk      DWORD   ?

OriginalFirstThunk
它指向first thunk,IMAGE_THUNK_DATA,该 thunk 拥有 Hint 和 Function name 的地址。

FirstThunk
它包含由IMAGE_THUNK_DATA定义的 first thunk数组的虚地址,通过loader用函数虚地址初始化thunk。在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。


二、OriginalFirstThunk 和 FirstThunk
(1)看图: (此图表示PE文件在磁盘中时(静态),OriginalFirstThunk(指向INT)和 FirstThunk(指向IAT) 都是指向IMAGE_IMPORT_BY_NAME的,下面会验证)


我们看到:OriginalFirstThunk 和 FirstThunk 他们都是两个类型为IMAGE_THUNK_DATA 的数组,它是一个指针大小的联合(union)类型。每一个IMAGE_THUNK_DATA 结构定义一个导入函数信息(即指向结构为IMAGE_IMPORT_BY_NAME 的家伙,这家伙稍后再议),然后数组最后以一个内容为0 的 IMAGE_THUNK_DATA 结构作为结束标志。

(2) IMAGE_THUNK_DATA 结构的定义如下:
IMAGE_THUNK_DATA STRUC
    union u1
      ForwarderString       DWORD?      ; 指向一个转向者字符串的RVA
      Function                  DWORD?      ; 被输入的函数的内存地址
      Ordinal                  DWORD?      ; 被输入的API 的序数值
      AddressOfData         DWORD?      ; 指向 IMAGE_IMPORT_BY_NAME
    ends
IMAGE_THUNK_DATA ENDS

(3)我们可以看出由于是union结构,所以IMAGE_THUNK_DATA 事实上是一个双字(DWORD)大小。该结构在不同时候赋予不同的意义(伟大神奇不得鸟……)。其实union这种数据结构很容易理解:说白了就是当时穷,能省就省,再说白了,就是几兄弟姐妹轮流穿一条裤子去相亲!理解了吧?哈哈~

(4)那我们怎么来区分何时是何意义呢?
规定如下:
当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。
当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。(演示请看小甲鱼解密系列视频讲座)

(5)IMAGE_IMPORT_BY_NAME 结构仅仅只有一个字型数据的大小,存有一个输入函数的相关信息结构。其结构如下:
IMAGE_IMPORT_BY_NAME STRUCT
    Hint      WORD    ?
    Name      BYTE      ?
IMAGE_IMPORT_BY_NAME ENDS


(6)结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0,Name 字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。


三、输入地址表(IAT)
(1)为什么由两个并行的指针数组同时指向 IMAGE_IMPORT_BY_NAME 结构呢?第一个数组(由 OriginalFirstThunk 所指向)是单独的一项,而且不能被改写,我们前边称为 INT。第二个数组(由 FirstThunk 所指向)事实上是由 PE 装载器重写的(IAT)。

(2)PE 装载器首先搜索 OriginalFirstThunk ,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME 结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,因此我们称为输入地址表(IAT)。所以,当我们的 PE 文件装载内存后准备执行时,刚刚的图就会转化为下图:


(3)上图表示PE文件在内存中时,OriginalFirstThunk 是指向IMAGE_IMPORT_BY_NAME的,而FirstThunk则指向了函数的真正地址,下面会验证~

四、输入表实例分析
(1)由上面的讲解和视频的描述,其实我们都知道了:
在磁盘中(静态)OriginalFirstThunk(指向INT) 和 FirstThunk(指向IAT)最后都是指向IMAGE_IMPORT_BY_NAME的,然后找到IMAGE_IMPORT_BY_NAME中的函数。

在内存中(动态)FirstThunk指向的IAT直接装着函数的真正地址了~

(2)实例验证:在磁盘中(静态)
1.根据数据目录表的第二个成员就是指向输入表的,使用PEInfo工具,找到输入表的RVA(2A000),从而找到输入表的物理地址(28000)(方法过程我就不用我多说了吧{:10_312:} )


2.找到输入表的物理地址后;根据"而输入表是以一个 IMAGE_IMPORT_DESCRIPTOR(简称IID) 的数组组成,每个被 PE文件链接进来的 DLL文件都分别对应一个 IID数组结构"
根据IID结构,我们就可以找到OriginalFirstThunk和 FirstThunk,(一个在结构的第一个,另一个在结构的末尾)
OriginalFirstThunk:2A15C(这是一个RVA),物理地址为:2815C
FirstThunk:2A2AC(这是一个RVA),物理地址为:282AC


3.根据上面得到的物理地址,找到地址上的值(方法同上)
物理地址为:2815C    地址上的值问为:0002A2DC
物理地址为:282AC    地址上的值问为:0002A2DC


4.判断: 现在我们得到的值,其实就是IMAGE_THUNK_DATA 结构的值!根据:
当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。
当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。

现在的两个值都是为:0002A2DC,最高位为0,表示是一个RVA,则又要寻找物理地址(方法同上),得到物理地址为:282DC。根据物理地址我们看到了IMAGE_IMPORT_BY_NAME!!,因为在INT和IAT上的值都是0002A2DC,所以也证明了:OriginalFirstThunk(指向INT) 和 FirstThunk(指向IAT)最后都是指向IMAGE_IMPORT_BY_NAME的



(3)实例验证:在内存中(动态)
1.使用LordPE把我们的实验程序(Hello.exe)在运行中,从内存里dump(转存)出来,得到dumped_hello.exe。(方法自己看视频去{:10_264:} )

2.使用PEInfo工具,找到输入表的RVA(2A000),从而找到输入表的物理地址(2A000)(方法过程我就不用我多说了吧{:10_312:} )

3. 根据IID结构,我们就可以找到 FirstThunk,(一个在结构的第一个,另一个在结构的末尾,这里我们值找FirstThunk)
FirstThunk:2A2AC(这是一个RVA),物理地址为:2A2AC

4.根据上面得到的物理地址,找到地址上的值(方法同上)
物理地址为:282AC    地址上的值问为:77D507EA


5.判断
当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。
证明了:在内存中(动态)FirstThunk指向的IAT直接装着函数的真正地址了~




谢谢小甲鱼带来的视频教程,感谢!! {:10_303:}
本节结束,多谢览阅!
越努力,越幸运。谢谢大家来看我的笔记{:10_297:},不对的请各位大佬指教,谢谢{:10_254:}

我没有加特林 发表于 2024-10-11 13:35:53

老铁忒详细了~
页: [1]
查看完整版本: 《解密系列-系统篇》第八讲:PE结构详解8