typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;    // 未使用,总为0 

    DWORD   TimeDateStamp;      // 文件创建时间戳
    WORD    MajorVersion;       // 未使用,总为0 

    WORD    MinorVersion;       // 未使用,总为0
    DWORD   Name;               // 指向一个代表此 DLL名字的 ASCII字符串的 RVA
    DWORD   Base;               // 函数的起始序号
    DWORD   NumberOfFunctions;  // 导出函数的总数

    DWORD   NumberOfNames;      // 以名称方式导出的函数的总数

    DWORD   AddressOfFunctions;     // 指向输出函数地址的RVA
    DWORD   AddressOfNames;         // 指向输出函数名字的RVA
    DWORD   AddressOfNameOrdinals;  // 指向输出函数序号的RVA

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;    // 未使用,总为0 

    DWORD   TimeDateStamp;      // 文件创建时间戳
    WORD    MajorVersion;       // 未使用,总为0 

    WORD    MinorVersion;       // 未使用,总为0
    DWORD   Name;               // 指向一个代表此 DLL名字的 ASCII字符串的 RVA
    DWORD   Base;               // 函数的起始序号
    DWORD   NumberOfFunctions;  // 导出函数的总数

    DWORD   NumberOfNames;      // 以名称方式导出的函数的总数

    DWORD   AddressOfFunctions;     // 指向输出函数地址的RVA
    DWORD   AddressOfNames;         // 指向输出函数名字的RVA
    DWORD   AddressOfNameOrdinals;  // 指向输出函数序号的RVA

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

[PE结构解析] 9.导出表 IMAGE_EXPORT_DIRECTORY,dockerexportimage

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;    // 未使用,总为0 

    DWORD   TimeDateStamp;      // 文件创建时间戳
    WORD    MajorVersion;       // 未使用,总为0 

    WORD    MinorVersion;       // 未使用,总为0
    DWORD   Name;               // 指向一个代表此 DLL名字的 ASCII字符串的 RVA
    DWORD   Base;               // 函数的起始序号
    DWORD   NumberOfFunctions;  // 导出函数的总数

    DWORD   NumberOfNames;      // 以名称方式导出的函数的总数

    DWORD   AddressOfFunctions;     // 指向输出函数地址的RVA
    DWORD   AddressOfNames;         // 指向输出函数名字的RVA
    DWORD   AddressOfNameOrdinals;  // 指向输出函数序号的RVA

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

AddressOfFunctions 所指向内容是以 4
字节为一个单位的数组元素,每个元素代表函数入口

AddressOfNames 所指向内容是以 4
字节为一个单位的数组元素,每个元素代表一个针对字符串的 RVA

AddressOfNamesOrdinals 所指向内容是以 2
字节为一个单位的数组元素,每个元素代表对应名字在 AddressOfFunctions
中的序号数。

AddressOfNames 和 AddressOfNamesOrdinals
的数额肯定是同样的,不是同一那么就出错了。

重点要控制二种检索函数入口地址的方式:

ELF&PE 文件结构解析

说简单点,ELF 对应于UNIX 下的文件,而PE 则是Windows
的可执行文件,分析ELF 和 PE
的文书结构,是逆向工程,或者是做调试,甚至是支付所应具备的要旨力量。在展开逆向工程的起来,大家获得ELF
文件,或者是PE
文件,首先要做的就是分析文件头,精晓音信,进而逆向文件。不说废话,伊始分析:

ELF和PE 文件都是基于Unix 的 COFF(Common Object File Format)
改造而来,尤其切实的来说,他是出自当时赫赫出名的 DEC(Digital Equipment
Corporation) 的VAX/VMS 上的COFF文件格式。大家从ELF 说起。

AddressOfFunctions 所指向内容是以 4
字节为一个单位的数组元素,每个元素代表函数入口

AddressOfFunctions 所指向内容是以 4
字节为一个单位的数组元素,每个元素代表函数入口

A. 从序号查找函数入口地址

  1. 定位到PE 文件头
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADER32
    结构中取出数据目录表,并从第二个数据目录中得到导出表的RVA
  3. 从导出表的 Base 字段得到初叶序号
    4.
    将索要寻找的导出序号减去发轫序号Base,得到函数在入口地址表中的索引,检测索引值是不是抢初阶出表的
    NumberOfFunctions 字段的值,如若超出后者的话,表明输入的序号是行不通的
  4. 用那么些索引值在 AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的花色,那就是函数入口地址的RVA
    值,当函数被装入内存的时候,那些RVA
    值加上模块实际装入的基地址,就赢得了函数真正的进口地址

