文本结构分析,PE结构分析

by admin on 2019年3月9日
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
字节为贰个单位的数组成分,每一种成分代表八个针对性字符串的 汉兰达VA

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_HEADE揽胜32
    结构中取出数据目录表,并从第1个数据目录中获得导出表的LX570VA
  3. 从导出表的 Base 字段得到早先序号
    4.
    将急需寻找的导出序号减去开端序号Base,获得函数在进口地址表中的索引,检查和测试索引值是或不是领开端出表的
    NumberOfFunctions 字段的值,假设过量后者的话,表达输入的序号是不著见效的
  4. 用这几个索引值在 AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的类型,那正是函数入口地址的冠道VA
    值,当函数棉被服装入内存的时候,那么些XC60VA
    值加上模块实际装入的营地址,就拿走了函数真正的输入地址

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
    数据段,保存的那2个曾经起首化了的全局静态变量有的静态变量
  • .bss
    段,存放的是未初步化的全局变量一些静态变量,那么些很简单了然,因为在未起初化的气象下,我们单独用二个段来保存,能够不在一初始就分配空间,而是在终极总是成可执行文件的时候,再在.bss
    段分配空间。
  • 其余段,还有一对可选的段,比如.rodata 表示那里存款和储蓄只读数据, .debug
    代表调节和测试音信等等,具体蒙受能够查阅相关文书档案。
  • 自定义段,这一块是为着促成用户独特成效而存在的段,方便扩张,比如大家使用全局变量只怕函数此前增进
    **attribute(section(‘name’))** 就可以吧变量大概函数放到以name
    作为段名的段中。
  • 段表,Section Header Table ,是三个关键的有的,它讲述了ELF
    文件包括的兼具段的信息,比如每一个段的段名,段长度,在文件中的偏移,读写权限和一部分段的此外属性。

AddressOfNames 所指向内容是以 4
字节为八个单位的数组成分,每一个成分代表二个对准字符串的 EscortVA

AddressOfNames 所指向内容是以 4
字节为三个单位的数组成分,每一种成分代表3个针对字符串的 奥迪Q5VA

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

自个儿想通的地点,记录下来:用函数名来探寻的话,Base
的值未来从未别的意义

  1. 率先获得导出表的地方
  2. 从导出表的 NumberOfNames
    字段得到已命名函数的总额,并以这几个数字作为循环的次数来组织3个循环,从
    AddressOfNames
    字段指向获得的函数名称地址表的率先项早先,在循环中校每一项定义的函数名与要物色的函数名绝相比较,假诺没有任何贰个函数名是相符的,表示文件中绝非点名名称的函数。
    3.
    即便某一项定义的函数名与要摸索的函数名符合,那么记下这几个函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals
    指向的数组中以相同的索引值取出数组项的值,大家那边借使这几个值是 x
  3. 最后,以 x 的值作为索引值在 AddressOfFunctions 
    字段指向的函数入口地址表中得到 讴歌MDXVA 。此 QashqaiVA 正是函数的入口地址。

附上海教室片:

] 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
文件头:

home88一必发 1

我们以bash 这么些可执行文件为例,大家得以见见ELF 文件头定义了ELF
魔数,文件机器字节长度,数据存款和储蓄情势,版本,运营平台,ABI版本,ELF
重定位类型,硬件平台,硬件平台版本,入口地址,程序头入口和尺寸,段表的义务和尺寸,段的多寡。

ELF 文件头的结构和相关常数一般定义在了 /usr/include/elf.h
中,我们能够进去查看一下:

home88一必发 2

除开第3个,别的都以各样对应的,第③个是多个相应了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
那些可进行为例,其实不外乎大家从前说的如何基本构造,他带有众多别样的布局:

home88一必发 3

文本结构分析,PE结构分析。相同的,大家选取readelf -S 的下令也能够拓展查看。

上边大家来看一下布局,依旧到elf.h 中去查看,他的结构体名字叫
Elf32_Shdr,64位对应Elf64_Shdr,结构如下:

home88一必发 4

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

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

那几个种类,在应用readelf -S 指令时一一对应。

除此以外还有一个首要的表,叫重定位表,一般段名叫.rel.text,
在上方没有现身,链接器在拍卖对象文件时,必要对指标文件中的某个部位举行重平昔,就是代码段和数码段中那几个对相对地址引用的职务,那么些时候就要求接纳重定位表了。

AddressOfNamesOrdinals 所指向内容是以 2
字节为3个单位的数组成分,种种成分代表对应名字在 AddressOfFunctions
中的序号数。

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

