获取中...

-

Just a minute...

总结一下pe文件

Microsoft为了让程序在Windows上可移植而做的一种文件格式规定。比如exe就是一种PE文件,再印象中Windows操作系统都能执行exe程序,这就是Microsoft做的让程序在Windows平台上实现移植的功能,这个移植功能的实现是因为规定了exe的格式,Windows在执行exe程序时,PE文件加载器会按照约定加载exe程序,所以程序就能正常运行了。

PE文件的基本结构

PE文件的基本结构是PE头和PE体
PE头又分为DOS头,DOS存根,NT头,节区头。

DOS头

DOS头文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD e_magic; // Magic number设置ox5A4D ascii码值为'MZ'(标志,不会变的标志)
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; //PE头文件的偏移地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS头是个结构体(IMAGE_DOS_HEADER),在32位系统中占64个字节。
其中首尾有两个重要的结构体成员e_magice_lfanew
e_magic表示这是一个DOS头,用两个字节4D5A,ASCII码为’MZ’
e_lfanew标注NT头的偏移,通常情况下32位系统下e_lfanew的值通常为000000E0
64位系统下e_lfanew的值通常为000000F0

DOS存根

DOS存根是可选项,可有可无。在DOS头下方,大小不固定,代码与数据混合。用来在dos环境下执行文件。

NT头