ELF

ELF 文件标准里把系统中应用ELF 格式的文本归类为四种:

  • 可重一贯文件,Relocatable File
    ,那类文件包涵代码和数据,可用来连接成可执行文件或共享目的文件,静态链接库归为此类,对应于Linux
    中的.o ,Windows 的 .obj.
  • 可执行文件,Executable File
    ,那类文件包涵了能够直接执行的次序,它的表示就是ELF
    可执行文书,他们一般没有增加名。比如/bin/bash ,Windows 下的 .exe
  • 共享目的文件,Shared Object File
    ,那种文件包含代码和多少,链接器可以行使这种文件跟任何可重平素文件的共享目的文件链接,爆发新的目的文件。其它是动态链接器可以将多少个这种共享目的文件与可执行文件结合,作为进度影像来运转。对应于Linux
    中的 .so,Windows 中的 DLL
  • 中央转储文件,Core Dump
    File,当进度意外终止,系统可以将该进程地址空间的情节及甘休时的部分音信转存到要旨转储文件。
    对应 Linux 下的core dump。

ELF 文件的一体化布局大体上是那样的:

ELF Header
.text
.data
.bss
… other section
Section header table
String Tables, Symbol Tables,..
  • ELF
    文件头放在最前端,它蕴涵了全方位文件的着力属性,如文件版本,目标机器型号,程序入口等等。
  • .text
    为代码段,也是反汇编处理的一部分,他们是以机器码的款型储存,没有反汇编的长河基本不会有人读懂那几个二进制代码的。
  • .data
    数据段,保存的这么些早已发轫化了的全局静态变量局地静态变量
  • 【澳门金沙国际】PE结构解析,文件结构解析。.bss
    段,存放的是未开端化的全局变量部分静态变量,这一个很不难掌握,因为在未早先化的景色下,大家单独用一个段来保存,能够不在一从头就分配空间,而是在终极总是成可执行文件的时候,再在.bss
    段分配空间。
  • 其余段,还有局地可选的段,比如.rodata 表示那里存储只读数据, .debug
    代表调试音讯等等,具体蒙受可以查阅相关文档。
  • 自定义段,这一块是为了兑现用户卓殊作用而存在的段,方便扩充,比如大家接纳全局变量或者函数从前增加
    **attribute(section(‘name’))** 就可以吧变量或者函数放到以name
    作为段名的段中。
  • 段表,Section Header Table ,是一个要害的有的,它描述了ELF
    文件包涵的兼具段的音讯,比如每个段的段名,段长度,在文件中的偏移,读写权限和一部分段的其余属性。

AddressOfNames 所指向内容是以 4
字节为一个单位的数组元素,每个元素代表一个针对字符串的 RVA

AddressOfNames 所指向内容是以 4
字节为一个单位的数组元素,每个元素代表一个对准字符串的 RVA

B. 从函数名称查找入口地址

我想通的地点,记录下来:用函数名来查找的话,Base
的值现在平昔不任何意义

  1. 先是获得导出表的地址
  2. 从导出表的 NumberOfNames
    字段获得已命名函数的总数,并以那几个数字作为循环的次数来布局一个巡回,从
    AddressOfNames
    字段指向得到的函数名称地址表的首先项初始,在循环上校每一项定义的函数名与要摸索的函数名相相比,即便没有其他一个函数名是吻合的,表示文件中从不点名名称的函数。
    3.
    只要某一项定义的函数名与要寻找的函数名符合,那么记下那几个函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals
    指向的数组中以相同的索引值取出数组项的值,大家那边假若那几个值是 x
  3. 末段,以 x 的值作为索引值在 AddressOfFunctions 
    字段指向的函数入口地址表中获得 RVA 。此 RVA 就是函数的入口地址。

屈居图片:

] 9.导出表
IMAGE_EXPORT_DIRECTORY,dockerexportimage typedef struct
_IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 未使用,总为0
DWORD TimeDateStamp;…

ELF Header