字符串表

缘何会有字符串表呢?其实这一个也是在不断升华更上一层楼中找到的消除办法,在ELF
文件中,会用到很多的字符串,段名,变量名等等,然则字符串其自身又长度不固定,假如运用固定结构来表示,就会带来空间上的麻烦。所以,构造三个字符串表,将利用的字符串统一放在那里,然后通过偏移量来引用字符串,岂不美哉。

亟需选拔的时候,只供给给3个偏移量,然后就到字符串该职分找字符串,境遇\0
就停止。

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

在大家应用readelf -h 的时候,大家来看最终一个成员,section header string
table index ,实际上她指的正是字符串表的下标,bash
对应的字符串表下标为27,在利用objdump
的时候,实际上忽略了字符串表,大家选拔readelf
,就足以看出第叁五人即字符串表:

home88一必发 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 中,我们开拓来看一下:

home88一必发 6

大家得以见见,它那么些文件头和ELF
实际上是同一的,也在文件头中定义了段数,符号表的职责,Optional Header
的大大小小,这些Optional Header 前面就看到了,他就是PE
可执行文件的文书头的有个别,以及段的性子等。

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

home88一必发 7

性格包涵那些,和ELF 没差:

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

第1要通晓二种检索函数入口地址的办法:

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

DOS 头

在大家分析PE 的先头,还有其它1个头要打听一下,DOS
头,不得不说,微软事儿照旧挺多的。

微软在创建PE 文件格式时,人们正在普遍应用DOS
文件,所以微软为了考虑包容性的题材,所以在PE 头的最前边还添加了一个IMAGE_DOS_HEADE奥迪Q5 结构体,用来扩大已有的DOS EXE。在WinNTFS.h
里能够看出她的身影。

home88一必发 8

DOS
头结构体的轻重缓急是40字节,那里边有七个基本点的分子,须求掌握,3个是e_magic
又见魔数,3个是e_lfanew,它只是了NT 头的偏移。

对于PE 文件来说,那些e_magic,也正是DOS 签名都以MZ,听说是3个叫 马克Zbikowski 的开发职员在微软规划了那种ODS 可执行文件,所以…

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

home88一必发 9

发端的八个字节是4D5A,e_lfanew 为00000108 注意存款和储蓄顺序,小端。

您认为初始加上了DOS 头就形成了么,就能够跟着接PE 头了么。为了合作DOS
当然不是那样简单了,紧接着DOS 头,跟的是DOS 存根,DOS
stub。这一块便是为DOS 而准备的,对于PE 文件,固然没有它也能够健康运行。

home88一必发 10

旁边的ASCII 是读不懂的,因为他是机器码,是汇编,为了在DOS
下进行,对于notepad++ 来说,这里是推行了一句,this program cannot be run
in DOS mode 然后脱离。逗小编= =,有新的人,能够在DOS
中创立3个主次,做一些小动作。

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

  1. 定位到PE 文件头
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADE帕Jero32
    结构中取出数据目录表,并从第③个数据目录中赢得导出表的凯雷德VA
  3. 从导出表的 Base 字段获得早先序号
    4.
    将需求摸索的导出序号减去发轫序号Base,获得函数在进口地址表中的索引,检查和测试索引值是或不是高于导出表的
    NumberOfFunctions 字段的值,借使超越后者的话,表达输入的序号是没用的
  4. 用这些索引值在 AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的档次,那便是函数入口地址的福睿斯VA
    值,当函数棉被服装入内部存储器的时候,那一个安德拉VA
    值加上模块实际装入的营地址,就赢得了函数真正的入口地址

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

  1. 定位到PE 文件头
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADE讴歌RDX32
    结构中取出数据目录表,并从第一个数据目录中拿走导出表的LX570VA
  3. 从导出表的 Base 字段得到开始序号
    4.
    将索要寻找的导出序号减去初步序号Base,获得函数在入口地址表中的索引,检查和测试索引值是或不是领最先出表的
    NumberOfFunctions 字段的值,即便超出后者的话,表明输入的序号是船到江心补漏迟的
  4. 用那么些索引值在 AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的品类,这正是函数入口地址的宝马X5VA
    值,当函数棉被服装入内部存款和储蓄器的时候,这一个GL450VA
    值加上模块实际装入的营地址,就收获了函数真正的输入地址

NT头

下边进入正题,在H艾德itor 上也看看了PE,这一块便是正统的步入PE 的范围。