1
2
3
4
5
6
typedef struct _IMAGE_DOS_HEADER
{
DWORD Signature; //PE Signature : 50450000("PE"00)
IMAGE_FILE_HEADER FileHeader; //文件头结构体
IMAGE_ OPTIONAL_HEADER32 OptionalHeader; //可选头结构体
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER32;

NT头也是一个结构体。它有三个成员。
第一个成员是“签名”成员占4个字节,类似于DOS头的“MZ”。
第二个成员本身是一个结构体,叫做文件头:file header。
第三个成员本身也是一个结构体,叫做可选头:OptionalHeader

文件头本身有七个成员(四个重要成员):

1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_DOS_HEADER
{
WORD Machine; //每个CPU都拥有唯一的machine码
WORD NumberOfSections; //节区数量,当定义节区数与实际不同时会发生错误
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER32结构体的大小,固定的
WORD Characteristics; //文件属性,0x0002h为可执行文件,0x2000h为DLL文件
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEAD

Machine:两个字节,标识CPU型号。
NumberOfSections:标识节区数目,两个字节。
SizeOfOptionalHeader:标识可选头的大小
Characteristic:标识文件属性,bit OR的形式,每一位代表一种信息,两个字节。
其他三个成员都是四个字节。

可选头的成员比较多,以可选头起始位置为基准,使用偏移量(十六进制)来描述重要成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //标志字(32位时0x10Bh)
BYTE MajorLinkerVersion; //连接器版本号
BYTE MinorLinkerVersion; //
DWORD SizeOfCode; //代码段大小
DWORD SizeOfInitializedData; //已初始化数据块大小
DWORD SizeOfUninitializedData; //未初始化数据块大小
DWORD AddressOfEntryPoint; //EP的RVA值,程序最先执行代码的地址
DWORD BaseOfCode; //代码段起始RVA
DWORD BaseOfData; //数据段起始RVA
DWORD ImageBase; //PE文件的装载地址
DWORD SectionAlignment; //块对齐,节区在内存中最小单位
DWORD FileAlignment; //文件块对齐,节区在文件中的最小单位
WORD MajorOperatingSystemVersion;//所需操作系统版本号
WORD MinorOperatingSystemVersion;//
WORD MajorImageVersion; //用户自定义版本号
WORD MinorImageVersion; //
WORD MajorSubsystemVersion; //win32子系统版本。若PE文件是专门为Win32设计的
WORD MinorSubsystemVersion; //该子系统版本必定是4.0否则对话框不会有3维立体感
DWORD Win32VersionValue; //保留
DWORD SizeOfImage; //内存中整个PE映像体的尺寸
DWORD SizeOfHeaders; //所有头+节表的大小,即整个PE头的大小
DWORD CheckSum; //校验和
WORD Subsystem; //NT用来识别PE文件属于哪个子系统(系统驱动、GUI、CUI)
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //指定DataDirectory数组的个数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

可选头的成员比较多,我们以可选头起始位置为基准,使用偏移量(十六进制)来描述重要成员。
Magic成员,32位系统可选头是010B,64位是020B。
AddressOfEntryPoint,标识PE文件的入口点地址(这个地址是相对虚拟地址)。
ImageBase成员,用来标识装载到内存时文件的起始位置。
SectionAlignment成员,标识节区在内存中的最小单位。
FileAlignment成员,标识节区在文件中的最小单位。
成员SizeOfImage,标识着文件加载到内存之后,在内存中的大小。
成员SizeOfHeaders,标识着PE头的大小,同时也就标识着第一节区头的偏移量。
成员Subsystem,标识着文件的类型,1表示驱动文件(sys)2表示窗口应用程序3表示控制台应用程序。
成员NumberOfRvaAndSizes,标识着DataDirectory成员的个数。
最后一个成员是一个结构体数组:DataDirectory,每个数组成员的大小是8个字节,成员的数目由NumberOfRvaAndSizes来给出,默认是16个。

节区头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 8个字节的节区名称
union {
DWORD PhysicalAddress;
DWORD VirtualSize; //内存中节区的大小
} Misc;
DWORD VirtualAddress; // 内存中节区的起始地址(RVA)
DWORD SizeOfRawData; // 磁盘中文件中节区所占大小
DWORD PointerToRawData; // 磁盘中文件的起始位置
DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用地)
WORD NumberOfRelocations; // 在OBJ文件中使用,重定位项数目
WORD NumberOfLinenumbers; // 行号表中行号的数目
DWORD Characteristics; // 节属性如可读,可写,可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

节区头定义了各个节区的属性。将代码、数据资源分为不同的节区存放的好处是保证程序的安全性。节区头是由结构体数组组成的。每个数组元素对应一个节区。每个数组元素都是一个结构体,每个结构体内成员个数是一样的。
第一个成员是name,标识着节区名字,默认为八个字节,可以变更。
第二个成员是一个联合体,物理地址PhysicalAddress和虚拟空间大小VirtualSize,四个字节(两个成员存储任意一个,这里一般存储后一个),VitualSize标识着该节区在内存中的大小。
第三个成员位虚拟地址,VirtualAddress,占用四个字节;
第四个成员SizeOfRawData,标识着该节区在磁盘中的大小,四个字节。
第五个成员PointerToRawData,四个字节。
第三、五个成员的值一般与可选头中SectionAlignment和FileAlignment成员的值的整数倍。

VA,RVA,RAW

RVA: 相对虚拟地址:是指相对于基地址的偏移量。也就是PE结构被映射到内存中后,某个位置所在内存相对于基地址的偏移。即:相对虚拟地址(RVA)=虚拟地址(VA)-基址(imageBase)。

基地址(imageBase): 文件执行时将被映像到指定内存地址中,这个初始内存地址称为基址。

VA(虚拟地址):由于windows运行在保护模式下,所以程序访问存储器所使用的逻辑地址就是偏移地址,简称VA,又称为内存偏移地址。也就是程序被加载到内存中的地址。

RAW:文件偏移

虚拟内存地址 VA (virtual Address)

就是PE文件在内存中的地址,因为用的是虚拟内存技术来管理内存,所以叫做虚拟内存,而物理内存是系统已经帮我们封装好了,所以我们不需要去管物理内存。
在od中打开一个PE文件,这些都是VA

相对虚拟地址 RVA(Relative Virtual Address)

相对虚拟地址是内存地址基于基地址的偏移,规则是VA=IMage Base+RVA

假设虚拟地址VA为00401518,要想知道相对偏移RVA,就要知道00401528属于那个节区,点M查看,是.text 也就是代码区,从00401000开始的,即RVA=00401518-00401000=518

文件偏移地址(File Offset)

数据在PE文件的偏移位置,通俗的讲就是文件的第几个字节,比如文件偏移地址为0x400,那么指的就是文件的第0x400个字节的地方。

基地址(Image Base)

PE装入内存的地方,相当于使用fread()函数把PE文件读入内存,而基地址就相当于PE在内存中的首地址,默认exe文件在内存中的基地址是0x00400000(不一定是,站长的32位的Win8的基地址就不是,一般为0x00EB0000),dll文件是0x10000000。

相对虚拟地址(Relative Virtual Address,RVA)

相对虚拟地址是内存地址基于基地址的偏移,规则是VA=IMage Base+RVA

物理地址偏移(Raw Offset,RO)

这个一般是相对于PE文件在文件中的各区块而言的文件偏移地址。我觉得简单来说这就是一个特殊的File Offset

VA to RAW

想要知道在文件中的偏移,首先要通过VA计算出RVA(如上所示),在通过RAV计算RAW,这时就需要查看区段表了

VOffset是内存中的偏移
VSize是内存中的大小
ROffset是文件中的偏移
RSize是文件中的大小
则RAW=400+518=918

IAT导入地址表/EAT机制

DLL

DLL是动态链接库,是win特有的系统,16位的DOS时代不存在DLL概念,只有”库”,当需要使用库函数时,编译器会将库函数一起插入到应用程序中。当运行多个程序时,会造成资源浪费,于是引入了动态链接库,内存映射技术使得加载后的DLL代码、资源在多个进程中实现共享。

加载DLL的两种方法
1.显示链接:程序使用DLL的时候加载,使用完毕释放内存。
2.隐式链接:程序开始时即一同加载DLL,程序终止时在释放内存。

IMAGE_IMPORT_DESCRIPTOR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //INT(Import Name Table) address (RVA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; //library name string address (RVA)
DWORD FirstThunk; //IAT(Import Address Table) address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //ordinal
BYTE Name[1]; //function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;</span>

结构体中记载了PE文件要导入哪些库文件,每个结构体中包含一个库文件的INT地址数组、Name数组和IAT地址数组,以NULL结束。

OriginalFirstThunk INT的地址
Name 库名称字符串的地址
FirstThunk IAT的地址
INT与IAT是长整型(4个字节数据类型)数组,以NULL结束
INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值)
INT与IAT的大小应相同