ELF 文件新闻的查看利器在Linux 下是是objdump, readelf,
相关命令较多,可查。下边大家从ELF 文件头说起。

文本头包括的始末很多,大家在Ubuntu 系统下行使 readelf 命令来查看ELF
文件头:

澳门金沙国际 1

咱俩以bash 这一个可执行文件为例,大家得以看出ELF 文件头定义了ELF
魔数,文件机器字节长度,数据存储格局,版本,运行平台,ABI版本,ELF
重定位类型,硬件平台,硬件平台版本,入口地址,程序头入口和长度,段表的任务和长度,段的数量。

ELF 文件头的结构和相关常数一般定义在了 /usr/include/elf.h
中,大家得以进入查看一下:

澳门金沙国际 2

除去首个,其余都是各样对应的,第四个是一个对应了Magic number, Class,
Data, Version, OS/ABI, ABI version.

出现在最开端的ELF Magic number, 16字节是用来标识ELF
文件的平台属性,比如字长,字节序,ELF
文件版本。在加载的时候,首先会认可魔数的不利,不正确的话就拒绝加载。

另一个关键的事物是段表(Section Header Table)
,保存了五花八门段的焦点特性,比如段名,段长度,文件中的偏移,读写权限,段的其它性能。而段表自己在ELF
文件中的地点是在ELF 头文件 e_shoff 决定的。

俺们可以利用 objdump -h 的指令来查看ELF 文件中蕴藏哪些段,以bash
那些可举行为例,其实除了大家事先说的怎么着基本构造,他饱含众多别样的协会:

澳门金沙国际 3

平等的,我们采纳readelf -S 的指令也足以展开查看。

上边我们来看一下构造,仍旧到elf.h 中去查看,他的结构体名字叫
Elf32_Shdr,64位对应Elf64_Shdr,结构如下:

澳门金沙国际 4

如上结构中,分别对应于:

  • 段名
  • 段类型
  • 段标志位
  • 段虚拟地址
  • 段偏移
  • 段长度
  • 段链接
  • 段对齐
  • 项,一些尺寸固定的项,如符号表等。

这个品种,在使用readelf -S 指令时一一对应。

此外还有一个首要的表,叫重定位表,一般段名叫.rel.text,
在上头没有出现,链接器在处理目的文件时,须要对目的文件中的某些地方举办重一向,就是代码段和数码段中这一个对相对地址引用的岗位,这一个时候就需求使用重定位表了。

AddressOfNamesOrdinals 所指向内容是以 2
字节为一个单位的数组元素,每个元素代表对应名字在 AddressOfFunctions
中的序号数。

AddressOfNamesOrdinals 所指向内容是以 2
字节为一个单位的数组元素,每个元素代表对应名字在 AddressOfFunctions
中的序号数。

字符串表

缘何会有字符串表呢?其实这么些也是在不断前行更上一层楼中找到的解决办法,在ELF
文件中,会用到很多的字符串,段名,变量名等等,不过字符串其本人又长度不固定,假若运用固定结构来表示,就会带来空间上的麻烦。所以,构造一个字符串表,将利用的字符串统一放在那里,然后经过偏移量来引用字符串,岂不美哉。

急需拔取的时候,只需要给一个偏移量,然后就到字符串该地方找字符串,遭受\0
就停止。

字符串在ELF 文件中,也是以段的款式保留的,常见的段名 .strtab, .shstrtab
七个字符串分别为字符串表和段表字符串,前者用来保存普通的字符串,后者保存段名。

在大家选拔readelf -h 的时候,我们见到最后一个成员,section header string
table index ,实际上他指的就是字符串表的下标,bash
对应的字符串表下标为27,在动用objdump
的时候,实际上忽略了字符串表,大家接纳readelf
,就足以看出第27位即字符串表:

澳门金沙国际 5


上面我们想起一下,那个ELF 构造的精致之处,当一个ELF
文件到来的时候,系统本来的找到他的开头,得到文件头,首先看魔数,识别基本音信,看是否正确的,或者是可识其他文件,然后加载他的主导音信,包蕴CPU
平台,版本号,段表的地点在哪,还足以获得字符串表在哪,以及整个程序的输入地址。这一多元伊始化音讯得到将来,程序可以通过字符串表定位,找到段名的字符串,通过段表的起头地点,确认种种段的岗位,段名,长度等等新闻,进而到达入口地址,准备实施。

