|
继续PE系列笔记的更新
PE其它笔记索引可前往:
PE文件笔记一 PE介绍
继续详细学习PE的各个结构细节,前面学完了DOS部首,接着学习PE文件头
由于PE文件头的内容较多,故要拆分为多个笔记,此笔记主要为尺度PE头
PE文件头
PE文件头结构
两种PE文件头
PE文件头的结构有两种,分别对应32位的步调和64位的步调,它们的差异在于扩展PE头的结构
PE文件头结构阐明_IMAGE_NT_HEADERS32位步调对应的PE文件头结构_IMAGE_NT_HEADERS6464位步调对应的PE文件头结构_IMAGE_NT_HEADERS对应C中的结构体(范例)阐明"PE",0,0DOWRDPE标识IMAGE_FILE_HEADERIMAGE_FILE_HEADER尺度PE头IMAGE_OPTIONAL_HEADER32IMAGE_OPTIONAL_HEADER32扩展PE头 32位_IMAGE_NT_HEADERS64对应C中的结构体(范例)阐明"PE",0,0DOWRDPE标识,固定值不可变IMAGE_FILE_HEADERIMAGE_FILE_HEADER尺度PE头IMAGE_OPTIONAL_HEADER64IMAGE_OPTIONAL_HEADER64扩展PE头 64位结构体截图
结构体代码
32位结构体
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; //PE文件头标识 IMAGE_FILE_HEADER FileHeader; //尺度PE头 IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头 32位} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;64位结构体
typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; //PE文件头标识 IMAGE_FILE_HEADER FileHeader; //尺度PE头 IMAGE_OPTIONAL_HEADER64 OptionalHeader; //扩展PE头 64位} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;PE文件头标记
实例分析
根据DOS MZ头的最后一个成员找到PE文件头的首部,也就是PE文件头标记的首部
可以看到,PE文件头标记固定为 50 45 00 00 ,对应ASCII为“PE ” ,是用来判定文件是否为PE文件的标识之一,另有一个PE标识为MZ头
PE文件头标记对应C语言变量数据宽度值阐明"PE",0,0SignatureDWORD(4字节)50 45 00 00对应ASCII为“PE ”PE文件标识尺度PE头
结构体截图
结构体代码
typedef struct _IMAGE_FILE_HEADER { WORD Machine;//可以运行在什么样的CPU上 恣意:0 Intel 386以及后续:14C x64:8664 WORD NumberOfSections;//表示节的数目 DWORD TimeDateStamp;//编译器填写的时间戳 与文件属性内里(创建时间、修改时间)无关 DWORD PointerToSymbolTable;//调试相关 DWORD NumberOfSymbols;//调试相关 WORD SizeOfOptionalHeader;//可选PE头的大小(32位PE文件:0xE0 64位PE文件:0xF0) WORD Characteristics;//文件属性} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;成员详情
成员数据宽度阐明值MachineWORD(2字节)步调支持的CPU恣意:0 Intel 386以及后续:14C x64:8664NumberOfSectionsWORD(2字节)节的数目不大于96TimeDateStampDWORD(4字节)编译器填写的时间戳与文件属性内里(创建时间、修改时间)无关PointerToSymbolTableDWORD(4字节)指向符号表调试相关NumberOfSymbolsDWORD(4字节)符号表中的符号个数调试相关SizeOfOptionalHeaderWORD(2字节)可选PE头结构大小32位PE文件:0xE0 64位PE文件:0xF0CharacteristicsWORD(2字节)文件属性由数据位拼接而成,详见下方Machine
盘算机的体系结构范例。映像文件只能在指定的盘算机或模仿指定盘算机的系统上运行。此成员可以是以下值之一:
值含义宏界说IMAGE_FILE_MACHINE_I386 = 0x014cx86宏界说IMAGE_FILE_MACHINE_IA64 = 0x0200Intel IPF宏界说IMAGE_FILE_MACHINE_AMD64 = 0x8664x64IA64:就是所谓的安腾(Itanium)(IPF),Intel跟HP联合折腾的一种64-bits全新架构,与x86系列不兼容
NumberOfSections
节数。这表示紧跟在PE文件头背面的节表的大小。请注意,Windows加载步调将节数限制为96。
TimeDateStamp
Image时间戳的低32位。这表示链接器创建Image的日期和时间。根据系统时钟,该值以自1970年1月1日半夜(00:00:00)后经过的秒数表示。
PointerToSymbolTable
符号表的偏移量,以字节为单位,如果不存在COFF符号表,则为零。
COFF是指通用对象文件格式,在Microsoft 实现叫做可移植可实行 (PE) 文件格式,在Linux上的实现叫做(可实行与可链接)ELF文件格式;COFF全拼为:Common Object File Format
NumberOfSymbols
符号表中的符号数
SizeOfOptionalHeader
扩展PE头的大小,以字节为单位。对于对象文件(object files),此值应为0。
32位的PE文件默认值为0xE0 64位PE文件默认值为0xF0 该值可变
Characteristics
Image的文件属性,其值对应的数据位含义为:
Characteristics的数据宽度为WORD(2字节=16 bits)
假设Characteristics的十六进制为0102,分析其文件属性
首先将十六进制转化为二进制:0000 0001 0000 0010
此时可以发现数据位1和8的位置的值为1(数据位由0开始),对照上面可得出:文件属性为 文件是可实行的、只在32位平台上运行
实例分析
紧跟着上面PE文件头标记的实例分析,继续分析尺度PE头对应的各个属性
根据尺度PE头各个成员的数据宽度不难得出尺度PE头的总宽度为:20字节(4个WORD+3个DWORD=4×2+3×4=20)
因此从前面PE文件头标记后再数20个字节都是尺度PE头的数据
4C 01 05 00 6B 01 AE 55 00 00 00 00 00 00 00 00 E0 00 02 01得到:
成员阐明值Machinex8614CNumberOfSections有5个节5TimeDateStamp编译器添补的时间戳55 AE 01 6BPointerToSymbolTable调试相关00 00 00 00NumberOfSymbols调试相关00 00 00 00SizeOfOptionalHeader可选PE头结构大小为E0E0Characteristics文件属性为 文件可实行且只在32位平台上运行102自写代码剖析PE文件头
因为有人提议用VS2019来编写,于是这里改成VS2019中的代码,但其实在VC6中也通用
#include #include//在VC6这个比力旧的环境里,没有界说64位的这个宏,需要自己界说,在VS2019中无需自己界说#define IMAGE_FILE_MACHINE_AMD64 0x8664int main(int argc, char* argv[]){ //创建DOS对应的结构体指针 _IMAGE_DOS_HEADER* dos; //读取文件,返回文件句柄 HANDLE hFile = CreateFileA("C:\\Users\\sixonezero\\Desktop\\dbgview64.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); //根据文件句柄创建映射 HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0); //映射内容 LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); //范例转换,用结构体的方式来读取 dos = (_IMAGE_DOS_HEADER*)pFile; //输出dos->e_magic,以十六进制输出 printf("dos->e_magic:%X\n", dos->e_magic); //创建指向PE文件头标记的指针 DWORD* peId; //让PE文件头标记指针指向其对应的地址=DOS首地址+偏移 peId = (DWORD*)((UINT)dos + dos->e_lfanew); //输出PE文件头标记,其值应为4550,否则不是PE文件 printf("peId:%X\n", *peId); //创建尺度PE头对应的结构体指针 _IMAGE_FILE_HEADER* file; //让尺度PE头指针指向其对应的地址=PE文件头标记地址+PE文件头标记大小 file = (_IMAGE_FILE_HEADER*)((UINT)peId + sizeof(DWORD)); //输出file->Machine printf("file->Machine:%X\n", file->Machine); //根据file->Machine判定步调为 x86或IPF或x64 switch (file->Machine) { //步调为32位 case IMAGE_FILE_MACHINE_I386: { printf("x86 program\n"); //确定步调为32位则扩展PE头确定为_IMAGE_OPTIONAL_HEADER //创建扩展PE头对应的结构体指针 _IMAGE_OPTIONAL_HEADER* opt; //让扩展PE头指针指向其对应的地址=尺度PE头地址+尺度PE头大小 opt = (_IMAGE_OPTIONAL_HEADER*)((UINT)file + sizeof(_IMAGE_FILE_HEADER)); //输出opt->Magic printf("opt->Magic:%X\n", opt->Magic); break; } //步调为IPF case IMAGE_FILE_MACHINE_IA64: printf("IPF program\n"); break; //步调为64位 case IMAGE_FILE_MACHINE_AMD64: { printf("x64 program\n"); //确定步调为64位则扩展PE头确定为_IMAGE_OPTIONAL_HEADER64 //创建扩展PE头对应的结构体指针 _IMAGE_OPTIONAL_HEADER64* opt; //让扩展PE头指针指向其对应的地址=尺度PE头地址+尺度PE头大小 opt = (_IMAGE_OPTIONAL_HEADER64*)((UINT)file + sizeof(_IMAGE_FILE_HEADER)); //输出opt->Magic printf("opt->Magic:%X\n", opt->Magic); break; } default: break; } return 0;}运行结果
分别演示32位步调和64位步调的运行结果
32位步调
64位步调
代码小解
代码中判定步调是32位或64位是看file->Machine的值来举行判定的,但其实这里并不一定准确,实际上应当判定opt->Magic才最为准确的。但关于扩展PE头的内容留作之后,这里为了学习尺度PE头,故先采用这种方式举行判定,背面也会修正为利用opt->Magic来判定步调为32位或64位
代码中大部门都有注释,并不难明确,主要阐明一下 让指针指向对应地址 的代码
//让PE文件头标记指针指向其对应的地址=DOS首地址+偏移peId = (DWORD*)((UINT)dos + dos->e_lfanew);//让尺度PE头指针指向其对应的地址=PE文件头标记地址+PE文件头标记大小file = (_IMAGE_FILE_HEADER*)((UINT)peId + sizeof(DWORD));//让扩展PE头指针指向其对应的地址=尺度PE头地址+尺度PE头大小opt = (_IMAGE_OPTIONAL_HEADER*)((UINT)file + sizeof(_IMAGE_FILE_HEADER));//让扩展PE头指针指向其对应的地址=尺度PE头地址+尺度PE头大小opt = (_IMAGE_OPTIONAL_HEADER64*)((UINT)file + sizeof(_IMAGE_FILE_HEADER));指针的地址 = 首地址 + 偏移 这个没有什么好说的,主要在指针前的一个(UINT)强制范例转换
为什么要在指针前加一个(UINT)的强制范例转换?
这就涉及到指针的加减问题了,详解可参考:指针的加减
这里简单引用一下指针加减的结论:
无论是指针的加亦或是减(这里只演示了加法,但减法同理),其加或减的单位为去掉一个*后的数据宽度
也就是实际增减的数值=去掉一个*后的数据宽度 × 增减的数值
上面的指针都是一级结构体指针,DWORD,_IMAGE_FILE_HEADER,_IMAGE_OPTIONAL_HEADER,_IMAGE_OPTIONAL_HEADER64
去掉一个*后的数据宽度为结构体的大小,但是我们这里想要举行的增减的单位应该为字节,而不是结构体的大小,于是要将指针范例强转为UINT(无符号整数)范例(数据宽度为字节),使得其每次增减的单位为字节
总结
- PE文件头的起始位置由DOS MZ头的最后一个成员确定
- PE文件头标记固定ASCII为“PE ”,若不是则阐明该文件非PE文件
- 尺度PE头的第一个成员Machine可以判定步调为32位或64位
- 尺度PE头的第二个成员NumberOfSections表示背面节的个数
- 可选PE头结构大小可变,且在尺度PE头的第六个成员SizeOfOptionalHeader指定
- 尺度PE头的最后一个成员Characteristics阐明白该文件的属性
附件
附上本笔记中分析的EverEdit文件:点我下载
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|