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表.这个表存储的才是数据

PE知识复习之PE的导入表的更多相关文章

  1. PE知识复习之PE的绑定导入表

    PE知识复习之PE的绑定导入表 一丶简介 根据前几讲,我们已经熟悉了导入表结构.但是如果大家尝试过打印导入表的结构. INT IAT的时候. 会出现问题. PE在加载前 INT IAT表都指向一个名称 ...

  2. PE知识复习之PE的重定位表

    PE知识复习之PE的重定位表 一丶何为重定位 重定位的意思就是修正偏移的意思.  如一个地址位 0x401234 ,Imagebase = 0x400000 . 那么RVA就是 1234.  如果Im ...

  3. PE知识复习之PE的节表

    PE知识复习之PE的节表 一丶节表信息,PE两种状态.以及重要两个成员解析. 确定节表位置: DOS + NT头下面就是节表. 确定节表数量: 节表数量在文件头中存放着.可以准确知道节表有多少个. 节 ...

  4. PE知识复习之PE的导出表

    PE知识复习之PE的导出表 一丶简介 在说明PE导出表之前.我们要理解.一个PE可执行程序.是由一个文件组成的吗. 答案: 不是.是由很多PE文件组成.DLL也是PE文件.如果我们PE文件运行.那么就 ...

  5. PE知识复习之PE的各种头属性解析

    PE知识复习之PE的各种头属性解析 一丶DOS头结构体 typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // M ...

  6. PE知识复习之PE合并节

    PE知识复习之PE合并节 一丶简介 根据上一讲.我们为PE新增了一个节. 并且属性了各个成员中的相互配合. 例如文件头记录节个数.我们新增节就要修改这个个数. 那么现在我们要合并一个节.以上一讲我们例 ...

  7. PE知识复习之PE新增节

    PE知识复习之PE新增节 一丶为什么新增节.以及新增节的步骤 例如前几讲.我们的PE文件在空白区可以添加代码.但是这样是由一个弊端的.因为你的空白区节属性可能是只读的不能执行.如果你修改了属性.那么程 ...

  8. PE知识复习之PE扩大节

    PE知识复习之PE扩大节 一丶为什么扩大节 上面我们讲了,空白区添加我们的代码.但是有的时候.我们的空白区不够了怎么办.所以需要进行扩大节. 扩大节其实很简单.修改节数据对齐后的大小即可. 并且在PE ...

  9. PE知识复习之PE的RVA与FOA的转换

    PE知识复习之PE的RVA与FOA的转换 一丶简介PE的两种状态 首先我们知道PE有两种状态.一种是内存展开.一种是在文件中的状态.那么此时我们有一个需求. 我们想改变一个全局变量的初始值.此时应该怎 ...

随机推荐

  1. 洛谷P3802:小魔女帕琪

    题目背景 从前有一个聪明的小魔女帕琪,兴趣是狩猎吸血鬼. 帕琪能熟练使用七种属性(金.木.水.火.土.日.月)的魔法,除了能使用这么多种属性魔法外,她还能将两种以上属性组合,从而唱出强力的魔法.比如说 ...

  2. JS区分对象类型

    Object.prototype.toString.call() 区分对象类型 在JavaScript中数据类型分为:1.基本类型,2.引用类型 基本类型:Undefined,Boolean,Stri ...

  3. 详谈kafka的深入浅出

    第一:kafka的介绍,kafka官网:http://kafka.apache.org/ http://www.jasongj.com/2015/03/10/KafkaColumn1/ kafka的简 ...

  4. JAVA---MYSQL 基本知识点 第二部分

    增删改查 (CRUD):   数据库  , 表  , 记录  ;   约束 ; 主键约束 :primary key  如果是int类型 可以使用 自动增长型  auto_increment; 唯一约束 ...

  5. post数据时报错:远程服务器返回错误: (400) 错误的请求。

    网上查了多种方法,有不少说法,报400说是传的数据格式不对,最后的结论确实是数据格式不对. Content_Type为:application/json,配的数据格式有些麻烦,特别数多层,单层还好.例 ...

  6. 为什么导入本地jquery.js老是无效?(已解决)

    我从jquery官网里复制过来jquery.js内容,然后粘贴到本地,但是引用的时候总是无效. 在翻看脚本所在目录,无意间发现脚本文件是个jquery.js.js,    原来是我的文件的扩展名的问题 ...

  7. 已知一个字符串S 以及长度为n的字符数组a,编写一个函数,统计a中每个字符在字符串中的出现次数

    import java.util.Scanner; /** * @author:(LiberHome) * @date:Created in 2019/3/6 21:04 * @description ...

  8. NFS部署文件共享

    本章解了如何配置网络文件系统(Network File System,NFS)服务来简化Linux系统之间的文件共享工作,以及通过部署NFS服务在多台Linux系统之间挂载并使用资源.在管理设备挂载信 ...

  9. 关于 Senparc.Weixin.Cache.Redis 引用的 StackExchange.Redis 版本不匹配的反馈测试

    推测原因是老系统中有地方引用了旧版本的 StackExchange.Redis,原因是 StackExchange.Redis 1.2.6 版本未提供针对 .net 4.6 以上的支持,导致库引用会失 ...

  10. 使用Java 线程池的利弊及JDK自带六种创建线程池的方法

    1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协 ...