理所当然,那只是开头河的情节,其后还要考虑链接,Import,Export
等等情节,留待将来完善。

AddressOfNames 和 AddressOfNamesOrdinals
的数码肯定是一模一样的,不是一模一样那么就出错了。

AddressOfNames 和 AddressOfNamesOrdinals
的数据肯定是均等的,不是相同那么就出错了。

PE 文件

下边我们去看看更为宽泛的PE 文件格式,实际上PE 与 ELF
文件基本相同,也是行使了基于段的格式,同时PE
也同意程序员将变量或者函数放在自定义的段中, GCC
**attribute(section(‘name’))** 扩大属性。

PE 文件的前身是COFF,所以分析PE 文件,先来看看COFF
的文件格式,他保存在WinNT.h 文件中。

COFF 的文件格式和ELF 大概一毛一样:

Image Header
SectionTable Image_SECTION_HEADER
.text
data
.drectve
.debug$S
… other sections
Symbol Table

文件头定义在WinNT.h 中,大家打开来看一下:

澳门金沙国际 6

我们得以阅览,它这些文件头和ELF
实际上是如出一辙的,也在文件头中定义了段数,符号表的职责,Optional Header
的大大小小,那些Optional Header 前边就观看了,他就是PE
可执行文件的文件头的有些,以及段的习性等。

跟在文件头后面的是COFF 文件的段表,结构体名叫 IMAGE_SECTION_HEADER :

澳门金沙国际 7

特性包罗那几个,和ELF 没差:

  • 段名
  • 物理地址 PhysicalAddress
  • 虚拟地址 VirtualAddress
  • 本来数据大小 Sizeof raw data
  • 段在文书中的地方 File pointer to raw data
  • 该段的重定位表在文书中的地点 File pointer to relocation table
  • 该段的行号表在文书中的地点 File pointer to line number
  • 标志位,包蕴段的连串,对齐方式,读取权限等标志。

第一要控制两种检索函数入口地址的点子:

重点要控制三种检索函数入口地址的章程:

DOS 头

在咱们解析PE 的前头,还有其余一个头要了然一下,DOS
头,不得不说,微软事儿仍然挺多的。

微软在成立PE 文件格式时,人们正在周边运用DOS
文件,所以微软为了考虑包容性的问题,所以在PE 头的最前面还添加了一个
IMAGE_DOS_HEADER 结构体,用来增加已部分DOS EXE。在WinNTFS.h
里可以观望他的身形。

澳门金沙国际 8

DOS
头结构体的深浅是40字节,那里边有多个重大的分子,须求明白,一个是e_magic
又见魔数,一个是e_lfanew,它只是了NT 头的撼动。

对此PE 文件来说,这么些e_magic,也就是DOS 签名都是MZ,据说是一个叫 马克Zbikowski 的开发人士在微软设计了那种ODS 可执行文件,所以…

大家以Windows 下的notepad++
的可执行文件为例,在二进制编辑软件中开辟,此类软件相比多,Heditor 打开:

澳门金沙国际 9

开首的三个字节是4D5A,e_lfanew 为00000108 注意存储顺序,小端。

你认为起始加上了DOS 头就成功了么,就足以跟着接PE 头了么。为了合作DOS
当然不是这么不难了,紧接着DOS 头,跟的是DOS 存根,DOS
stub。这一块就是为DOS 而准备的,对于PE 文件,固然没有它也足以正常运转。

澳门金沙国际 10

旁边的ASCII 是读不懂的,因为他是机器码,是汇编,为了在DOS
下进行,对于notepad++ 来说,那里是推行了一句,this program cannot be run
in DOS mode 然后退出。逗我= =,有新的人,可以在DOS
中开创一个顺序,做一些小动作。

A. 从序号查找函数入口地址

  1. 定位到PE 文件头
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADER32
    结构中取出数据目录表,并从第三个数据目录中得到导出表的RVA
  3. 从导出表的 Base 字段获得初叶序号
    4.
    将急需摸索的导出序号减去初始序号Base,获得函数在输入地址表中的索引,检测索引值是或不是超出导出表的
    NumberOfFunctions 字段的值,借使超出后者的话,表达输入的序号是不行的
  4. 用那个索引值在 AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的项目,这就是函数入口地址的RVA
    值,当函数被装入内存的时候,这么些RVA
    值加上模块实际装入的基地址,就获取了函数真正的入口地址

