PE知识复习之PE的导入表

一丶简介

  上一讲讲解了导出表. 也就是一个PE文件给别人使用的时候.导出的函数  函数的地址 函数名称 序号 等等.

  一个进程是一组PE文件构成的.  PE文件需要依赖那些模块.以及依赖这些模块中的那些函数.这个就是导入表需要做的.

确定PE依赖那个模块. 确定PE依赖的那个函数.  以及确定函数地址.

总共分为三部分讲解.

  导入表定位位置: 在扩展头中有一个数据目录结构体. 第二项保存的就是导入表的 RVA 以及大小.

如下图所示:

EXE文件.没有导出表.有一个导入表. RVA 是 0x1A1C0  位于节Text中. 虚拟地址位 0x11000  文件偏移为 0x400

转换为 FOA =  1A1C0 - 11000 + 400 = 0x95c0

我们发现在文件中定位导入表的时候都是0,原因是程序加载到内存中.需要用到的时候.操作系统才会往这个地方填写数据.

二丶导入表结构

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 指向IAT结构注释表明了
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 时间戳.
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;                //指向DLL名字的 RVA
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

导入表大小为十进制的20个字节.  16进制的 0x14. 如果以16进制为一行. 则是 一行零4个字节

导入表跟导出表不同.导出表只有一个.里面有子表记录. 而导入表你依赖一个模块.则有一个导入表存在.

导入表结束位置是20个字节的连续为0的数据为结束位置. 也就是导入表最后一项都为0的时候.说明导入表结束了.

对于导入表来说.我们只需要关心三个成员.上面都标红了.

会一一进行讲解.首先从最简单的成员开始.

  2.1 Name成员. 确定依赖的模块的名字是什么

我们说过.一个PE文件.依赖模块. 那么这个成员就是记录了.我要依赖的模块的名字是什么.是一个RVA属性. RVA指向了一个ASCII码字符串.以0结尾.

因为在文件中导入表并没有.所以我们直接在内存中查看.

根据数据目录 导入表位置 0x1A1C0  + ImageBase(0x400000) == 0x41AC0

在内存中的0x41AC0位置.则是导入表的位置. 我们看一下.

导入表大小总共一行零4个字节. 倒数第二个成员则是 Name的 RVA  0x1A4A6

我们可以加上ImageBase 去内存中查看.

可以通过RVA 属性.看到导入表依赖的模块名字就是 VCRUNTIME140D.dll   带有D结尾的.dll说明是调试DLL. 140是编译器版本.说明是

VS2015编译的 .VCRuntime 是运行库 .  说明我们这个程序是一个 Debug版本编译的程序. 并且使用编译器 140版本编译的.

我们查看的这个Name属性.描述的就是 VCRUNTIME140D.dll 这个模块的信息了.如果想看其它依赖的模块就需要查看下一张导入表.

下一张导入表在第一章导入表的下面.最后一项的导入表全部为0.  我们下一张导入表的 依赖模块的模块名称的 RVA 属性是 0x1A75A

VA = Imagebase + RVA = 41A75A

依次查看即可.

   2.2 确定依赖的函数的名称

上面我们讲了Name成员.确定了导入表依赖的DLL的名字.那么我们导入表怎么确定依赖了那些函数那?

这个主要讲解导入表的第一个成员跟最后一个成员.

如下图所示:

第一个成员指向了一个INT 表.最后一个成员指向了一个 IAT表.

INT :: 导入名称表  Improt Name Table

IAT::  导入地址表  Improt Address Table

Name成员直接指向一个 ASC 结尾的字符串.

根据上图所示. 两张表是一样的. 但是所在位置是不一样的名字也不一样.一个叫做 INT 一个叫做IAT

typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

结构体大小:  4个字节. 他是一个联合体.找最大的.

里面有4个成员.为当前的4个字节起了四个名字.  真正有用的是下面两个. 也就是说有的时候需要用第三个成员.

有的时候需要用第四个成员. 而第四个成员是指向一个  IMAGE_IMPORT_BY_NAME的结构的RVA

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //编译器决定,不是空的话,就是函数在导出表中的 函数地址表的导出索引.
CHAR Name[]; //函数名称,0结尾.
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

无论是第一个成员还是最后一个成员.都能确定 我一来的当前模块的那个函数.

为什么需要两个表. 这个下面会将. 首先讲解的就是无论使用那个表.都能找到依赖当前模块的函数.

第一个成员找:

  INT表  INT表是4个字节.最后0结尾.  INT表有多大.就是说依赖这个模块的多少个函数.

  IAT 同上. 0结尾.

那么我们怎么去寻找?

  看这个表的4个字节.  最高位为1那么就是函数的导出序号. 去掉最高位.就是函数的序号. 也就是说我们看的是序号.

  如果最高位不是1,那么找的就是一个 RVA ,一个指向 IMAGE_IMPROT_BY_NAME的结构.