IAT装载顺序

  1. 读取IID的Name成员,获取库名称字符串(如“Kernel32.dll”)
  2. 装载相应库,通过LoadLibrary(“Kernel32.dll”)
  3. 读取IID的OriginalFirstThunk成员,获取INT地址
  4. 逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址
  5. 使用IMAGE_IMPORT_BY_NAME的HINT或NAME项,获取相应函数的起始地址,通过GetProcAdress(“GetCurrentThreadld”)
  6. 读取IID的FirstThunk成员,获得IAT地址
  7. 将上面获得的函数地址放入相应的IAT数组值
  8. 重复4-7步骤,将所有INT装载(以NULL结束)

    EAT

    EAT是一种核心机制,使不同的应用程序可以调用库文件中提供的函数。
    也就是说,要通过EAT才能求得从相应库中导出函数的起始地址。

    IMAGE_EXPORT_DIRECTORY

    PE文件内的IMAGE_EXPORT_DIRECTORY结构体保存着导出信息,PE文件的PE头通过查找IMAGE_EXPORT_DIRECTORY结构体的位置,也就时结构体数组的起始位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct_IMAGE_DIRECTORE
{
DWORD Characteristics
DWORD TimeDateStamp ; 文件生成时间
WORD MajorVersion ; 未使用,总是定义为0
WORD MinorVersion ; 未使用,总是定义为0
DWORD Name ; 模块的真实名称
DWORD Base ; 基数,加上序数就是函数地址数组的索引值
DWORD NumberOfFunctions ; 导出函数的总数
DWORD NumberOfNames ; 以名称方式导出的函数的总数
DWORD AddressOfFunctions ; 指向输出函数地址的RVA

DWORD AddressOfNames ; 指向输出函数名字的RVA
DWORD AddressOfNameOrdinals ; 指向输出函数序号的RVA
};IMAGE_EXPORT_DIRECTORY

结构体中的重要成员
1.NumberOfFunctions:表示实际导出函数的个数
2.NumberOfNames:表示有名字的导出函数的个数
3.AddressOfFunctions:表示导出函数地址数组
4.AddressOfNames:表示导出函数的名字字符串数组
5.AddressOfNameOrdinals

GetProcAddress()操作原理

1.利用AddressOfNames成员找到函数名称数组;其中保存着导出函数的Names字符串;通过Strcmp字符串查找指定的函数名称,得到索引(name_index)
2.利用AddressOfNameOrdinals成员,转到ordinal数组;其中保存着导出函数的ordinal索引与实际地址索引的对应关系(通常是相同的,但是当出现无名导出函数,ordinal中无,而实际地址中有时,两个索引就不同了);通过name_index找到对应的ordinal值
3.利用AddressOfFunctions成员,转到函数地址数组(EAT);其中保存着函数与实际地址的关系;通过ordinal值找到地址

相关文章
评论
分享
  • 网鼎杯部分wp

    pwnboom1分析远程已经打不通了,远程的偏移和本地的偏移不一样,只能复现一下本地的了。 首先看到流程图,代码量很大,有很大的switch语句和嵌套结构,可能是虚拟机或者是解析器。 从下图看出是一个C语言的解析器。 然后看了...

    网鼎杯部分wp
  • 网络设备配置与管理

    Linux网络设备与管理大作业 下图为某企业网络拓扑图,接入层采用二层交换机2960,汇聚和核心层使用了一台三层交换机3560 24PS,局域网边缘采用一台路由器LanRouter用于连接到外部网络的Isp Router两台路由器...

    网络设备配置与管理
  • 数字中国创新大赛

    又是自闭的一天。。 game这一题是关于python字节码的题目,之前没有了解过,看了几篇关于python字节码的文章,死磕,手工还原。。 python字节码 12345678910111213141516171819202122...

    数字中国创新大赛
Please check the parameter of comment in config.yml of hexo-theme-Annie!