A. 从序号查找函数入口地址

  1. 定位到PE 文件头
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADER32
    结构中取出数据目录表,并从第四个数据目录中拿走导出表的RVA
  3. 从导出表的 Base 字段得到开始序号
    4.
    将要求寻找的导出序号减去初步序号Base,获得函数在进口地址表中的索引,检测索引值是或不是当先导出表的
    NumberOfFunctions 字段的值,如若超过后者的话,表达输入的序号是于事无补的
  4. 用这一个索引值在 AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的类型,这就是函数入口地址的RVA
    值,当函数被装入内存的时候,这么些RVA
    值加上模块实际装入的基地址,就获得了函数真正的进口地址

NT头

上面进入正题,在HEditor 上也看看了PE,这一块就是正统的步入PE 的范围。

澳门金沙国际 11

澳门金沙国际 ,那是32位的PE
文件头定义,64位对应改。第二个成员就是签字,如我辈所说,就是大家见到的「PE」,对应为50450000h。

此地边有三个东西,第三个就是大家此前看来的COFF
文件头,那里一直放进来了,我们不再分析。

看第三个,IMAGE_OPTIONAL_HEADER
不是说这些头可选,而是里边有些变量是可选的,而且有部分变量是必须的,否则会导致文件无法运行:

澳门金沙国际 12

有那般多少个须求珍重关心的分子,这几个都是文件运行所需求的:

  1. Magic 魔数,对于32结构体来说是10B,对于64结构体来说是20B.
  2. AddressOfEntryPoint 持有EP 的RVA
    值,之处程序先河执行的代码发轫地点,也就是先后入口。
  3. ImageBase 进程虚拟内存的限制是0-FFFFFFFF (32位)。PE
    文件被加载到那般的内存中,ImageBase 指出文件的先行装入地点。
  4. SectionAlignment, FileAlignment PE 文件的Body
    部分区划为多少段,FileAlignment
    之处段在磁盘文件中的最小单位,SectionAlignment指定了段在内存中的最小单位。
  5. SizeOfImage 指定 PE Image 在虚拟内存中所占的空中尺寸。
  6. SizeOfHeader PE 头的轻重缓急
  7. Subsystem 用来不相同系统驱动文件与常见可执行文件。
  8. NumberOfRvaAndSizes 指定DataDirectory
    数组的个数,即使最后一个值,提出个数是16,但实质上PE
    装载仍然经过辨认那个值来确定大小的。至于DataDirectory 是怎么看上面
  9. DataDirectory 它是一个由IMAGE_DATA_DIRECTORY
    结构体组成的数组,数组每一项都有定义的值,里边有局地器重的值,EXPORT/IMPORT/RESOURCE,
    TLS direction 是必不可缺关怀的。

B. 从函数名称查找入口地址

自家想通的地点,记录下来:用函数名来查找的话,Base
的值现在从未任何意义

  1. 第一得到导出表的地点
  2. 从导出表的 NumberOfNames
    字段得到已命名函数的总和,并以那么些数字作为循环的次数来布局一个循环,从
    AddressOfNames
    字段指向得到的函数名称地址表的率先项先河,在循环大校每一项定义的函数名与要寻找的函数名相相比,假如没有其他一个函数名是切合的,表示文件中尚无点名名称的函数。
    3.
    只要某一项定义的函数名与要物色的函数名符合,那么记下这一个函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals
    指向的数组中以平等的索引值取出数组项的值,大家那边假使那一个值是 x
  3. 说到底,以 x 的值作为索引值在 AddressOfFunctions 
    字段指向的函数入口地址表中取得 RVA 。此 RVA 就是函数的进口地址。

沾满图片:

澳门金沙国际 13

B. 从函数名称查找入口地址

