zoukankan      html  css  js  c++  java
  • PE结构详解

    PE结构详解

    EXE和DLL文件之间的区别完全是语义上的,因为他们使用完全相同的PE格式。而唯一的区别就是用一个字段标识出这个文件是EXE还是DLL。

    64位Windows只是对PE格式做了一些简单的修饰,新格式叫PE32+。并没有任何新的结构加进去,改变的只是简单的将32位字段扩展成64位。

    PE格式定义的主要地方位于我们的头文件winnt.h,这个头文件中几乎能找到关于PE文件的所有定义
    PE文件中的数据结构一般都有32位和64位之分,一般在名称上会表现出来:例如IMAGE_NT_HEADERS32 或 IMAGE_NT_HEADER64

    我们的讲解主要是基于现在主流的32位PE格式来进行研究的,64位请依此类推,因为64位版本只是对32位做了一些简单的扩展,结构几乎是一摸一样的。


    PE的基本概念
    PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的结构。

    文件的内容被分割为不同的区块,块中包含代码或数据。各个区块按页边界来对齐,区块没有大小限制,是一个连续的结构。

    此外,每个块有自己在内存中的一套属性,比如说这个区块是否包含代码、是否只读或可读/写等。

    认识PE文件不是作为单一内存映射文件被装入内存是很重要的。

    Windows加载器(又称PE装载器)遍历PE文件并决定文件的哪一部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中。

    当磁盘文件一旦被装入内存中,磁盘上的数据结构布局和内存中的数据结构布局是一致的。

    这样如果知道在磁盘的数据结构中寻找一些内容,那么几乎都能在被装入到内存映射文件中找到相同的信息。

    但数据之间的相对位置可能改变,其某项的偏移地址可能区别于原始的偏移位置,不管怎样,所有表现出来的信息都允许从磁盘文件偏移到内存偏移的转换。

    基地址ImageBase:内存中的头地址,就是模块的句柄,通过HMODULE GetModuleHandle(LPCTSTR lpModuleName)函数可以获取

    相对虚拟地址RVA:在内存中,每一个区块虚拟地址,相对于基地址的偏移(相减)

    文件偏移地址:就是在PE文件在硬盘中,各个区块相对文件头的偏移


    MS - DOS 头部
    每个PE文件是以一个DOS程序开始的,有了它,一旦程序在DOS下执行,DOS才能识别出这是有效的执行体。

    PE 文件的第一个字节起始于一个传统的MS-DOS 头部,被称作 IMAGE_DOS_HEADER。

    MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构如下:
    //WINNT.H

    typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
    USHORT e_magic; // 魔术数字
    USHORT e_cblp; // 文件最后页的字节数
    USHORT e_cp; // 文件页数
    USHORT e_crlc; // 重定义元素个数
    USHORT e_cparhdr; // 头部尺寸,以段落为单位
    USHORT e_minalloc; // 所需的最小附加段
    USHORT e_maxalloc; // 所需的最大附加段
    USHORT e_ss; // 初始的SS值(相对偏移量)
    USHORT e_sp; // 初始的SP值
    USHORT e_csum; // 校验和
    USHORT e_ip; // 初始的IP值
    USHORT e_cs; // 初始的CS值(相对偏移量)
    USHORT e_lfarlc; // 重分配表文件地址
    USHORT e_ovno; // 覆盖号
    USHORT e_res[4]; // 保留字
    USHORT e_oemid; // OEM标识符(相对e_oeminfo)
    USHORT e_oeminfo; // OEM信息
    USHORT e_res2[10]; // 保留字
    LONG e_lfanew; // 新exe头部的文件地址 DWORD双字,4个字节
    } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    第一个域e_magic,被称为魔术数字,它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D,表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。还有许多其它的域对于MS-DOS操作系统来说都有用,但是对于Windows NT来说,这个结构中只有一个有用的域——最后一个域e_lfnew,一个4字节的文件偏移量,PE文件头部就是由它定位的。对于Windows NT的PE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。


    PE 文件头
    PE 文件头(PE Header)紧挨着DOS stub
    PE Header 是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,里边包含着许多PE装载器用到的重要字段。

    执行体在支持PE文件结构的操作系统中执行时,PE装载器将从IMAGE_DOS_HEADER结构中的e_lfanew字段里找到PE Header的起始偏移量,加上基地址就得到PE文件头的指针。

    PNTHeader = ImageBase + dosHeader -> e_lfanew

    IMAGE_NT_HEADER 结构

    typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature; 04h //00004550h PE的标志
    IMAGE_FILE_HEADER FileHeader;//一个结构,文件头的说明
    IMAGE_OPTIONAL_HEADER OptionalHeader;//一个结构,文件头的说明
    } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

    typedef struct _IMAGE_FILE_HEADER {
    WORD Machine;//运行的平台
    WORD NumberOfSections;//文件的区块数目
    DWORD TimeDateStamp;//文件的创建日期和时间
    DWORD PointerToSymbolTable;
    DWORD NumberOfSymbols;
    WORD SizeOfOptionalHeader;
    WORD Characteristics;
    } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

    如何换算RVA和文件偏移呢?
    当处理PE文件时候,任何的RVA必须经过到文件偏移的换算,才
    能用来定位并访问文件中的数据,但换算却无法用一个简单的公式来
    完成,事实上,唯一可用的方法就是最土最笨的方法:
    步骤一:循环扫描区块表得出每个区块在内存中的起始RVA(根据
    IMAGE_SECTION_HEADER 中的VirtualAddress字段),并根据区
    块的大小(根据IMAGE_SECTION_HEADER 中的SizeOfRawData
    字段)算出区块的结束RVA(两者相加即可),最后判断目标RVA是
    否落在该区块内。
    步骤二:通过步骤一定位了目标RVA处于具体的某个区块中后,那
    么用目标RVA减去该区块的起始RVA,这样就能得到目标RVA
    相对于起始地址的偏移量RVA2.
    步骤三:在区块表中获取该区块在文件中所处的偏移地址(根据
    IMAGE_SECTION_HEADER 中的PointerToRawData字段), 将这
    个偏移值加上步骤二得到的RVA2值,就得到了真正的文件偏移地
    址。








  • 相关阅读:
    css伪类和伪对象
    多浏览器css兼容分析小结
    Hibernate3源码分析之hibernate.cfg.xml配置文件与SessionFactory类
    php示例代码之使用mysqli对象
    C#中Web Service身份验证
    未能转换为类型库。类型库导出程序在处理,时遇到了错误。错误: 找不到元素
    MS RDLC reportview 在 Chrome中不显示查询结果的原因
    C# 创建ActiveX
    How to rebuild performance counters on Windows Vista/Server2008/7/Server2008R2
    jqGrid中文说明文档——选项设置
  • 原文地址:https://www.cnblogs.com/poli/p/4959224.html
Copyright © 2011-2022 走看看