12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 408|回复: 0

PE文件笔记四 PE文件头之标准PE头

[复制链接]
发表于 2021-8-12 18:02:13 | 显示全部楼层 |阅读模式
继续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
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-4-25 18:02 , Processed in 0.140625 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表