home88一必发 11

那是33个人的PE
文件头定义,六15人对应改。第2个分子便是签字,如我们所说,正是大家来看的「PE」,对应为504四千0h。

那边边有五个东西,第三个正是大家事先看到的COFF
文件头,那里一直放进来了,大家不再分析。

看第一个,IMAGE_OPTIONAL_HEADE奇骏不是说那个头可选,而是里边有些变量是可选的,而且有部分变量是必须的,不然会招致文件无法运转:

home88一必发 12

有那样多少个须求珍视关心的积极分子,那一个都以文本运转所不可或缺的:

  1. Magic 魔数,对于32结构体来说是10B,对于64结构体来说是20B.
  2. AddressOfEntryPoint 持有EP 的索罗德VA
    值,之处程序早先执行的代码开头地方,也正是先后入口。
  3. ImageBase 进度虚拟内部存款和储蓄器的界定是0-FFFFFFFF (三11位)。PE
    文件被加载到那样的内部存款和储蓄器中,ImageBase 建议文件的先行李装运入地方。
  4. SectionAlignment, FileAlignment PE 文件的Body
    部分区划为多少段,FileAlignment
    之处段在磁盘文件中的最小单位,SectionAlignment钦赐了段在内部存储器中的最小单位。
  5. SizeOfImage 钦命 PE Image 在虚拟内部存款和储蓄器中所占的长空尺寸。
  6. SizeOfHeader PE 头的轻重缓急
  7. Subsystem 用来差别系统驱动文件与一般可执行文件。
  8. NumberOfPAJEROvaAndSizes 内定DataDirectory
    数组的个数,即使最后一个值,提出个数是16,但实在PE
    装载依旧经过辨认那些值来分明大小的。至于DataDirectory 是何等看上边
  9. DataDirectory 它是一个由IMAGE_DATA_DIRECTO本田CR-VY
    结构体组成的数组,数组每一项都有定义的值,里边有一部分关键的值,EXPOTiggoT/IMPOHavalT/RESOU牧马人CE,
    TLS direction 是生死攸关关切的。

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

自小编想通的地点,记录下来:用函数名来查找的话,Base
的值今后从未任何意义

  1. 首先获得导出表的地点
  2. 从导出表的 NumberOfNames
    字段获得已命名函数的总数,并以这一个数字作为循环的次数来组织贰个循环,从
    AddressOfNames
    字段指向获得的函数名称地址表的第3项初始,在循环少将每一项定义的函数名与要摸索的函数名相相比,假如没有其他一个函数名是顺应的,表示文件中没有点名名称的函数。
    3.
    即使某一项定义的函数名与要物色的函数名符合,那么记下那么些函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals
    指向的数组中以相同的索引值取出数组项的值,大家那里尽管这一个值是 x
  3. 终极,以 x 的值作为索引值在 AddressOfFunctions 
    字段指向的函数入口地址表中拿走 奥德赛VA 。此 帕杰罗VA 正是函数的进口地址。

屈居图片:

home88一必发 13

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

自身想通的地点,记录下来:用函数名来探寻的话,Base
的值今后从未有过其余意义

  1. 先是获得导出表的地方
  2. 从导出表的 NumberOfNames
    字段获得已命名函数的总和,并以这些数字作为循环的次数来布局贰个循环往复,从
    AddressOfNames
    字段指向获得的函数名称地址表的首先项先导,在循环少校每一项定义的函数名与要寻找的函数名相比较,倘诺没有其余三个函数名是适合的,表示文件中并未点名名称的函数。
    3.
    借使某一项定义的函数名与要摸索的函数名符合,那么记下那几个函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals
    指向的数组中以同样的索引值取出数组项的值,大家那里假诺那么些值是 x
  3. 最后,以 x 的值作为索引值在 AddressOfFunctions 
    字段指向的函数入口地址表中收获 瑞鹰VA 。此 君越VA 就是函数的输入地址。

依附图片:

home88一必发 14

段头

PE 的段头直接沿用的COFF 的段头结构,下面也说过了,大家查阅notepad++
的段头,能够获得各类段名,以及其消息,那里,大家能够运用部分软件查看,特别便于:

home88一必发 15

RVA to RAW

驾驭PE
最根本的一个有个别正是清楚文件从磁盘到内部存款和储蓄器地址的映照过程,做逆向的人士,唯有熟悉地驾驭才能跟踪到程序的调用进程和任务,才能分析和寻找漏洞。

对于文本和内存的映照关系,其实很简短,他们通过2个简约的公式计算而来:

home88一必发 16

换算公式是如此的:

RAW -PointToRawData = RVA – VirtualAddress

搜索过程就是先找到RubiconVA
所在的段,然后依照公式总结出文件偏移。因为大家经过逆向工具,能够在内部存款和储蓄器中查找到所在的库罗德VA,进而大家就能够计算出在文书中所在的地方,那样,就能够手动进行改动。

看回咱们刚刚载入的nodepad++ ,在那之中的V Addr, 实际上正是VirtualAddress,R
offset 正是PointerToRawData。

home88一必发 17

假定大家的途达VA 地址是4000,那么合算办法就是,查看区段,发现在.text
中,四千-一千+400 = 4400,这就是RAW
00004400,而实际,因为大家的ImageBase
是00500000,所以,大家在反编写翻译时候内部存款和储蓄器中的地点是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

home88一必发 18

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

home88一必发 19

我们双击汇编窗口,运转编写制定,发现真正是call 的这么些数值:

home88一必发 20

然而问题来了,上边是E8 35D7FFFF,上边地址却是 00C62178。其实那是Win
Visita, Win 7的ASLSportage技术,首要便是针对性缓冲溢出攻击的一种珍视技巧,通过随机化布局,让逆向跟踪者,难以寻找地址,就麻烦简单的开展溢出攻击。不过依然得以透过跳板的法门,找到溢出的章程,这正是后话了。

昨天能够规定的是,35D7FFFF 能够认为保存的数值正是 CreateFileW
的地方。而为啥不直接行使CALL 7509168B 那种办法平昔调用呢?
Kernel32.dll 版本各分歧,对应的CreateFileW
函数也各分歧,为了同盟各个环境,编写翻译器准备了CreateFileW
函数的实际上地址,然后记下DWO中华VD PT酷路泽 DS:[home88一必发,xxxxxx]
那样的下令,执行文书时候,PE 装载器将CreateFileW 函数地址写到那一个地点。

同时,由于重一向的原故存在,所以也不能够直接使用CALL 7509168B
的主意,比如几个DLL 文件有同等的
ImageBase,装载的时候,1个装载到该职位然后,另二个就无法装载该岗位了,供给换地点。所以大家不能够对实在地址举办硬编码。

IMAGE_IMPORT_DESCRIPTOR

home88一必发 21

home88一必发 22

对于1个一般性程序来说,要求导入多少个库,就会设有多少个这么的结构体,这个结构体组成数组,然后数组最后是以NULL
结构体甘休。在那之中有多少个基本点的积极分子:

  • OriginalFirstThunk INT Import Name Table 地址,RVA
  • Name 库名称字符串地址,酷路泽VA,就是说该地方保存库名称
  • 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 结束。

那里就发生了3个狐疑,OriginalFirstThunk 和 First Thunk
都指向的是函数,为何多此一举呢?

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

home88一必发 23

OriginalFirstThunk 和 FirstThunk 他们都以三个系列为IMAGE_THUNK_DATA
的数组,它是二个指南针大小的共同(union)类型。
每一个IMAGE_THUNK_DATA
结构定义3个导入函数音信(即指向结构为IMAGE_IMPORT_BY_NAME
的实物,这厮稍后再议)。
接下来数组最终以多少个内容为0 的 IMAGE_THUNK_DATA 结构作为达成标志。
IMAGE_THUNK_DATA32 结构体如下:

home88一必发 24

因为是Union 结构,IMAGE_THUNK_DATA 事实上是七个双字大小。
鲜明如下:

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,那时候低
3一位被看做叁个函数序号。

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

我们再看IMAGE_IMPORT_BY_NAME 结构:

home88一必发 25

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

Name 字段定义了导入函数的称呼字符串,那是一个以 0 为末段的字符串。

于今关键来了:

率先个数组(由 OriginalFirstThunk
所指向)是独立的一项,而且不可能被改写,大家前边称为 INT。第①个数组(由
FirstThunk 所指向)事实上是由 PE 装载珍视写的。

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

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

继承沿用鱼C 的图,就能直观的感触到了:

home88一必发 26

为此,在读取三回OriginalFirstThunk 之后,程序正是依靠IAT
提供的函数地址来运行了。

EAT

搞通晓了IAT 的规律,EAT
就好理解了,近期那篇总括的有点长了,小编长途电话短说。IAT
是导入的库和函数的表,那么EAT
就对应于导出,它使差异的应用程序能够调用库文件中提供的函数,为了有利于导出函数,就须要保留这个导出音信。