例如下图:

INT 或者 IAT表. 都可以通过最高位判断. 是函数的序号.还是函数的名字.

INT或者IAT就是两种情况, 高位为1, 那么去掉高位就是依赖的函数序号. 不是1, 那么就是一个RVA. 指向了一个  IMAGE_IMPROT_BY_NAME 结构.

以一个导入表为例

INT的 RVA 为 1A2A8 VA = 41A2A8

41A2A8是INT表开始. 每一个是4个字节,以0结尾. 观看第一项. 高位为0,所以 0x1A48E 是一个RVA. 一个指向 IMAGE_IMPROT_BY_NAME 的结构

VA = 41A48E

高位两个字节,是函数在导出表中的导出索引.  后面就是以0结尾的函数名称了.

总结来说: 不管是INT表还是 IAT表. 主要看其高位值,高位为1,那么去掉高位,就是函数的序号. 高位为0.指向一个结构.这个结构保存了函数的导出序号.以及函数名称.

在IMAGE_IMPROT_BY_NAME 结构中的 HINT 如果不是空,那么这个序号(索引) 就是导出表的函数地址表的索引. 我们可以直接拿着这个索引去导出表中获取函数地址.

  

    2.3 确定函数地址

如果我们使用DLL的函数.那么在程序中.调用这个DLL的函数.那么就会生成一个间接Call

比如我们程序调用MessageBoxA

反汇编

跳转过去之后.会看到内存中有一个地址

这个地址才是真正的MessageBox的地址

在我们导入表中,最后一个成员  IAT表.就是上面所说的表,保存了函数地址表.

那么这和我们说的结构是不一样的. IAT不是说跟INT是一样的吗?

PE加载前加载后的区别.

一样是一样的.但是需要分清 PE加载前.还有PE加载后.如果加载前,那么IAT跟INT一样.都可以找到依赖的函数名称.

如果是加载后.也就是在内存中的话.那么IAT表保存的就是函数的地址.

PE加载后如下图:

IAT表保存的就是函数地址了.

从导入表中找到IAT表.

IAT表的RVA 偏移为 0x1A098  VA == 41A098

IAT表中存储了函数地址,4个字节为单位.0x6AD79CF0 就是函数 __Vcrt_loadlibraryExW . INT表中存储的就是 依赖的函数名称.上面我们也看到了.

三丶知识总结

导入表大小为20个字节. 十六进制 0x14 ,一行零4个字节.

  1.导入表重要成员有三个.  INT表. Name表.  IAT表.

    PE加载前.

        INT 表 IAT表相同. 根据INT或者IAT表的高位,高位为1.去掉高位就是函数序号. 高位为0. 那么是一个RVA偏移. 指向函数名称表.

          函数名称表

            HINT  当前函数在导出函数地址表中的索引

            Name  当前函数的名称.

    PE加载后INT 表同上. IAT表变成了存储函数地址的地址表了.

  2. Name 民称表. 直接指向DLL名称文件名. 是一个RVA .注意是直接指向.

  3.INT IAT表.的RVA 都是定位INT IAT表位置. 定位的位置是INT IAT表.这个表存储的才是数据

最新文章

  1. 我为NET狂-----大前端专帖
  2. Linux 之 GCC 和 GDB
  3. nodejs httpserver
  4. MVC5+EF6 入门完整教程五
  5. 1-Spark高级数据分析-第一章 大数据分析
  6. .net平台下垃圾回收机制
  7. asp.net MVC SignalR 与数据库 实时同步显示
  8. Lucky String
  9. JQuery 常用方法基础教程
  10. Codeforces449A Jzzhu and Chocolate && 449B Jzzhu and Cities
  11. TCP/IP协议原理与应用笔记13:底层网络技术之传输介质
  12. 开源Office Word——DocX
  13. 10.python面向对象进阶功能
  14. Linq2DB之研究和探索
  15. LaTeX语法笔记
  16. floor函数
  17. JS两日期相减
  18. Asp.Net 天气 WebService 使用
  19. POJ 1330 Nearest Common Ancestors (LCA,倍增算法,在线算法)
  20. mariadb多实例搭建

热门文章

  1. RSP小组——团队冲刺博客六
  2. 【工作手札】Nginx接口代理可跨域
  3. iOS-ERROR ITMS-90086:"missing 64-bit support...解决办法
  4. 使用PIA查找组件的PeopleSoft导航
  5. vs2012,打开早期版本窗体错误
  6. 别以为真懂Openstack: 虚拟机创建的50个步骤和100个知识点(4)
  7. SSL/TLS握手过程
  8. 二叉查找树(BST)的实现
  9. 分享13道上海尚学堂拿回来的Java面试真题,这些都是Java核心常见问题,想拿OFFER必看!
  10. [Swift]LeetCode248.对称数 III $ Strobogrammatic Number III