本人想通的地方,记录下来:用函数名来查找的话,Base
的值现在从未任何意义

  1. 率先取得导出表的地方
  2. 从导出表的 NumberOfNames
    字段得到已命名函数的总和,并以这几个数字作为循环的次数来社团一个循环,从
    AddressOfNames
    字段指向得到的函数名称地址表的率先项开始,在循环元帅每一项定义的函数名与要寻找的函数名相相比较,假设没有其余一个函数名是符合的,表示文件中从不点名名称的函数。
    3.
    一旦某一项定义的函数名与要物色的函数名符合,那么记下这一个函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals
    指向的数组中以相同的索引值取出数组项的值,大家那边假诺那个值是 x
  3. 末段,以 x 的值作为索引值在 AddressOfFunctions 
    字段指向的函数入口地址表中取得 RVA 。此 RVA 就是函数的进口地址。

沾满图片:

澳门金沙国际 14

段头

PE 的段头直接沿用的COFF 的段头结构,上面也说过了,大家查阅notepad++
的段头,可以收获各类段名,以及其音讯,那里,大家得以选用一些软件查看,越发有利:

澳门金沙国际 15

RVA to RAW

知情PE
最重大的一个片段就是清楚文件从磁盘到内存地址的映照进程,做逆向的人手,唯有熟识地精晓才能跟踪到程序的调用进程和职责,才能分析和查找漏洞。

对于文本和内存的映射关系,其实很不难,他们经过一个简短的公式计算而来:

澳门金沙国际 16

换算公式是如此的:

RAW -PointToRawData = RVA – VirtualAddress

检索进程就是先找到RVA
所在的段,然后依据公式计算出文件偏移。因为大家透过逆向工具,可以在内存中查找到所在的RVA,进而大家就可以统计出在文件中所在的职位,那样,就足以手动进行改动。

看回我们刚刚载入的nodepad++ ,其中的V Addr, 实际上就是VirtualAddress,R
offset 就是PointerToRawData。

澳门金沙国际 17

倘诺大家的RVA 地址是5000,那么合算方法就是,查看区段,发现在.text
中,5000-1000+400 = 4400,那就是RAW
00004400,而事实上,因为大家的ImageBase
是00400000,所以,大家在反编译时候内存中的地址是00405000.

接下去,使大家的PE头中的主题内容,IAT 和 EAT,也就是 Import address
table, export address table.

IAT

导入地址表的情节与Windows 操作系统的中坚进程,内存,DLL
结构有关。他是一种表格,记录了程序行使什么库中的哪些函数。

下边,让大家把眼光转到DLL 上,Dynamic Linked Library 支撑了整套 OS。DLL
的补益在于,不需求把库包蕴在程序中,单独构成DLL
文件,必要时调用即可,内存映射技术使加载后的DLL
代码,资源在多少个经过中落到实处共享,更新库时候假诺替换相关DLL 文件即可。

加载DLL 的不二法门有两种,一种是显式链接,使用DLL
时候加载,使用完释放内存。另一种是隐式链接,程序初叶就共同加载DLL,程序终止的时候才获释掉内存。而IAT
提供的机制与隐式链接相关,最特异的Kernel32.dll。

咱俩来看看notepad++ 调用kernel32.dll 中的CreateFileW, 使用PE
调试工具Ollydbg

澳门金沙国际 18

俺们看来填入参数之后,call 了35d7ffff 地址的内容,然后大家去dump
窗口,找一下kernel.CreateFileW:

澳门金沙国际 19

咱俩双击汇编窗口,启动编制,发现确实是call 的那几个数值:

澳门金沙国际 20

但是问题来了,上面是E8 35D7FFFF,上面地址却是 00C62178。其实这是Win
Visita, Win 7的ASLR
技术,紧要就是针对性缓冲溢出攻击的一种爱戴技术,通过随机化布局,让逆向跟踪者,难以寻找地址,就不便简单的进行溢出攻击。可是还是能通过跳板的章程,找到溢出的章程,那就是后话了。

明天得以规定的是,35D7FFFF 可以认为保存的数值就是 CreateFileW
的地址。而为什么不直接采纳CALL 7509168B 那种办法一向调用呢?
Kernel32.dll 本子各差距,对应的CreateFileW
函数也各差别,为了合营各样环境,编译器准备了CreateFileW
函数的莫过于地址,然后记下DWORD PTR DS:[xxxxxx]
那样的吩咐,执行文书时候,PE 装载器将CreateFileW 函数地址写到那个职分。

还要,由于重一贯的来由存在,所以也不可能直接动用CALL 7509168B
的艺术,比如七个DLL 文件有同样的
ImageBase,装载的时候,一个装载到该任务然后,另一个就不可能装载该职位了,须求换地方。所以大家不可以对实际地址举行硬编码。