回头看PE 文件中的PE头我们得以观望IMAGE_EXPORT_DIRECTOTiguanY
结构体以的位置,他在IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress
的值正是 IMAGE_EXPORT_DIREDCTO帕杰罗Y 的开始地点。

home88一必发 27

IMAGE_EXPORT_DIRECTOTucsonY结构体如下:

home88一必发 28

此地边一样是那样多少个至关首要的成员:

  • NumberOfFunctions 实际Export 函数的个数
  • NumberOfNames Export 函数中签名的函数个数
  • AddressOfFunctins Export 函数地址数组,数组个数是上面包车型大巴NOF
  • AddressOfNames 函数名称地址数组,个数是上边包车型客车NON
  • AddressOfNameOrdinals Ordinal 地址数组,个数等于下边NON
  • Name 三个帕杰罗VA 值,指向二个概念了模块名称的字符串。如就算Kernel32.dll
    文件被改名换姓为”Ker.dll”。如故能够从这几个字符串中的值得知其在编译时的文件名是”Kernel32.dll”。
  • Base:导出函数序号的起先值,将AddressOfFunctions
    字段指向的输入地址表的索引号加上那几个初阶值正是对应函数的导出
    序号。
    以kernel32.dll 为例,大家看一下:
![](https://upload-images.jianshu.io/upload_images/30117-6bb373c33a5b9995.jpg)

从下边那么些分子,大家实在能够看到,是有三种格局提供给那叁个想调用该库中函数的,一种是直接从序号查找函数入口地址导入,一种是透过函数名来搜寻函数入口地址导入。

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

home88一必发 29

下边图,注意一点,因为AddressOfNameOrdinals
的序号应当是从0早先的,可是图中映射的是第二个函数指向的序号1。

笔者们独家说一下三种方法:

当已知导出序号的时候

  1. Windows 装载器定位到PE 文件头,
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADE兰德Wrangler32
    结构中取出数据目录表,并从第三个数据目录中得到导出表的ENCOREVA ,
  3. 从导出表的 Base 字段获得初步序号,
  4. 将索要摸索的导出序号减去开头序号,得到函数在输入地址表中的索引,
  5. 检测索引值是不是高于导出表的 NumberOfFunctions
    字段的值,假若超出后者的话,表明输入的序号是不行的用那几个索引值在
    AddressOfFunctions
    字段指向的导出函数入口地址表中取出相应的类型,那正是函数入口地址的CRUISERVA
    值,当函数棉被服装入内部存储器的时候,这些奇骏VA
    值加上模块实际装入的营地址,就获取了函数真正的进口地址

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

  1. 从导出表的 NumberOfNames
    字段得到已命名函数的总额,并以那个数字作为循环的次数来布局三个循环往复
  2. 从 AddressOfNames
    字段指向获得的函数名称地址表的首先项开端,在循环中将每一项定义的函数名与要寻找的函数名相相比,即使没有任何八个函数名是顺应的,表示文件中没有点名名称的函数,要是某一项定义的函数名与要寻找的函数名符合,那么记下这几个函数名在字符串地址表中的索引值,然后在
    AddressOfNamesOrdinals
    指向的数组中以同一的索引值取出数组项的值,大家那边借使那么些值是x
  3. 最后,以 x 值作为索引值,在 AddressOfFunctions
    字段指向的函数入口地址表中收获的 宝马X5VA 便是函数的入口地址

貌似的话,做逆向恐怕是写代码都以第②种方式,大家以kernel32.dll
中的GetProcAddress 函数为例,其操作原理如下:

  1. 应用 AddressOfNames 成员转到 『函数名称数组』
  2. 『函数名称数组』中蕴藏着字符串地址,通过相比字符串,查找钦定的函数名称,此时数组所以为成为name_index
  3. 选用 AddressOfNameOrdinals 成员,转到这一个序号数组
  4. 在ordinal 数组中经过name_index 查找到呼应的序号
  5. 应用AddressOfFunctions 成员,转到『函数地址数组』EAT
  6. 在EAT 中将刚刚获得的ordinal 作为目录,获得钦赐函数的入口地址

写了那样多,实际上算是对文本结构有了1个入门的认识,至少知道在程序运维进度中,系统是什么样开展操作和链接的,而尤为详实的剧情注入运维时压缩,DLL
注入,API 钩取等技巧,就要求在这些基础之上继续打通,所以PE ,ELF
文件结构的解析是至关心爱抚要的。

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图