• PE文件解析(1):Dos头与NT头


    什么是PE文件
    PE文件是在windows平台可执行的文件。
    包括:.exe(可执行程序),dll(动态链接库).sys(驱动程序)

    这是PE文件的基本结构:
    在这里插入图片描述

    DOS头

    Dos头是PE文件的起始位置,它

    typedef struct _IMAGE_DOS_HEADER {      
        WORD   e_magic;      //PE文件的标识,PE文件的标识一定是5A4D             
        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];                  
        LONG   e_lfanew;  //Dos头距离Nt头的距离,是一个偏移                  
      } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    DOS头部分,有用的就两个参数:e_magic 和 e_lfanew

    • e_magic可以确定它是否是一个PE文件
    • e_lfanew是一个偏移,通过它可以找到NT头部分,即标准PE文件的位置。

    使用010editor来解析PE文件: 这是一个标准DOS头的组成部分。
    e_magic: 5A4D 标识它是一个PE文件(exe dll)等
    e_lfanew: 0110,这是一个偏移地址,表示从Dos头开始位置到标准NT部分的偏移,这里为110。
    在这里插入图片描述

    使用代码解析DOS头部分

    1. 使用CreateFile打开文件句柄
    2. GetFileSize获取文件读取的数量
    3. ReadFile读取文件内容,存储到一个数组中,这个数组存储的就是一个PE文件的基址,即是一个起始地址。
    4. FileBuff可以被转换为DOS头。
    BOOL PE::Load(const char* DllName)
    {
    	HANDLE MainHandle =  CreateFileA(
    		DllName,
    		GENERIC_READ | GENERIC_WRITE,
    		FILE_SHARE_READ,
    		NULL,
    		OPEN_EXISTING,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL
    	);
    	DWORD FileSize =  GetFileSize(MainHandle, NULL);
    	char* FileBuff = new char[FileSize]{NULL};
    	DWORD RealSize = 0;
    	BOOL IsSuccess =  ReadFile(MainHandle, FileBuff, FileSize, &RealSize, NULL);
    	if (!IsSuccess)
    	{
    		printf("读取文件失败!\n");
    		return FALSE;
    	}
    	pDosHeader = (PIMAGE_DOS_HEADER)FileBuff;
    	return TRUE;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    NT头

    NT头结构体:

    IMAGE_NT_HEADERS STRUCT
    {
    +0h       DWORD    Signature
    +4h       IMAGE_FILE_HEADER    FileHeader
    +18h      IMAGE_OPTIONAL_HEADER32   OptionalHeader
    } IMAGE_NT_HEADERS ENDS
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 前四个字节是一个标识,即PE文件的标识
    2. 第二个参数是一个标准PE文件的一个结构体
    3. 第三个参数是一个可选PE文件的结构体

    Signature: 一个5A4D的标识,标识是一个PE文件。

    这里表示的就是NT标识的部分:
    他的全四个字节是一个DWORD的标识,Signature字段被设置为4550h,ASCII码为”PE00“。
    在这里插入图片描述
    可以看到它的偏移地址就是110h ,别忘了我们上面的DOS头部分的e_lfanew的值就是110h ,它表示的就是Dos头到NT头的偏移地址。

    注意:我们使用010editor来查看的是偏移地址,不是真正的地址,需要加上FileBuff的基址,才是真正的地址。


    标准NT头

    标准NT头也叫做文件头。

    structIMAGE_FILE_HEADER
    {
    	WORD Machine;//运行平台
    	WORD NumberOfSections;//区块表的个数
    	DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数
    	DWORD PointerToSymbolicTable;//指向符号表的指针
    	DWORD NumberOfSymbols;//符号表的数目
    	WORD SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0
    	WORD Characteristics;//文件的属性值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里面比较重要的字段是:

    1. NumberOfSections:表示区段的个数,这个很重要,之后我们会讲到。
    2. SizeOfOptionalHeader:表示可选NT头大小,通过它我们可以得到区段表的位置。

    这里是标准NT头的部分:
    在这里插入图片描述

    代码实现标准PE头:

    PIMAGE_NT_HEADERS pNtHeader;	//NT头
    PIMAGE_FILE_HEADER pFileHeader;	//标准NT头
    
    //首先要找到NT头的位置:基址+偏移地址
    NtHeader = (PIMAGE_NT_HEADERS)(FileBuff + pDosHeader->e_lfanew);
    pFileHeader =&pNtHeader->FileHeader;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    pNtHeader包含了标准NT头与可选NT头,取地址即可以到达。


    可选NT头

    typedefstruct_IMAGE_OPTIONAL_HEADER
    {
    +18h    WORD    Magic;// 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
    +1Ah    BYTE    MajorLinkerVersion;// 链接程序的主版本号
    +1Bh    BYTE    MinorLinkerVersion;// 链接程序的次版本号
    +1Ch    DWORD   SizeOfCode;// 所有含代码的节的总大小
    +20h    DWORD   SizeOfInitializedData;// 所有含已初始化数据的节的总大小
    +24h    DWORD   SizeOfUninitializedData;// 所有含未初始化数据的节的大小
    +28h    DWORD   AddressOfEntryPoint;// 程序执行入口RVA
    +2Ch    DWORD   BaseOfCode;// 代码的区块的起始RVA
    +30h    DWORD   BaseOfData;// 数据的区块的起始RVA
    //
    // NT additional fields.    以下是属于NT结构增加的领域。
    //
    +34h    DWORD   ImageBase;// 程序的首选装载地址
    +38h    DWORD   SectionAlignment;// 内存中的区块的对齐大小
    +3Ch    DWORD   FileAlignment;// 文件中的区块的对齐大小
    +40h    WORD    MajorOperatingSystemVersion;// 要求操作系统最低版本号的主版本号
    +42h    WORD    MinorOperatingSystemVersion;// 要求操作系统最低版本号的副版本号
    +44h    WORD    MajorImageVersion;// 可运行于操作系统的主版本号
    +46h    WORD    MinorImageVersion;// 可运行于操作系统的次版本号
    +48h    WORD    MajorSubsystemVersion;// 要求最低子系统版本的主版本号
    +4Ah    WORD    MinorSubsystemVersion;// 要求最低子系统版本的次版本号
    +4Ch    DWORD   Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0
    +50h    DWORD   SizeOfImage;// 映像装入内存后的总尺寸
    +54h    DWORD   SizeOfHeaders;// 所有头 + 区块表的尺寸大小
    +58h    DWORD   CheckSum;// 映像的校检和
    +5Ch    WORD    Subsystem;// 可执行文件期望的子系统
    +5Eh    WORD    DllCharacteristics;// DllMain()函数何时被调用,默认为 0
    +60h    DWORD   SizeOfStackReserve;// 初始化时的栈大小
    +64h    DWORD   SizeOfStackCommit;// 初始化时实际提交的栈大小
    +68h    DWORD   SizeOfHeapReserve;// 初始化时保留的堆大小
    +6Ch    DWORD   SizeOfHeapCommit;// 初始化时实际提交的堆大小
    +70h    DWORD   LoaderFlags;// 与调试有关,默认为 0
    +74h    DWORD   NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
    +78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    // 数据目录表
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    重要的有:

    1. AddressOfEntryPoint:!!!非常重要,它是程序的入口点,即OEP,它是一个偏移,需要加上基址,才是真正的在程序中的地址。
    2. ImageBase:程序的基址,在没有随机基址的情况下,ImageBase + AddressOfEntryPoint 就是真正的程序地址,我们就找到了程序的代码执行起始位置。
    3. NumberOfRvaAndSizes:数据目录表,非常重要,这里面包含了了导出表,导入表,重定位表,等各种信息,我们之后会解释。

    这里是可选PE头的部分
    在这里插入图片描述

    代码解析:

    PIMAGE_OPTIONAL_HEADER pOptionalHeader//可选NT头
    
    //得到可选NT头的
    pOptionalHeader = &pNtHeader->OptionalHeader;
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    HCE OS------操作系统基础操作
    基础-MySQL
    Scala基础【常用方法补充、模式匹配】
    微信小程序里配置less
    【C语言】动态内存管理 经典笔试题
    cpu和gpu已过时,npu和apu的时代开始
    Pikachu靶场之SSRF服务器端请求伪造
    用DIV+CSS技术设计的数码购物商城网站(web前端网页制作课作业)
    IO中节点流和处理流的理解学习
    「Python循环结构」利用while循环求1~n的平方和
  • 原文地址:https://blog.csdn.net/jj6666djdbbd/article/details/127604515