首页
社区
课程
招聘
[原创]PE头解析-字段说明
发表于: 2022-9-22 22:32 9915

[原创]PE头解析-字段说明

2022-9-22 22:32
9915

分别打开.exe .dll .sys等文件,观察特征前2个字节,都是4D5A,变成字母是MZ,是DOS系统开发人员的一个名字,再看3C字节是什么,3C存的是一个地址,这个地址对应的5045,变为字母就是PE。

对齐是为了读写速度,找起来快,用空间换时间

早期计算机硬盘水平比较低,只有很小的几个G,节与节之间的空隙比较小,硬盘对齐为200H,后来随着硬件水平提高了,硬盘对齐与内存对齐一样了为1000H

硬盘间隔小,内存间隔大,这是老的编译器

任何一个exe程序都会有一个自己独立的4G内存空间,虚拟内存

2G是平时写应用程序用的,2G是给操作系统用的

这里注意:还有一些exe程序 当我们用winhex打开时

它在硬盘上和内存中是一样的

这个时候我们要有两个概念 就是硬盘对齐(200h字节)和内存对齐(1000h字节),它是为了增加读写速度

比如一个qq程序有两个部分,数据1(可读),数据2(可读可写) ,都是100M,我运行一个账号就需要200M,我再开一个,数据1(可读)已经有了,就没必要在复制一份丢在内存中,只需要复制一份数据2就可以了

img

PE磁盘文件与内存映像图

img

节表(块表): PE文件中所有节的属性都被定义在节表中

PE文件头/DOS头:对当前整个exe程序做概要性描述

从文件开始的地方算,过80个字节,就是PE文件真正开始的地方

image-20220112125230743

中间这一部分大小是不确定的

留了一块空间,可以放一些随意的数据

image-20220112125355815

TimeDateStamp

.map文件是对.exe文件中函数的描述,对.exe文件的说明

.map文件和.exe文件 不同步时

就是检查 时间戳是否 一致

Characteristics

image-20220112153714023

打勾的 即为1

把所有值 对应起来 0 1 0 E

(第六位是标志保留位,但是没有显示出来)

image-20220112153733828

程序入口 + 内存镜像基址 才是真正的地址

进行了拉伸,完成之后完全遵守操作系统,就可以执行了

程序入口 + 内存镜像基址 才是真正的入口点地址

image-20220828181443181

代码有个极大的缺点,就是只能加载打印特定固定的notepad.exe文件,而且在
不同的操作系统下面会有异常,可移植性差

 
 
 
 
 
 
 
 
 
 
