0


PE头解析-字段说明

一、什么是可执行文件

1、可执行文件(executable file)指的是可以由操作系统进行加载执行的文件。

2、可执行文件的格式:

  • Windows平台:- PE(Portable Executable)文件结构
  • Linux平台:- ELF(Executable and Linking Format)文件结构

二、如何识别PE文件

1、PE文件的特征(PE指纹)

分别打开

.exe .dll .sys

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

2、不要仅仅通过文件的后缀名来认定PE文件

三、对齐

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

1、硬盘对齐(200h)

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

2、内存对齐(1000h)

四、为什么要分节

1、节省硬盘空间.(这个不是决定的,由编译器决定)

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

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

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

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

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

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

2、一个应用程序多开

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

PE磁盘文件与内存映像图

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

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

DOS头(64个字节)

typedef struct IMAGE_DOS_HEADER{
      WORD e_magic;         //DOS头的标识,为4Dh和5Ah。分别为字母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文件

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

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

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

PE文件头(NT头)

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; 
​

标准PE头(20字节)

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*14,64位PE文件默认为F0h,大小可以自定义
0x12 WORD Characteristics; //010E * 每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1
​

TimeDateStamp

.map

文件是对

.exe

文件中函数的描述,对

.exe

文件的说明

.map

文件和

.exe

文件 不同步时

就是检查 时间戳是否 一致

Characteristics

打勾的 即为1

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

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

0000 0001 0000 1110

可选PE头(大小是不确定的)

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

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个字节

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

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

编写程序读取一个.exe文件,输出所有的PE头信息.

通过指针偏移方式实现

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

#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>
#include <windows.h>
#include <malloc.h>
​
#define F_PATH "C:\\cntflx\\ipmsg.exe"
​
FILE* open_file(char* file_path,char* open_mode);
int compute_file_size(FILE* file_address);
char* allocate_buffer(int file_size);
char* readfile2memory(char* file_buffer,int file_size,FILE* file_address);
void analysis_PE_head(char* File_buffer);
​
VOID PrintNTHeaders()
{
    // 初始化
    //char file_path[] = "C:\\Windows\\System32\\notepad.exe";
    char file_path[] = F_PATH;
    char open_mode[] = "rb";
    // 打开文件,返回文件指针
    FILE* file_address = open_file(file_path,open_mode);
    // 计算文件长度
    int file_size = compute_file_size(file_address);
    // 分配内存
    char* File_buffer = allocate_buffer(file_size);
    // 写入内存,返回内存地址
    File_buffer = readfile2memory(File_buffer,file_size,file_address);
    // 打印PE头部信息
    analysis_PE_head(File_buffer);
    // 释放内存、关闭文件流
    free(File_buffer);
    fclose(file_address);
}
​
FILE* open_file(char* file_path,char* open_mode)
{
    FILE* file_address = fopen(file_path,open_mode);  // fopen() 参数是字符串也就是常量指针类型
    if(!file_address)
    {
        printf("打开文件失败!\r\n");
        return 0;
    }
    return file_address;
}
​
int compute_file_size(FILE* file_address)
{
    int size = 0;
    fseek(file_address,0,SEEK_END);
    size = ftell(file_address);
    fseek(file_address,0,SEEK_SET);
    return size;
}
​
char* allocate_buffer(int file_size)
{
    char* file_buffer = (char*)malloc(file_size);
    if(!file_buffer)
    {
        printf("申请内存失败!\r\n");
        return 0;
    }
    memset(file_buffer,0,file_size);
    return file_buffer;
}
​
char* readfile2memory(char* file_buffer,int file_size,FILE* file_address)
{
    if(!(fread(file_buffer,file_size,1,file_address)))
    {
        printf("从文件向内存中读取数据失败!\r\n");
        return 0;
    }
    return file_buffer; // 如果写入内存成功,则返回内地址
}
​
void analysis_PE_head(char* File_buffer)
{
    // 实例化PE文件头几个结构体
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    // 强制类型转换
    pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
    // 判断是不是有效的MZ标志
    if(*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标志!\r\n");
        free(File_buffer);
        return;
    }
    // 强制类型转换 PIMAGE_DOS_HEADER结构体
    pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
    // 打印DOS头
    printf("=============================DOS头信息如下=============================\r\n");
    printf("MZ标志:\t\t\t%04X\r\n",pDosHeader->e_magic);
    printf("PE偏移:\t\t\t%08X\r\n",pDosHeader->e_lfanew);
​
    // 判断是不是有效的PE标志
    if(*((PDWORD)((DWORD)File_buffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志!\r\n");
        free(File_buffer);
        return;
    }
    // 强制类型转换 PIMAGE_NT_HEADERS结构体
    pNTHeader = PIMAGE_NT_HEADERS((DWORD)File_buffer+pDosHeader->e_lfanew);
    // 打印NT头
    printf("=============================NT头信息如下===============================\r\n");
    printf("NT:\t\t\t\t%04X\r\n",pNTHeader->Signature);
    // 强制类型转换 PIMAGE_FILE_HEADER结构体
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4);
    // 打印标准PE文件头
    printf("=============================标准PE头信息如下============================\r\n");
    printf("PE_machine:\t\t\t%04X\r\n",pPEHeader->Machine);
    printf("NumberOfSections:\t\t%04X\n",pPEHeader->NumberOfSections);
    printf("SizeOfOptionalHeader:\t\t%04X\r\n",pPEHeader->SizeOfOptionalHeader);
    // 强制类型转换
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);//
    // 打印可选PE头
    printf("==============================可选PE头信息如下==============================\r\n");
    printf("Magic:\t\t\t\t%04X\r\n",pOptionHeader->Magic);
    printf("AddressOfEntryPoint:\t\t%08X\r\n",pOptionHeader->AddressOfEntryPoint);
    printf("ImageBase:\t\t\t%08X\r\n",pOptionHeader->ImageBase);
    printf("SizeOfImage:\t\t\t%08X\r\n",pOptionHeader->SizeOfImage);
    printf("SizeOfHeaders:\t\t\t%08X\r\n",pOptionHeader->SizeOfHeaders);
    printf("SectionAlignment:\t\t%08X\r\n",pOptionHeader->SectionAlignment);
    printf("FileAlignment:\t\t\t%08X\r\n",pOptionHeader->FileAlignment);
    // 强制类型转换
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
    printf("==============================节表信息如下===============================\n");
    //printf("name:%s\n",pSectionHeader->Misc);
    DWORD dwNumberOfSection = pPEHeader->NumberOfSections;
    /*
    printf("%x\n",pPEHeader->NumberOfSections);
    printf("IMAGE_SIZEOF_SHORT_NAME:%x\n",IMAGE_SIZEOF_SHORT_NAME);
    printf("option_add:%x\n",pOptionHeader);
    printf("psection_add:%x\n",pSectionHeader);
    printf("==============================================================\n");*/
    for(DWORD i = 0;i<dwNumberOfSection;i++,pSectionHeader++)
    {
        printf("========================第%d个节信息:===============================\n",i+1);
        printf("section_name:");
        for(DWORD j = 0;j<IMAGE_SIZEOF_SHORT_NAME;j++)
        {
            printf("%c",pSectionHeader->Name[j]);
        }
        printf("\r\n");
        printf("Misc:\t\t\t\t%08X\r\n",pSectionHeader->Misc);
        printf("VirtualAddress:\t\t\t%08X\r\n",pSectionHeader->VirtualAddress);
        printf("SizeOfRawData:\t\t\t%08X\r\n",pSectionHeader->SizeOfRawData);
        printf("PointerToRawData:\t\t%08X\r\n",pSectionHeader->PointerToRawData);
        printf("Characteristics:\t\t%08X\r\n",pSectionHeader->Characteristics);
    }
}
​
int main(int argc, char* argv[])
{
    PrintNTHeaders();
    //getchar();
    return 0;
}
标签: windows 安全

本文转载自: https://blog.csdn.net/qq_51600802/article/details/127551409
版权归原作者 骑着驴子追宝马 所有, 如有侵权,请联系我们删除。

“PE头解析-字段说明”的评论:

还没有评论