IMAGE_IMPORT_DESCRIPTOR

澳门金沙国际 21

澳门金沙国际 22

对此一个日常程序来说,必要导入多少个库,就会设有多少个那样的结构体,这个结构体组成数组,然后数组最后是以NULL
结构体停止。其中有多少个重大的成员:

  • OriginalFirstThunk INT Import Name Table 地址,RVA
  • Name 库名称字符串地址,RVA,就是说该地方保存库名称
  • First Thunk IAT 地址 RVA
  • INT 中个元素的值是上面这些IMAGE_IMPORT_BY_NAME 结构体指针。
  • INT 与 IAT 大小应平等。

那么PE 是如何导入函数输出到IAT 的:

  1. 读取NAME 成员,获取扩名称字符串
  2. 装载相应库: LoadLibrary(“kernel32.dll”)
  3. 读取OriginalFirstThunk成员,获取INT 地址
  4. 读取INT 数组中的值,获取相应的
    IMAGE_IMPORT_BY_NAME地址,是RVA地址
  5. 使用IMAGE_IMPORT_BY_NAME 的Hint 或者是name
    项,获取相应函数的发端地点 GetProcAddress(“GetCurrentThreadId”)
  6. 读取FistrThunk 成员,获得IAT 地址。
  7. 将方面得到的函数地址输入相应IAT 数组值。
  8. 重复4-7 到INT 结束。

这边就发出了一个疑心,OriginalFirstThunk 和 First Thunk
都指向的是函数,为啥多此一举呢?

第一,从直观上说,四个都指向了库中引入函数的数组,鱼C 画的那张图挺直观:

澳门金沙国际 23

OriginalFirstThunk 和 FirstThunk 他们都是几个类型为IMAGE_THUNK_DATA
的数组,它是一个指南针大小的联手(union)类型。
每一个IMAGE_THUNK_DATA
结构定义一个导入函数新闻(即指向社团为IMAGE_IMPORT_BY_NAME
的玩意,这厮稍后再议)。
下一场数组最终以一个内容为0 的 IMAGE_THUNK_DATA 结构作为完成标志。
IMAGE_THUNK_DATA32 结构体如下:

澳门金沙国际 24

因为是Union 结构,IMAGE_THUNK_DATA 事实上是一个双字大小。
规定如下:

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号格局输入,那时候低
31位被当做一个函数序号。

当 IMAGE_THUNK_DATA 值的最高位为
0时,表示函数以字符串类型的函数名艺术输入,那时双字的值是一个
RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。

俺们再看IMAGE_IMPORT_BY_NAME 结构:

澳门金沙国际 25

协会中的 Hint
字段也代表函数的序号,不过这几个字段是可选的,有些编译器总是将它设置为 0。

Name 字段定义了导入函数的名目字符串,这是一个以 0 为最终的字符串。

现行重点来了:

率先个数组(由 OriginalFirstThunk
所指向)是独立的一项,而且不能被改写,我们后边称为 INT。第四个数组(由
FirstThunk 所指向)事实上是由 PE 装载珍惜写的。

PE 装载器装载顺序正如上面所讲的那么,我们再将它讲详细一点:

PE 装载器首先搜索 OriginalFirstThunk
,找到之后加载程序迭代搜索数组中的每个指针,找到每个
IMAGE_IMPORT_BY_NAME
结构所指向的输入函数的地方,然后加载器用函数真正入口地址来顶替由
FirstThunk 数组中的一个进口,由此大家称为输入地址表(IAT).

后续沿用鱼C 的图,就能直观的感受到了:

澳门金沙国际 26

从而,在读取两回OriginalFirstThunk 之后,程序就是凭借IAT
提供的函数地址来运行了。

EAT

搞精通了IAT 的规律,EAT
就好明白了,近年来那篇统计的有点长了,我长话短说。IAT
是导入的库和函数的表,那么EAT
就对应于导出,它使分歧的应用程序可以调用库文件中提供的函数,为了便于导出函数,就必要保留那一个导出信息。

回头看PE 文件中的PE头大家可以看到IMAGE_EXPORT_DIRECTORY
结构体以的地点,他在IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress
的值就是 IMAGE_EXPORT_DIREDCTORY 的原初地方。