typedef struct IMAGE_DOS_HEADER{
      WORD e_magic;            //DOS头的标识,为4Dh5Ah。分别为字母MZ
      WORD e_cblp;
      WORD e_cp;
      WORD e_crlc;
      WORD e_cparhdr;
      WORD e_minalloc;
      WORD e_maxalloc;
      WORD e_ss;
      WORD e_sp;
      WORD e_csum;
      WORD e_ip;
      WORD e_cs;
      WORD e_lfarlc;
      WORD e_ovno;
      WORD e_res[4];
      WORD e_oemid;
      WORD e_oeminfo;
      WORD e_res2[10];
      DWORD e_lfanew;           //指向IMAGE_NT_HEADERS的所在(PE头相对于文件的偏移,用于定位PE文件)
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct IMAGE_DOS_HEADER{
      WORD e_magic;            //DOS头的标识,为4Dh5Ah。分别为字母MZ
      WORD e_cblp;
      WORD e_cp;
      WORD e_crlc;
      WORD e_cparhdr;
      WORD e_minalloc;
      WORD e_maxalloc;
      WORD e_ss;
      WORD e_sp;
      WORD e_csum;
      WORD e_ip;
      WORD e_cs;
      WORD e_lfarlc;
      WORD e_ovno;
      WORD e_res[4];
      WORD e_oemid;
      WORD e_oeminfo;
      WORD e_res2[10];
      DWORD e_lfanew;           //指向IMAGE_NT_HEADERS的所在(PE头相对于文件的偏移,用于定位PE文件)
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
0x00 WORD e_magic; //5A4D * MZ标记用于判断是否为可执行文件
0x02 WORD e_cblp; //0090
0x04 WORD e_cp; //0003
0x06 WORD e_crlc; //0000
0x08 WORD e_cparhdr; //0004
0x0a WORD e_minalloc; //0000
0x0c WORD e_maxalloc; //FFFF
0x0e WORD e_ss; //0000
0x10 WORD e_sp; //00B8
0x12 WORD e_csum; //0000
0x14 WORD e_ip; //0000
0x16 WORD e_cs; //0000
0x18 WORD e_lfarlc; //0040
0x1a WORD e_ovno; //0000
0x1c WORD e_res[4]; //0000000000000000
0x24 WORD e_oemid; //0000
0x26 WORD e_oeminfo; //0000
0x28 WORD e_res2[10]; //20
0x3c DWORD e_lfanew; //00000080 * PE头相对于文件的偏移,用于定位PE文件
0x00 WORD e_magic; //5A4D * MZ标记用于判断是否为可执行文件
0x02 WORD e_cblp; //0090
0x04 WORD e_cp; //0003
0x06 WORD e_crlc; //0000
0x08 WORD e_cparhdr; //0004
0x0a WORD e_minalloc; //0000
0x0c WORD e_maxalloc; //FFFF
0x0e WORD e_ss; //0000
0x10 WORD e_sp; //00B8
0x12 WORD e_csum; //0000
0x14 WORD e_ip; //0000
0x16 WORD e_cs; //0000
0x18 WORD e_lfarlc; //0040
0x1a WORD e_ovno; //0000
0x1c WORD e_res[4]; //0000000000000000
0x24 WORD e_oemid; //0000
0x26 WORD e_oeminfo; //0000
0x28 WORD e_res2[10]; //20
0x3c DWORD e_lfanew; //00000080 * PE头相对于文件的偏移,用于定位PE文件
 
 
 
 
typedef struct IMAGE_NT_HEADERS{
0x00      DWORD Signature;                    //PE标识
0x04      IMAGE_FILE_HEADER FileHeader;        //标准PE头(20字节)
0x18      IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //扩展PE头(大小不确定)
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
typedef struct IMAGE_NT_HEADERS{
0x00      DWORD Signature;                    //PE标识
0x04      IMAGE_FILE_HEADER FileHeader;        //标准PE头(20字节)
0x18      IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //扩展PE头(大小不确定)
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
0x00 WORD Machine;  //014C * 程序运行的CPU型号,0x0任何处理器/0x14C Intel 386及后续处理器
0x02 WORD NumberOfSections; //0008 * 文件中存在的节的总数,除了头,还有几节数据,如果要新增节或者合并节就要修改这个值.
0x04 DWORD TimeDateStamp; //3E22F0DF * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的
0x08 DWORD PointerToSymbolTable; //00000000
0x0c DWORD NumberOfSymbols; //00000000
0x10 WORD SizeOfOptionalHeader; //00E0 * 可选PE头的大小,32位PE文件默认E0h=16*1464位PE文件默认为F0h,大小可以自定义
0x12 WORD Characteristics; //010E * 每个位有不同的含义,可执行文件值为10F 0 1 2 3 8位置1
0x00 WORD Machine;  //014C * 程序运行的CPU型号,0x0任何处理器/0x14C Intel 386及后续处理器
0x02 WORD NumberOfSections; //0008 * 文件中存在的节的总数,除了头,还有几节数据,如果要新增节或者合并节就要修改这个值.
0x04 DWORD TimeDateStamp; //3E22F0DF * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的
0x08 DWORD PointerToSymbolTable; //00000000
0x0c DWORD NumberOfSymbols; //00000000
0x10 WORD SizeOfOptionalHeader; //00E0 * 可选PE头的大小,32位PE文件默认E0h=16*1464位PE文件默认为F0h,大小可以自定义
0x12 WORD Characteristics; //010E * 每个位有不同的含义,可执行文件值为10F 0 1 2 3 8位置1
 
 
 
 
 
 
 
 
0000 0001 0000 1110
0000 0001 0000 1110
0x00 WORD Magic; * 说明文件类型:10B->32位下的PE文件 20B->64位下的PE文件
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; * 代码大小/所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用
0x08 DWORD SizeOfInitializedData; * 已初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x0c DWORD SizeOfUninitializedData; * 未初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x10 DWORD AddressOfEntryPoint; * 程序入口
0x14 DWORD BaseOfCode; * 代码开始的基址,编译器填的 没用
0x18 DWORD BaseOfData; * 数据开始的基址,编译器填的 没用
0x1c DWORD ImageBase; * 内存镜像基址
0x20 DWORD SectionAlignment; * 内存对齐,区段对齐
0x24 DWORD FileAlignment; * 文件对齐
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; *镜像大小/ 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍,是拉伸之后的大小,,,
0x3c DWORD SizeOfHeaders; * 所有头+节表,技照‘文件对齐 FileAlignment’后的大小,否则加载会出错
0x40 DWORD CheckSum; * 校验和,一些系统文件有要求,用来判断文件是否被修改
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve; * 初始化时保留的堆栈大小
0x4c DWORD SizeOfStackCommit; * 初始化时实际提交的大小
0x50 DWORD SizeOfHeapReserve; * 初始化时保留的堆大小
0x54 DWORD SizeOfHeapCommit; * 初始化时实践提交的大小
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes; * 目录项数目,RVA数目和大小
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; 16个结构体,每个结构体是8个字节
0x00 WORD Magic; * 说明文件类型:10B->32位下的PE文件 20B->64位下的PE文件
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; * 代码大小/所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用
0x08 DWORD SizeOfInitializedData; * 已初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x0c DWORD SizeOfUninitializedData; * 未初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x10 DWORD AddressOfEntryPoint; * 程序入口
0x14 DWORD BaseOfCode; * 代码开始的基址,编译器填的 没用
0x18 DWORD BaseOfData; * 数据开始的基址,编译器填的 没用
0x1c DWORD ImageBase; * 内存镜像基址
0x20 DWORD SectionAlignment; * 内存对齐,区段对齐
0x24 DWORD FileAlignment; * 文件对齐
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; *镜像大小/ 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍,是拉伸之后的大小,,,
0x3c DWORD SizeOfHeaders; * 所有头+节表,技照‘文件对齐 FileAlignment’后的大小,否则加载会出错
0x40 DWORD CheckSum; * 校验和,一些系统文件有要求,用来判断文件是否被修改
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve; * 初始化时保留的堆栈大小
0x4c DWORD SizeOfStackCommit; * 初始化时实际提交的大小
0x50 DWORD SizeOfHeapReserve; * 初始化时保留的堆大小
0x54 DWORD SizeOfHeapCommit; * 初始化时实践提交的大小
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes; * 目录项数目,RVA数目和大小
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; 16个结构体,每个结构体是8个字节
 
 
 
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int* OpenFile()
{
    FILE* PointToFile = NULL;
    int FileSize = 0;
    int* StrBuffer = NULL;
    int Num = 0;
 
    //打开文件
    if ((PointToFile = fopen("C:\\notepad.exe","rb")) == NULL) {
        printf("打开文件失败!\n");
        exit(1);
    }
 
    //获取文件大小
    fseek(PointToFile,0,2);
    FileSize = ftell(PointToFile);
 
    //重定位指针
    fseek(PointToFile,0,0);
 
    //buffer指向申请的堆
    StrBuffer = (int*)(malloc(FileSize));
    if (!StrBuffer)
    {
        printf("堆空间分配失败!\n");
        free(StrBuffer);
        return 0;
    }
 
    //读取文件内容
    Num = fread(StrBuffer,FileSize,1,PointToFile);
    if (!Num)
    {
        printf("读取文件内容失败!\n");
        free(StrBuffer);
        return 0;
    }
 
    //关闭文件
    fclose(PointToFile);
 
    //将缓冲区内的文件内容的地址返回到调用函数的地方
    return StrBuffer;
}
 
int* FileSizes = OpenFile();
 
int PrintfNtHeaders()
{
    //文件指针
    unsigned int* PointBuffer = (unsigned int*)FileSizes;
    unsigned short* pBuffer = (unsigned short*)PointBuffer;
    unsigned char* pcBuffer = (unsigned char*)PointBuffer;
 
    //判断MZ和PE的标志
    unsigned short Cmp1 = 0x5A4D;
    unsigned int Cmp2 = 0x00004550;
 
    //判断文件是否读取成功
    if(!PointBuffer)
    {
        printf("文件读取失败!\n");
        free(PointBuffer);
        return 0;
    }
 
    //判断是否为MZ标志
    if (*pBuffer != Cmp1)
    {
        printf("不是有效MZ标志!\n");
        printf("%X\n",*pBuffer);
        free(PointBuffer);
        return 0;
    }
    printf("*********打印DOS头*********\n");
    printf("e_magic:\t\t\t%X\n",*(pBuffer));
    printf("e_ifanew:\t\t\t%08X\n\n\n",*(PointBuffer+15));
 
    //判断是否为PE标志
    if (*(PointBuffer+56) != Cmp2)
    {
        printf("不是有效的PE标志!\n");
        printf("%X\n",*(PointBuffer+56));
        free(PointBuffer);
        return 0;
    }
 
    printf("*********打印标准PE文件头*********\n");
 
    printf("PE标志:\t\t\t\t%X\n",*(PointBuffer+56));
 
    printf("Machine:\t\t\t%04X\n",*(pBuffer+114));
    printf("NumberOfSection:\t\t%04X\n",*(pBuffer+115));
    printf("TimeDateStamp:\t\t\t%08X\n",*(PointBuffer+58));
    printf("PointerToSymbolTable:\t\t%08X\n",*(PointBuffer+59));
    printf("NumberOfSymbols:\t\t%08X\n",*(PointBuffer+60));
    printf("SizeOfOptionalHeader:\t\t%04X\n",*(pBuffer+122));
    printf("Chrarcteristics:\t\t%04X\n\n\n",*(pBuffer+123));
 
    printf("*********打印标准可选PE头*********\n");
 
    printf("Magic:\t\t\t\t%04X\n", *(pBuffer+124));
    printf("MajorLinkerVersion:\t\t%02X\n", *(pcBuffer+250));
    printf("MinorLinkerVersion:\t\t%02X\n", *(pcBuffer+251));
    printf("SizeOfCode:\t\t\t%08X\n", *(PointBuffer+63));
    printf("SizeOfInitializedData:\t\t%08X\n", *(PointBuffer+64));
    printf("SizeOfUninitializedData:\t%08X\n", *(PointBuffer+65));
    printf("AddressOfEntryPoint:\t\t%08X\n", *(PointBuffer+66));
    printf("BaseOfCode:\t\t\t%08X\n", *(PointBuffer+67));
    printf("BaseOfData:\t\t\t%08X\n", *(PointBuffer+68));
    printf("ImageBase:\t\t\t%08X\n", *(PointBuffer+69));
    printf("SectionAlignment:\t\t%08X\n", *(PointBuffer+70));
    printf("FileAlignment:\t\t\t%08X\n", *(PointBuffer+71));
    printf("MajorOperatingSystemVersion:\t%04X\n", *(pBuffer+144));
    printf("MinorOperatingSystemVersion:\t%04X\n", *(pBuffer+145));
    printf("MajorImageVersion:\t\t%04X\n", *(pBuffer+146));
    printf("MinorImageVersion:\t\t%04X\n", *(pBuffer+147));
    printf("MajorSubsystemVersion:\t\t%04X\n", *(pBuffer+148));
    printf("MinorSubsystemVersion:\t\t%04X\n", *(pBuffer+149));
    printf("Win32VersionValue:\t\t%08X\n", *(PointBuffer+75));
    printf("SizeOfImage:\t\t\t%08X\n", *(PointBuffer+76));
    printf("SizeOfHeaders:\t\t\t%08X\n", *(PointBuffer+77));
    printf("CheckSum:\t\t\t%08X\n", *(PointBuffer+78));
    printf("Subsystem:\t\t\t%04X\n", *(pBuffer+158));
    printf("DllCharacteristics:\t\t%04X\n", *(pBuffer+159));
    printf("SizeOfStackReserve:\t\t%08X\n", *(PointBuffer+80));
    printf("SizeOfStackCommit:\t\t%08X\n", *(PointBuffer+81));
    printf("SizeOfHeapReserve:\t\t%08X\n", *(PointBuffer+82));
    printf("SizeOfHeapCommit:\t\t%08X\n", *(PointBuffer+83));
    printf("LoaderFlags:\t\t\t%08X\n", *(PointBuffer+84));
    printf("NumberOfRvaAndSizes:\t\t%08X\n", *(PointBuffer+85));
 
    free(PointBuffer);
    return 0;
}
 
int main()
{
    PrintfNtHeaders();
    OpenFile();
    return 0;
}
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int* OpenFile()
{
    FILE* PointToFile = NULL;
    int FileSize = 0;
    int* StrBuffer = NULL;
    int Num = 0;
 
    //打开文件
    if ((PointToFile = fopen("C:\\notepad.exe","rb")) == NULL) {
        printf("打开文件失败!\n");
        exit(1);
    }
 
    //获取文件大小
    fseek(PointToFile,0,2);
    FileSize = ftell(PointToFile);
 
    //重定位指针
    fseek(PointToFile,0,0);
 
    //buffer指向申请的堆
    StrBuffer = (int*)(malloc(FileSize));
    if (!StrBuffer)
    {
        printf("堆空间分配失败!\n");
        free(StrBuffer);
        return 0;
    }
 
    //读取文件内容
    Num = fread(StrBuffer,FileSize,1,PointToFile);
    if (!Num)
    {
        printf("读取文件内容失败!\n");
        free(StrBuffer);
        return 0;
    }
 
    //关闭文件
    fclose(PointToFile);
 
    //将缓冲区内的文件内容的地址返回到调用函数的地方
    return StrBuffer;
}
 
int* FileSizes = OpenFile();
 
int PrintfNtHeaders()
{
    //文件指针
    unsigned int* PointBuffer = (unsigned int*)FileSizes;
    unsigned short* pBuffer = (unsigned short*)PointBuffer;
    unsigned char* pcBuffer = (unsigned char*)PointBuffer;
 
    //判断MZ和PE的标志
    unsigned short Cmp1 = 0x5A4D;
    unsigned int Cmp2 = 0x00004550;
 
    //判断文件是否读取成功
    if(!PointBuffer)
    {
        printf("文件读取失败!\n");
        free(PointBuffer);
        return 0;
    }
 
    //判断是否为MZ标志
    if (*pBuffer != Cmp1)
    {
        printf("不是有效MZ标志!\n");
        printf("%X\n",*pBuffer);
        free(PointBuffer);
        return 0;
    }
    printf("*********打印DOS头*********\n");
    printf("e_magic:\t\t\t%X\n",*(pBuffer));
    printf("e_ifanew:\t\t\t%08X\n\n\n",*(PointBuffer+15));
 
    //判断是否为PE标志
    if (*(PointBuffer+56) != Cmp2)
    {
        printf("不是有效的PE标志!\n");
        printf("%X\n",*(PointBuffer+56));
        free(PointBuffer);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 4
支持
分享
最新回复 (4)
雪    币: 10023
活跃值: (4416)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2022-9-23 11:48
0
雪    币: 1423
活跃值: (1072)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
楼主能请教一个问题吗?一个pe文件运行后,写内存破坏MZ标记是不是会导致无法启动新的线程(调用NtCreateThread启动)?如果是有什么办法在MZ标记被破坏的情况下(不手动修复)启动线程吗?
2022-9-25 21:32
0
雪    币: 583
活跃值: (997)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
傲雪丶三生 楼主能请教一个问题吗?一个pe文件运行后,写内存破坏MZ标记是不是会导致无法启动新的线程(调用NtCreateThread启动)?如果是有什么办法在MZ标记被破坏的情况下(不手动修复)启动线程吗?
MZ就是可执行文件的文件标识,系统加载程序的,先读这个MZ,没有这个MZ,系统就不承认这个一个可执行文件,就没有必要读后面的东西
科锐官网的PE视频已经公开了,建议去看一下
2022-10-22 07:40
0
雪    币: 1423
活跃值: (1072)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
mb_kbkqyusp MZ就是可执行文件的文件标识,系统加载程序的,先读这个MZ,没有这个MZ,系统就不承认这个一个可执行文件,就没有必要读后面的东西 科锐官网的PE视频已经公开了,建议去看一下
(⊙﹏⊙)上面是已经成功执行后 手动写内存破坏
2022-10-24 21:54
0
游客
登录 | 注册 方可回帖
返回
//