PE文件结构
检验PE文件的有效性<1>首先检验文件头部第一个字的值是否等于IMAGE_DOS_SIGNATURE,是则表示DOS MZ header有效<2>一旦证明文件的Dos header 有效后,就可用e_lfanew来定位PE header<3>比较PE header 的第一个字的值是否等于IMAGE_NT_HEADER,如果前后两个值都匹配. PS.WinHex使用方法1.Alt+G跳到指定位置2.Ctrl+Shift+N放入新文件3.大文件扩容,新建一个扩容大小+1的文件,把这个文件的数据复制后写入整个文件的尾地址.4.文本搜索ctrl+F5.十六进制搜索ctrl+alt+x6.文本显示F77.打开内存alt+F98.进制转换器F89.分析选块F210.计算HASH ctrl+F211.收集文本信息ctrl+F1012.编辑模式F6一.IMAGE_DOS_HEADER<1>位置00H,WORD(2个字节)的e_magic为4D5A,即MZ<2>位置3CH,60,LONG(4个节节)的e_lfanew为64+112=176即B0H,二.IMAGE_NT_HEADERS<1>位置B0H,DWORD(4个字节),PE开始标记,写入50450000,即PE<2>位置B4H,WORD,PE所要求的CPU,对于Intel平台,为4C01<2>位置B6,WORD,PE中段总数,计划有3个段,.text代码段,.rdata只读数据段,.data全局变量数据段,所以值为0300,<3>位置C4,WORD,表示后面的PE文件可选头的占空间大小,即224字节(E0),值为E000<4>位置C6,WORD,表示文件是EXE还是DLL,如果是可执行文件写0200,如果是dll,写0020,<5>位置C8,WORD,表示文件格式,如果是0B01表示.exe,如果是0701表示ROM映像<6>位置D8,DWORD,表示代码入口的RVA地址,要代码写好后才能确定,这里先用AAAAAAAA 代替,后修改为00100000,<7>位置E4,DWORD,表示文件映射到内存的基地址,PE文件优先装载地址,这里值为00004000,<8>位置E8,DWORD,表示段加载后内存中的对齐方式,如果是1000h(即4096),每节的起始地址必须是4096的倍数,若第一节从401000h开始,大小是10个字节,下一个节并不是从401011开始,而是从402000h开始,一般情况下都为4KB.<9>位置EC,DWORD,表示段在文件中对齐方式,一般情况下,程序文件对齐粒度都为200h(512),将此值设为00020000,<10>位置F8,表示子系统主版本号,该子系统版本必定是4.0,那么此处值为04.<11>位置100,表示程序载入内存后占用内存的大小(单位字节),等于所有段的长度之和--PE 结构要占1000h,3个段每个占1000h,总大小为4000h,值为"00400000"<12>位置104,表示所有文件头的长度之和,PE文件头总大小为64+112+4+20+224=424,3个节表的总大小3*40=120,424+120=544 字节,转为十六进制为220h,实际上要占用400h空间,所以值为00040000,<13>位置10C,表示子系统,0300表示控制台程序,0200表示Windows程序,<14>位置124,表示IMAGE_DA TA_DIRECTORY结构数组大小,通常有16个元素,所以值为1000000,<15>位置130,目录表的起始RVA地址,目录表的长度,先填写AAAAAAAAAAAAAAAA,因为要文件对齐,全部添零直到1a7h处.(后改写为1020 0000 3C00 0000<16>位置1A8,表示该节的名称,该节的名字为.text,此值为2E74657874000000,<17>位置1B0,表示该节数据映射到内存后所占字节数,这里是指有效代码所占的字节数,也可以填写26h经过内存对齐后的值,即00100000,<18>位置1B4,表示.text段映射到内存中的起始地址,因为PE头本身结构小于1000h字节,经过内存对齐后便为1000h,那么此值便为00001000,因此此处填写00100000,<19>位置1B8,表.text段在文件中所占的大小,可以填写2600 0000,也可以填写此值经过文件对齐后的值即200h,即00020000,<20>位置1BC,表示.text段在文件中的起始地址,上面经计算PE文件的总长度为400h,实际上也就是.text的起始偏移地址,此值为00040000,<21>位置1CC,表示是代码段,bit5位要置1,含有初始化数据,bit6要置1,可执行bit29置1,所以此处应该填写6000 0020.<22>.text头编写完毕,按照上面方法分别填写.rdata段和.data段,后面的代码用零补齐,直到3ff<23>位置400,表示.text段的程序执行代码,push 0;MessageBoxA的第4个参数,即消息框的风格,这里传入0push 0x403000;第3个参数,消息框的标题字符所在的地址PE头1000h,.text段1000h,.data段1000h,则在后面的.rdata段应该在偏移为3000h处,push 0x403007;第2个参数,消息框的内容所在的地址,"wcc526"占7个字符,push 0;第1个参数,消息框所属窗口句柄0call 40101A;调用MessageBoxA,实际上是跳转到该函数的跳转指令所在地址,因为执行代码起始地址为.text段的起始地址为0x1000,总长度为2+5+5+2+5+2+5=26,转为十六进制是1A, push 0 ;ExitProcess函数的参数,程序退出码,传入0call 401020;调用ExitProcess,实际上是跳转到该函数的跳转指令所在地址,再加6个字节jmp dword ptr[0x402080];跳转到MessageBoxA的真正地址处,通过导入表得到其填充地址为0x2080,这是其RVA值,jmp dword ptr[0x402088];跳转到ExitProcess的真正地址处, 通过导入表得到其填充地址为0x2088,这是其RVA值,转成机器码为push 0 ->6A 00push 0x403000->68 00304000push 0x403007->68 07304000push 0 ->6A 00call 40101A->E8 07 00 00 00push 0->6A 00call 401020->E8 06 00 00 00jmp dword ptr[0x402080]->FF 2580204000jmp dword ptr[0x402088]->FF 2588204000其余部分用00填充,直到600h处.<24>位置61C,指向DLL名字的RVA,由导入函数名称表可知此user32.dll名称所在地址为66A,所以RVA地址为6A20 0000<25>位置630, 指向DLL名字的RVA,由导入函数名称表可知此kernel32.dlol名称所在地址为684,所以RVA地址为8420 0000<24>位置64C,库函数名称地址表,即RVA地址,文件偏移600h对应的RVA是2000h,所以MessageBoxA的地址为65C,所以写5C20 00 0000,<25>位置654, 库函数名称地址表,即RVA地址,文件偏移600h对应的RVA是2000h,所以ExitProcess的地址为676,所以写7620 00 0000导出表结构1.Characteristics 是DWORD 类型,4个字节,可以为零2.TimeDateStampe,DWORD,表示自1970年1月1日至今的秒数,输出表创建时间,可以为零3.MajorVersion,WORD,表示主版本号,可以是零4.MinorVersion,WORD,表示次版本号,可以是零,DWORD,指向模块的真实名称字符串的RVA值,若该值为0x2e0a6,在文件中找到偏移为0x2e0a6的位置,得到Dll.dll,得知导出模块的名字为Dll.dll6.Base,等于所有函数导出序号中最小的值7.NumberOfFunctions,DWORD,表示模块中导出函数/符号个数8.NumberofNames,DWORD,表示通过名字引出的函数/符号的数目,9.AddressofFunctions,DWORD,指向一个地址表,存放的是所有导出函数所在地址的RV A值10.AddressofNames,DWORD,指向一个地址表,存放的是所有导出函数名称字符串所在地址的RVA值.11.AddressOfNameOrdinals,DWORD,指向一个导出序号表,解析导出表的步骤1.找出导出目录,定位到导出表的文件偏移2.查看导出表的第7个成员,得知导出地址成员的个数,这里是43.查看导出表的第9个成员,得到RVA并且将其转成文件偏移,4.查看导出表的第8个成员,得到由函数名导出函数的个数5.查看导出表的第11个成员,得到导出序号表6.查看导出表第10个成员,得出导出表中导出函数名称字符串所在地址的函数名称地址表。
PE文件格式详解
PE文件格式详解(一)基础知识什么是PE文件格式:我们知道所有文件都是一些连续(当然实际存储在磁盘上的时候不一定是连续的)的数据组织起来的,不同类型的文件肯定组织形式也各不相同;PE文件格式便是一种文件组织形式,它是32位Window系统中的可执行文件EXE以及动态连接库文件DLL的组织形式。
为什么我们双击一个EXE文件之后它就会被Window运行,而我们双击一个DOC文件就会被Word打开并显示其中的内容;这说明文件中肯定除了存在那些文件的主体内容(比如EXE文件中的代码,数据等,DOC 文件中的文件内容等)之外还存在其他一些重要的信息。
这些信息是给文件的使用者看的,比如说EXE文件的使用者就是Window,而DOC文件的使用者就是Word。
Window可以根据这些信息知道把文件加载到地址空间的那个位置,知道从哪个地址开始执行;加载到内存后如何修正一些指令中的地址等等。
那么PE文件中的这些重要信息都是由谁加入的呢?是由编译器和连接器完成的,针对不同的编译器和连接器通常会提供不同的选项让我们在编译和联结生成PE文件的时候对其中的那些Window需要的信息进行设定;当然也可以按照默认的方式编译连接生成Window中默认的信息。
例如:WindowNT默认的程序加载基址是0x40000;你可以在用VC连接生成EXE文件的时候使用选项更改这个地址值。
在不同的操作系统中可执行文件的格式是不同的,比如在Linux上就有一种流行的ELF格式;当然它是由在Linux上的编译器和连接器生成的,所以编译器、连接器是针对不同的CPU架构和不同的操作系统而涉及出来的。
在嵌入式领域中我们经常提到交叉编译器一词,它的作用就是在一种平台下编译出能在另一个平台下运行的程序;例如,我们可以使用交叉编译器在跑Linux的X86机器上编译出能在Arm上运行的程序。
程序是如何运行起来的:一个程序从编写出来到运行一共需要那些工具,他们都对程序作了些什么呢?里面都涉及哪些知识需要学习呢?先说工具:编辑器-》编译器-》连接器-》加载器;首先我们使用编辑器编辑源文件;然后使用编译器编译程目标文件OBJ,这里面涉及到编译原理的知识;连接器把OBJ文件和其他一些库文件和资源文件连接起来生成EXE文件,这里面涉及到不同的连接器的知识,连接器根据OS的需要生成EXE文件保存着磁盘上;当我们运行EXE文件的时候有Window的加载器负责把EXE文件加载到线性地址空间,加载的时候便是根据上一节中说到的PE文件格式中的哪些重要信息。
逆向分析实验2PE文件结构分析
实验二PE文件结构分析一. 实验目的1.了解PE文件的输入表结构;2.手工解析PE文件的输入表;3.编程实现PE文件输入表的解析。
二. 实验内容1.第一步:手动解析输入表结构(1)使用工具箱中的工具e verything,寻找当前系统中任意一个e xe文件,文件名称是: actmovie.exe(2)使用LordPE“PE编辑器”打开exe文件,确定输入表的RVA,截图如下(图1):(3)点击PE编辑器右侧的“位置计算器”,得到文件偏移值,截图如下(图2):(4)使用16进制编辑工具,跳转到相应的输入文件偏移地址,输入表是每个IID对应一个DLL,根据IID大小,这里取20字节的数据进行分析,将输入表第一个IID结构的数据与IID结构体的成员一一对应,具体如下所示:IMAGE_IMPORT_DESCRIPTOR {OriginalFirstThunk = 000013C0TimeDateStamp = FFFFFFFFForwarderChain = FFFFFFFFName = 000014C0FirstThunk = 0000100C}(5)关注OriginalFirstThunk和Name两个成员,其中Name是一个RVA,用步骤(3)的方法得到其文件偏移值为 000008C0 ,在16进制编辑工具转到这个偏移地址,可见输入表的第一个D LL名为 msvcrt.dll ,截图如下(图3):(6)分析一下OriginalFirstThunk,它指向一个类型为IMAGE_THUNK_DATA的数组,上面已经分析出了它的值为000013C0 ,这是一个RVA,用步骤(3)的方法得到文件偏移地址 00007C0 。
在16进制编辑工具转到这个偏移地址,其中前面4个字节的数据为 63 5F 00 C8 ,截图如下(图4):(7)可以看出,这是以序号(填“以名字”或“以序号”)的方式输入函数;用与步骤(3)相同的方式在16进制编辑工具中对应IMAGE_IMPORT_BY_NAME结构的数据,可以看到函数的输入序号为 20 ,函数名为 cexit ,截图如下(图5):(8)验证:使用L ordPE单击“目录表”界面中输入表右侧的“…按钮”,打开输入表对话框,可以验证获取的DLL名和函数名是否正确。
PE文件结构(二)
l
// Pe.h: 定义CPe类
//
#ifndef _PE_H__INCLUDED
#define _PE_H__INCLUDED
#include <io.h>
#include <fcntl.h>
#include <sys\stat.h>
typedef struct PE_HEADER_MAP
经过3层IMAGE_RESOURCE_DIRECTORY_ENTRY(一般是3层,也有可能更少些)最终可以找到一个IMAGE_RESOURCE_DATA_ENTRY结构,这个结构中存有相应资源的位置和大小。这个结构长16个字节,有4个参数,其原型如下:
l
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
标识一个IMAGE_RESOURCE_DIRECTORY_ENTRY一般都是使用Id,就是一个整数。但是也有少数使用IMAGE_RESOURCE_DIR_STRING_U来标识一个资源类型。这个结构定义如下:
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
class CPe
{
public:
CPe();
virtual ~CPe();
public:
void CalcAddress(const void *base);
void ModifyPe(CString strFileName,CString strMsg);
void WriteFile(CString strFileName,CString strMsg);
BOOL WriteNewEntry(int ret,long offset,DWORD dwAddress);
PE文件格式
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
么正确的开始地址是0x401560。如果可执行程序调入0x100000处,则开始地址为0x101560。
因为PE文件的每一个段不必按同样的边界对齐方式调入,因此RVA地址的计算变得比较复
杂。例如,在文件中每一个段往往按512个字节的方式对齐,而在内存中可能以4096字节的方
式对齐。这方面的介绍可见下面的“SectionAlignment”、“FileAlignment”。举个例子,
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
三、文件头(File Header)
通过DOS头,你可以找到一个叫做IMAGE_FILE_HEADER的结构,如下;下面我分别介绍一
下。
typedef struct _IMAGE_FILE_HEADER {
中只有大约100个字节的代码,只输出一个诸如“this program needs windows NT ”之类的
信息。
你可以通过一个叫做IMAGE_DOS_HEADER的结构来识别一个合法的DOS头。这个结构的头两
个字节一定是“MZ”(#define IMAGE_DOS_SIGNATURE "MZ")。怎么才能找到PE开始的标志呢
PE文件结构
LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
IMAGE_OS2_SIGNATURE_LE)
return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
else if (*(DWORD *)NTSIGNATURE (lpFile) ==
注意:请查阅本文末尾关于如何使用PEFILE.DLL的那一段。
MS-DOS头部/实模式头部
如上所述,PE文件格式的第一个组成部分是MS-DOS头部。在PE文件格式中,它并非一个新概念,因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是,当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候,操作系统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说,当你在MS-DOS 6.0下运行一个Windows NT可执行文件时,你会得到这样一条消息:“This program cannot be run in DOS mode.”如果MS-DOS头部不是作为PE文件格式的第一部分的话,操作系统装载文件的时候就会失败,并提供一些完全没用的信息,例如:“The name specified is not recognized as an internal or external command, operable program or batch file.”
DWORD WINAPI ImageFileType (LPVOID lpFile)
{
/* 首先出现的是DOS文件标志 */
if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
PE文件头简析及输入表、输出表的分析
PE文件头简析及输入表、输出表的分析2008年01月13日星期日 22:50PE文件头简析及输入表、输出表的分析前两天由于做个免杀,需要对输入表做些改变,所以又重新看了pe文件的结构尤其是输入表及输出表的一些细节方面,做个笔记,方便自己以后翻看,也给大家提个方便,呵呵!1、PE文件格式文件尾___________________________________________________________________Code View调试信息 |COFF 符号表 | 调试信息COFF 行号 |------------------------------------------------------------------.reloc |.edata | 块(Section) .data |.text |---------------------------------------------------------------------------------------------------IMAGE_SECTION_HEADER |IMAGE_SECTION_HEADER | 块表(Section Table)IMAGE_SECTION_HEADER |IMAGE_SECTION_HEADER | //对应.text块---------------------------------------------------------------------------------------------------------数据目录表 | //包含在可选映像头中,包含输出\入表信息 IMAGE_OPTIONAL_HEADER32 | 可选映像头IMAGE_FILE_HEADER | 文件映像头"PE",0,0 | pe文件标识(pe文件头包含三个部分)---------------------------------------------------------------------------------------------------------DOS stub |DOS 'MZ' HEADER | 该字段中的e_lfanew字段指向pe头 ---------------------------------------------------------------------------------------------------------文件头说明:01、输入表、输出表的位置:在pe头中可选映像头字段中数据目录表字段中,相对于pe文件标识处的偏移分别为:+80h、+78h;(其中pe文件标识的位置在DOS 'MZ' HEADER字段中的e_lfanew指出,该字段相对于文件开始的偏移为+3Ch,4个字节);02、在用例子说明时用c32asm打开,其中的偏移均为文件在磁盘上存储的物理偏移,而查看的值均为给出的各个地址的RVA,转化方法入下:查到的RVA值-VOffset(可由lordpe查看)+ROffset 得到的就是可以c32asm中的偏移2、上面对pe文件格式做了简要介绍,下面主要分析输入表和输出表:输入表篇:概念不做介绍,位置上面已经给出,呵呵输入表的结构:输入表是以一个IMAGE_IMPORT_DESCRIPTOR(IID)数组开始,一个程序要调用几个dll就会有几个IID项,即每个IID对应于一个dllIID结构:IMAGE_IMPORT_DESCRIPTOR structunion{DWORD Characteristics ; ;00hDWORD OriginalFirstThunk; // 注释1};TimeDateStamp DWORD ;04h // 时间标志,可以忽略;ForwarderChain DWORD ;08h // 正向链接索引,一般为0,当程序引用一个dll中的api,而这个api又引用其它dll中的api时用;Name DWORD ;0Ch //DLL名字的指针,以00结尾的ASCII字符的RVA地址;FirstThunk DWORD ;10h // 注释2IMAGE_IMPORT_DESCRIPTOR ends注释 1:该值为一个 IMAGE_THUNK_DATA数组的RVA,其中的每个指针都指向IMAGE_IMPORT_BY_NAME结构。
深入剖析PE文件
深入剖析PE文件PE文件是Win32的原生文件格式.每一个Win32可执行文件都遵循PE文件格式.对PE文件格式的了解可以加深你对Win32系统的深入理解.一、基本结构。
上图便是PE文件的基本结构。
(注意:DOS MZ Header和部分PE header的大小是不变的;DOS stub部分的大小是可变的。
)一个PE文件至少需要两个Section,一个是存放代码,一个存放数据。
NT上的PE文件基本上有9个预定义的Section。
分别是:.text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, 和.debug。
一些PE文件中只需要其中的一部分Section.以下是通常的分类:l 执行代码Section , 通常命名为:.text (MS) or CODE (Borland)l 数据Section, 通常命名为:.data, .rdata, 或.bss(MS) 或DATA(Borland).资源Section, 通常命名为:.edatal 输入数据Section, 通常命名为:.idatal 调试信息Section,通常命名为:.debug这些只是命名方式,便于识别。
通常与系统并无直接关系。
通常,一个PE文件在磁盘上的映像跟内存中的基本一致。
但并不是完全的拷贝。
Windows加载器会决定加载哪些部分,哪些部分不需要加载。
而且由于磁盘对齐与内存对齐的不一致,加载到内存的PE文件与磁盘上的PE文件各个部分的分布都会有差异。
当一个PE文件被加载到内存后,便是我们常说的模块(Module),其起始地址就是所谓的HModule.二、DOS头结构。
所有的PE文件都是以一个64字节的DOS头开始。
这个DOS头只是为了兼容早期的DOS操作系统。
这里不做详细讲解。
只需要了解一下其中几个有用的数据。
1. e_magic:DOS头的标识,为4Dh和5Ah。
分别为字母MZ。
PE文件结构解析
PE⽂件结构解析说明:本⽂件中各种⽂件头格式截图基本都来⾃看雪的《加密与解密》;本⽂相当《加密与解密》的阅读笔记。
1.PE⽂件总体结构PE⽂件框架结构,就是exe⽂件的排版结构。
也就是说我们以⼗六进制打开⼀个.exe⽂件,开头的那些内容就是DOS头内容,下来是PE头内容,依次类推。
如果能认识到这样的内含,那么“exe开头的内容是不是就直接是我们编写的代码”(不是,开头是DOS头内容)以及“我们编写的代码被编排到了exe⽂件的哪⾥”(在.text段,.text具体地址由其相应的IMAGE_SECTION_HRADER指出)此类的问题答案就显⽽易见了。
exe⽂件从磁盘加载到内存,各部份的先后顺序是保持不变的,但由于磁盘(⼀般200H)和内存(⼀般1000H)区块的对齐⼤⼩不⼀样,所以同⼀内容在磁盘和在内存中的地址是不⼀样的。
换⾔之你在磁盘上看到⼀段内容⼀内容要到在内存中找到它--假设它是能映射到内容的部份--那么要做相应的地址转换。
(⽐如你在Ultraedit 中看到某⼏个字节⽽想在OllyDbg中找到这⼏个字节那么需要进⾏地址转换)另外要注意,PE⽂件中存放的地址值都是内存中的地址,这些地址在OllyDbg中不需要转换到其指定的位置就能找到其指向的内容;这要根据这个地址找到内容在Ultraedit的地址,需要将此RVA址转换成⽂件偏移地址。
还要注意DOS头/PE头/块表,映射到内存时属同⼀区块⽽且是第⼀区块,所以此三者上的RVA和⽂件偏移地址是相等的。
2.DOS头部2.1MS-DOS头部(IMAGE_DOS_HEADER)最后的e_lfanew即是PE⽂件的RVA地址我们在前边已经提过,对于DOS头/PE头/区块表三部分RVA和⽂件偏移地址是相等的,所以上边在⼗六进制⽂本编缉器中,直接转向e_lfanew指向的000000B0可以正好找到PE头。
2.2DOS stubDOS stub是当操作系统不⽀持PE⽂件时执⾏的部分,⼀般由编译器⾃⼰⽣成内容是输出“This program cannot be run in MS-DOS mode”等提⽰。
PE文件结构
节表的数据
第一个结构
最后一个结构
对齐填充数据
偏移转换计算 一
入口RVA:00261001
内存块大小
内存块起始 文件块大小
文件块起始
起始位置 块起始
结束位置 块起始 + 块长度
各个节的范围
• • • • • • • • • • • 节名 Code DATA BSS .idata .tls .rdata .reloc .rsrc .aspack .adata 起始地址 - 终了地址 00001000 - 00140000 00140000 - 00145000 00145000 - 0014B000 0014B000 - 0014E000 0014E000 - 0014F000 0014F000 - 00150000 00150000 - 00165000 00165000 - 00261000 00261000 - 00264000 00264000 - 00265000
文件头数据信息
节数目:0Ah
可选头定义
• • • • • • • • • • • • • • IMAGE_OPTIONAL_HEADER32 STRUCT SizeOfCode DWORD ? ;001ch 所有含代码的节的总大小 SizeOfInitializedData DWORD ? ;0020h 所有含已初始化数据的节的总大小 SizeOfUninitializedData DWORD ? ;0024h 所有含未初始化数据的节的大小 AddressOfEntryPoint DWORD ? ;0028h 程序执行入口RVA BaseOfCode DWORD ? ;002ch 代码的节的起始RVA BaseOfData DWORD ? ;0030h 数据的节的起始RVA ImageBase DWORD ? ;0034h PE文件的装载地址 SectionAlignment DWORD ? ;0038h 内存中的节的对齐粒度 FileAlignment DWORD ? ;003ch 文件中的节的对齐粒度 SizeOfImage DWORD ? ;0050h 内存中整个PE映像尺寸 SizeOfHeaders DWORD ? ;0054h 所有头+节表的大小 DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>) ;0078h IMAGE_OPTIONAL_HEADER32 ENDS
PE文件格式详解(一)
PE文件格式详解(一)0x00 前言PE文件是portable File Format(可移植文件)的简写,我们比较熟悉的DLL和exe文件都是PE文件。
了解PE文件格式有助于加深对操作系统的理解,掌握可执行文件的数据结构机器运行机制,对于逆向破解,加壳等安全方面方面的同学极其重要。
接下来我将通过接下来几篇详细介绍PE文件的格式。
0x01 基本概念PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的组织结构。
文件的内容分割为不同的区块(Setion,又称区段,节等),区段中包含代码数据,各个区块按照页边界来对齐,区块没有限制大小,是一个连续的结构。
每块都有他自己在内存中的属性,比如:这个块是否可读可写,或者只读等等。
认识PE文件不是作为单一内存映射文件被装入内存是很重要的,windows加载器(PE加载器)便利PE文件并决定文件的哪个部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中。
当磁盘的数据结构中寻找一些内容,那么几乎能在被装入到内存映射文件中找到相同的信息。
但是数据之间的位置可能改变,其某项的偏移地址可能区别于原始的偏移位置,不管怎么样,所表现出来的信息都允许从磁盘文件到内存偏移的转换,如下图:PS:PE文件头以下的地址无论在内存映射中还是在磁盘映射中都是一样的,当内存分页和磁盘分页一致时无需进行地址转换,只有当磁盘分页和内存分页不一样时才要进行地址转化,这点很重要,拿到PE文件是首先查看分页是否一致。
前两天一直没碰到内存和磁盘分页不一样的,所以这个点一直没发现,今天特来补上。
下面要介绍几个重要概念,分别是基地址(ImageBase),相对虚拟地址(Relative Virtual Address),文件偏移地址(File Offset)。
1)基地址定义:当PE文件通过Windows加载器被装入内存后,内存中的版本被称作模块(Module)。
映射文件的起始地址被称作模块句柄(hMoudule),可以通过模块句柄访问其他的数据结构。
