二进制入门学习笔记-3.PE头

PE文件

1.0 PE简介

1.0.0什么是PE文件

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)。

1.0.1 PE文件的两种状态

PE文件在运行的时候,在内存中的状态和在硬盘当中是不一样的,要在内存中进行一次类似拉伸的操作才能够成功运行。如下图所示:

1.1PE头

PE头一般包含如下两个部分

  • DOS头
  • NT头

其中NT头包含如下三个部分:

IMAGE_NT_HEADERS STRUCT 
+0h DWORD Signature                    //PE文件标识
+4h   IMAGE_FILE_HEADER  FileHeader  //标准PE头
+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader    //可选PE头
IMAGE_NT_HEADERS ENDS

注:标准PE头和可选PE头都在NT头里,PE头的结构和内容如下两张图所示:

1.1.1 DOS头:

WORD   e_magic               //"MZ标记" 用于判断是否为可执行文件.
DWORD  e_lfanew;              //标准PE头相对于文件的偏移,用于定位标准PE文件头的开始位置

1.1.2、标准PE头:

WORD    Machine;              *             //程序运行的CPU型号:0x0 任何处理器/0x14C 386及后续处理器                      
WORD    NumberOfSections;     *             //文件中存在的节的总数,如果要新增节或者合并节 就要修改这个值.                       
DWORD   TimeDateStamp;        *             //时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的.                     
DWORD   PointerToSymbolTable;                                       
DWORD   NumberOfSymbols;                                        
WORD    SizeOfOptionalHeader; *             //可选PE头的大小,32位PE文件默认E0h 64位PE文件默认为F0h  大小可以自定义.                     
WORD    Characteristics;      *             //每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1                        

1.1.3 可选PE头

WORD    Magic;        *             //说明文件类型:10B 32位下的PE文件     20B 64位下的PE文件                        
BYTE    MajorLinkerVersion;                                     
BYTE    MinorLinkerVersion;                                     
DWORD   SizeOfCode;*                //所有代码节的和,必须是FileAlignment的整数倍 编译器填的  没用                        
DWORD   SizeOfInitializedData;*             //已初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的  没用                     
DWORD   SizeOfUninitializedData;*               //未初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的  没用                     
DWORD   AddressOfEntryPoint;*               //程序入口                      
DWORD   BaseOfCode;*                //代码开始的基址,编译器填的   没用                        
DWORD   BaseOfData;*                //数据开始的基址,编译器填的   没用                        
DWORD   ImageBase;*             //内存镜像基址                        
DWORD   SectionAlignment;*              //内存对齐                      
DWORD   FileAlignment;*             //文件对齐                      
WORD    MajorOperatingSystemVersion;                                        
WORD    MinorOperatingSystemVersion;                                        
WORD    MajorImageVersion;                                      
WORD    MinorImageVersion;                                      
WORD    MajorSubsystemVersion;                                      
WORD    MinorSubsystemVersion;                                      
DWORD   Win32VersionValue;                                      
DWORD   SizeOfImage;*               //内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍                     
DWORD   SizeOfHeaders;*             //所有头+节表按照文件对齐后的大小,否则加载会出错                      
DWORD   CheckSum;*              //校验和,一些系统文件有要求.用来判断文件是否被修改.                        
WORD    Subsystem;                                      
WORD    DllCharacteristics;                                     
DWORD   SizeOfStackReserve;*                //初始化时保留的堆栈大小                       
DWORD   SizeOfStackCommit;*             //初始化时实际提交的大小                   
DWORD   SizeOfHeapReserve;*             //初始化时保留的堆大小                        
DWORD   SizeOfHeapCommit;*              //初始化时实践提交的大小                   
DWORD   LoaderFlags;                                
DWORD   NumberOfRvaAndSizes;*               //目录项数目 

1.1.4 PE头解析

PE头使用代码进行解析需要明白PE文件大概又如下图所示的结构: 十六进制下的PE文件 使用c语言设计PE头解析程序,具体代码如下:

// shellcode_inject.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include<Windows.h>
#include<stdlib.h>

void * readFile(char * filepath){
    //printf(filepath);
    int filesize=0;
    FILE *filep=NULL;
    char *buffer=NULL;
    filep = fopen(filepath,"rb");
    if (filep == NULL){
        printf("打开文件失败!");
        return NULL;
    }
    fseek(filep,0,SEEK_END);
    filesize = ftell(filep);
    fseek(filep,0,SEEK_SET);
    //printf("filesize is %d\n",filesize);
    buffer = (char *)malloc(sizeof(char)*filesize);
    if(buffer == NULL){
        printf("内存分配失败!");
        fclose(filep);
        return NULL;
    }
    memset(buffer,0,filesize);
    size_t n = fread(buffer,1,filesize,filep);
    if(!n){
        printf("读取数据失败!");
        fclose(filep);
        free(buffer);
        return NULL;
    }
    fclose(filep);
    return buffer;
}
void readPEHeader(char *filebuffer){
    //声明所要用到的变量
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;

    IMAGE_FILE_HEADER PEHeader;
    PIMAGE_OPTIONAL_HEADER64 pOptionHeader;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;

    //获取DOS头
    printf("*********DOS头输出:**********\n");
    pDosHeader = (PIMAGE_DOS_HEADER)filebuffer;
    printf("MZ标志:%x\n",(*pDosHeader).e_magic);
    printf("NT头相对于文件初始位置的偏移:%x\n",(*pDosHeader).e_lfanew);
    printf("*********DOS头输出:**********\n");
    //获取NP头
    printf("*********NT头输出:**********\n");
    pNTHeader = PIMAGE_NT_HEADERS(filebuffer + (*pDosHeader).e_lfanew);
    printf("PE标记:%x\n",(*pNTHeader).Signature);
    printf("*********NT头输出:**********\n");

    //获取标准PE头
    printf("*********标准PE头输出:**********\n");
    PEHeader = (*pNTHeader).FileHeader;
    printf("Machine:%x\n",PEHeader.Machine);
    printf("节的数量为:0x%x\n",PEHeader.NumberOfSections);
    printf("可选PE头的大小:0x%x\n",PEHeader.SizeOfOptionalHeader);
    printf("*********标准PE头输出:**********\n");

    //获取可选PE头,我这里用的是64位的notepad.exe所以使用这个PIMAGE_OPTIONAL_HEADER64
    pOptionHeader = PIMAGE_OPTIONAL_HEADER64(filebuffer + (*pDosHeader).e_lfanew + sizeof((*pNTHeader).Signature)+sizeof((*pNTHeader).FileHeader));
    printf("*********可选头输出:**********\n");
    printf("Magic:0x%x\n",(*pOptionHeader).Magic);
    printf("*********可选PE头输出:**********\n");
    free(filebuffer);//释放内存

}
int main(int argc, char* argv[])
{
    char *filepath = "C:\\Users\\crls\\Desktop\\VC6.0green\\MyProjects\\PEReader\\Debug\\notepad.exe";
    char *filebuffer = (char *)readFile(filepath);
    readPEHeader(filebuffer);
    return 0;
}