12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 334|回复: 0

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

[复制链接]

52

主题

52

帖子

114

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
114
发表于 2021-5-7 23:08:50 | 显示全部楼层 |阅读模式
继续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-11-24 20:01 , Processed in 0.093750 second(s), 32 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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