澳门金沙国际 27

IMAGE_EXPORT_DIRECTORY结构体如下:

澳门金沙国际 28

那边边一样是这么多少个举足轻重的积极分子:

  • NumberOfFunctions 实际Export 函数的个数
  • NumberOfNames Export 函数中签名的函数个数
  • AddressOfFunctins Export 函数地址数组,数组个数是上边的NOF
  • AddressOfNames 函数名称地址数组,个数是上边的NON
  • AddressOfNameOrdinals Ordinal 地址数组,个数等于上边NON
  • Name 一个RVA 值,指向一个概念了模块名称的字符串。如即使Kernel32.dll
    文件被改名换姓为”Ker.dll”。仍旧可以从那么些字符串中的值得知其在编译时的公文名是”Kernel32.dll”。
  • Base:导出函数序号的伊始值,将AddressOfFunctions
    字段指向的进口地址表的索引号加上那一个起头值就是对应函数的导出
    序号。
    以kernel32.dll 为例,咱们看一下:
![](https://upload-images.jianshu.io/upload_images/30117-6bb373c33a5b9995.jpg)

从上边那几个成员,大家其实可以看到,是有二种办法提需要这么些想调用该库中函数的,一种是直接从序号查找函数入口地址导入,一种是由此函数名来搜寻函数入口地址导入。

先上一个鱼C 的图,方便清楚:

澳门金沙国际 29

上边图,注意一点,因为AddressOfNameOrdinals
的序号应当是从0开始的,不过图中映射的是第三个函数指向的序号1。

俺们分别说一下二种方式:

当已知导出序号的时候

  1. Windows 装载器定位到PE 文件头,
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADER32
    结构中取出数据目录表,并从第二个数据目录中拿走导出表的RVA ,
  3. 从导出表的 Base 字段得到起初序号,
  4. 将索要摸索的导出序号减去开头序号,获得函数在输入地址表中的索引,
  5. 检测索引值是不是高于导出表的 NumberOfFunctions
    字段的值,若是当先后者的话,表达输入的序号是无用的用那些索引值在
    AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的品种,那就是函数入口地址的RVA
    值,当函数被装入内存的时候,那个RVA
    值加上模块实际装入的基地址,就得到了函数真正的输入地址

当已知函数名称查找入口地址时

  1. 从导出表的 NumberOfNames
    字段得到已命名函数的总额,并以这一个数字作为循环的次数来社团一个巡回
  2. 从 AddressOfNames
    字段指向得到的函数名称地址表的第一项初叶,在循环中校每一项定义的函数名与要寻找的函数名相相比较,如若没有其余一个函数名是顺应的,表示文件中绝非点名名称的函数,借使某一项定义的函数名与要摸索的函数名符合,那么记下那么些函数名在字符串地址表中的索引值,然后在
    AddressOfNamesOrdinals
    指向的数组中以同样的索引值取出数组项的值,大家那里如若那么些值是x
  3. 最后,以 x 值作为索引值,在 AddressOfFunctions
    字段指向的函数入口地址表中赢得的 RVA 就是函数的入口地址

一般的话,做逆向或者是写代码都是第二种方式,大家以kernel32.dll
中的GetProcAddress 函数为例,其操作原理如下:

  1. 采取 AddressOfNames 成员转到 『函数名称数组』
  2. 『函数名称数组』中存储着字符串地址,通过比较字符串,查找指定的函数名称,此时数组所以为成为name_index
  3. 利用 AddressOfNameOrdinals 成员,转到那些序号数组
  4. 在ordinal 数组中通过name_index 查找到呼应的序号
  5. 选择AddressOfFunctions 成员,转到『函数地址数组』EAT
  6. 在EAT 中校刚刚得到的ordinal 作为目录,得到指定函数的入口地址

写了那样多,实际上算是对文件结构有了一个入门的认识,至少知道在程序运行进度中,系统是怎么样举办操作和链接的,而更是详实的情节注入运行时压缩,DLL
注入,API 钩取等技巧,就须求在那个基础之上继续打通,所以PE ,ELF
文件结构的剖析是一定重大的。

PS. 参考:
鱼C 讲解PE
文件格式之INT
《Windows PE 权威指南》
《逆向工程基本原理》
《程序员的我修养-链接,装载与库》

相关文章