zoukankan      html  css  js  c++  java
  • 逆向学习-PE文件格式

    从DOS头到节区头是PE头部分,其下的节区合称PE体。文件中使用偏移(offset),内存中使用VA(Virtual Address,虚拟地址)来表示位置。文件加载到内存时,情况就会发生变化(节区的大小、位置等)。文件的内容一般可分为代码(.text)、数据(.data)、资源(,rsrc)节,分别保存。

    VA指的是进程虚拟内存的绝对地址,RVA(Relative Virtuall Address,相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址。

    VA与RVA满足:RVA+ImageBase=VA         PE头内部信息大多以RVA形式存在。

    DOS头

      IMAGE_DOS_HEADER结构体大小为64字节。

      其中有2个重要成员:e_magic与e_lfanew。(分别在结构体的开头和结尾)

      e_magic: DOS签名(signature, 4D5A=>ASCII值“MZ”)

      e_lfanew: 指示NT头的偏移(根据不同文件拥有可变值)。

    NT头

      IMAGE_NT_HEADERS结构体由3个成员组成,第一个成员为签名(Signature)结构体,其值为50450000h(“PE”00)。另外两个成员分别为文件头(File Header)与可选头(Optional Header)结构体。IMAGE_NT_HEADERS结构体的大小为F8。

     文件头

      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;

      IMAGE_FILE_HEADER结构体有4种重要成员。

      1.Machine,每个cpu都有唯一的Machine码。

      2.NumberOfSections  用来指出文件中存在的节区数量,要与实际节区数相同。

      3.SizeOfOptionalHeader  用来指出IMAGE_OPTIONAL_HEADER32结构体的长度。(32位和64位是不同的)

      4.Characteristics     用于标识文件的属性,文件是否是可运行的形态、是否为DLL文件等信息,以bit OR形式组合起来。 0x0002代表可执行文件,0x2000代表DLL文件。

     可选头

      typedef struct _IMAGE_OPTIONAL_HEADER { 

      WORD Magic; 10B 32位PE 20B 64位PE 107 ROM映像
      BYTE MajorLinkerVersion; 链接器版本号
      BYTE MinorLinkerVersion; 链接器副版本号
      DWORD SizeOfCode; 所有代码节的总和 该大小是基于文件对齐后的大小
      DWORD SizeOfInitializedData; 所有含已初始化数据的节的总大小
      DWORD SizeOfUninitializedData; 所有含未初始化数据的节的大小
      DWORD AddressOfEntryPoint; 程序执行入口RVA
      DWORD BaseOfCode; 代码节的起始RVA
      DWORD BaseOfData; 数据节的起始RVA
      DWORD ImageBase; 程序的优先装载地址
      DWORD SectionAlignment; 内存中节的对齐粒度
      DWORD FileAlignment; 文件中节的对齐粒度
      WORD MajorOperatingSystemVersion; 操作系统主版本号
      WORD MinorOperatingSystemVersion; 操作系统副版本号
      WORD MajorImageVersion; PE文件映像的版本号
      WORD MinorImageVersion;
      WORD MajorSubsystemVersion; 子系统的版本号
      WORD MinorSubsystemVersion;
      DWORD Win32VersionValue; 未用 必须设置0
      DWORD SizeOfImage; 内存中整个PE文件的映像尺寸
      DWORD SizeOfHeaders; 所有节表按照文件对齐粒度后的大小
      DWORD CheckSum; 校验和
      WORD Subsystem; 指定使用界面的子系统
      WORD DllCharacteristics; DALL文件属性
      DWORD SizeOfStackReserve; 初始化时保留的栈的大小
      DWORD SizeOfStackCommit; 初始化时实际提交的栈的大小
      DWORD SizeOfHeapReserve; 初始化时保留的堆的大小
      DWORD SizeOfHeapCommit; 初始化时实际提交的堆的大小
      DWORD LoaderFlags; 加载标志 未用
      DWORD NumberOfRvaAndSizes; 下面的数据目录结构的数量
      IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
      重要成员

      1.Magic  32位时,值为10B,64位时,值为20B。

      2.AddressOfEntryPoint  持有EP的RVA值,指出程序最先执行的代码起始地址。

      3.ImageBase  进程虚拟内存的范围是0~FFFFFFFF(32位)。ImageBase指出文件的优先装入地址。EXE、DLL文件被装载到用户内存的0~7FFFFFFF中,SYS加载到80000000~FFFFFFFF。一般而言,EXE的ImageBase的值为00400000,DLL文件的ImageBase的值为10000000.文件载入后的地址为ImageBase+AddressOfEntryPoint。

      4.SectionAlignment,FileAlignment   FileAlignment指定了节区在磁盘文件中的最小单位,SectionAlignment指定了节区在内存中的最小单位。磁盘文件或内存的节区大小必定为他们的值的整数倍。

      5.SizeOfImage  指定了PE Image在虚拟内存中所占空间的大小。一般而言,文件的大小与加载到内存中的大小是不同的。

      6.SizeOfHeader  指出整个PE头的大小。该值必须是FileAlignment的整数倍。第一节区所在位置与SizeHeaders距文件开始偏移的量相同。

      7.Subsystem  区分系统驱动文件和普通的可执行文件。

      8.NumberOfRvaAndSizes  用来指定DataDirectory数组的个数。

      9.DataDirectory  是由IMAGE_DATA_DIRECTORY结构体组成的数组。 

        #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 导出表
        #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 导入表
        #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 资源目录
        #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 异常目录
        #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 安全目录
        #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 重定位基本表
        #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 调试目录
        #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 描术字串
        #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 机器值
        #define IMAGE_DIRECTORY_ENTRY_TLS 9 TLS目录
        #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 载入配值目录
        #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 绑定输入表
        #define IMAGE_DIRECTORY_ENTRY_IAT 12 导入地址表
        #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 延迟载入描述
        #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 COM信息

    节区头

      

      VirtualSize  内存中节区所占大小

      VirtualAddress  内存中节区起始地址(RVA)  不带有任何值,由SectionAlignment确定

      SizeOfRawData  磁盘文件中节区所占大小

      PointerToRawData  磁盘文件中节区起始位置  不带有任何值,由FileAlignment确定

      Characteristics  节区属性(bit OR)

    RVA to RAW

      RAW = RVA - VirtualAddress + PointerToRawData

    IAT(Import Address Table,导入地址表)

      IMAGE_IMPORT_DESCRIPTOR

        记录着PE文件要导入哪些库文件。

      typedef struct _IMAGE_IMPORT_DESCRIPTOR {

      union {
      DWORD Characteristics;
      DWORD OriginalFirstThunk; //INT(Import Name Table) address (RVA)
      };
      DWORD TimeDateStamp;
      DWORD ForwarderChain;
      DWORD Name; //library name string address (RVA)
      DWORD FirstThunk; //IAT(Import Address Table) address (RVA)
      } IMAGE_IMPORT_DESCRIPTOR;

      typedef struct _IMAGE_IMPORT_BY_NAME {
      WORD Hint; //ordinal
      BYTE Name[1]; //function name string
      } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

        重要成员:

          OriginalFirstThunk  INT的地址(RVA)

          Name  库名称字符串的地址(RVA)

          FirstThunk  IAT的地址(RVA)

        导入函数输入至IAT的顺序:

          1.读取IID的Name成员,获取库名称字符串("kernel32.dll")

          2.装载相应库——————> LoadLibrary("kernel32.dll")

          3.读取IID的OriginalFirstThunk成员,获取INT地址

          4.逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA)

          5.使用IMAGE_IMPORT_BY_NAME的Hint(ordinal) 或Name项,获取相应函数的起始地址。 GetProcAddress("GetCurrentThreadId")

          6.读取IID的FirstThunk(IAT)成员,获得IAT地址

          7.将上面获得的函数地址输入相应IAT数组值。

          8.重复以上补助4~7,直到INT结束(遇到NULL时)

    EAT

      IMAGE_EXPORT_DIRECTORY

        typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD Characteristics; // 未使用,总为0 DWORD TimeDateStamp; // 文件创建时间戳
        WORD MajorVersion; // 未使用,总为0 WORD MinorVersion; // 未使用,总为0
        DWORD Name; // 指向一个代表此 DLL名字的 ASCII字符串的 RVA
        DWORD Base; // 函数的起始序号
        DWORD NumberOfFunctions; // 导出函数的总数 DWORD NumberOfNames; // 以名称方式导出的函数的总数 DWORD AddressOfFunctions; // 指向输出函数地址的RVA
        DWORD AddressOfNames; // 指向输出函数名字的RVA
        DWORD AddressOfNameOrdinals; // 指向输出函数序号的RVA} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

        重要成员:

          NumberOfFunctions  实际Export函数的个数

          NumberOfNames    Export中具名的函数个数

          AddressOfFunctions  Export函数地址数组

          AddressOfName    函数名称地址数组

          AddressOfNameOrdinals  Ordinal地址数组

        GetProcAddress()操作原理:

          (1)利用AddressOfNames成员转到“函数名称数组”。

          (2)“函数名称数组”中存储着字符串地址。通过比较字符串,查找指定的函数名称(此时数组的索引称为name_index)。

          (3)利用AddressOfNameOrdinals成员,转到ordinal数组。

          (4)在ordinal数组中通过name_index查找相应ordinal值。

          (5)利用AddressOfFunctions成员转到“函数地址数字”(EAT)

          (6)在“函数地址数组”中将刚刚求得的ordinal用作数组索引,获得指定函数的起始地址。

  • 相关阅读:
    Twitter如何在数千台服务器上快速部署代码?
    系统架构师学习笔记_第六章(上)_连载
    使用IIS内置压缩功能,增加网站访问速度
    系统架构师学习笔记_第八章_连载
    微软企业库4.1学习笔记(十五)缓存模块3 使用数据库作为后端存储
    快速搞懂 SQL Server 的锁定和阻塞
    微软企业库4.1学习笔记(十四)缓存模块2 使用缓存模块进行开发
    微软企业库4.1学习笔记(十六)缓存模块4 服务器场中的缓存使用
    Agile PLM Engineering Collaboration
    EC Client Customizing EC Client 客户化
  • 原文地址:https://www.cnblogs.com/whitehawk/p/10761631.html
Copyright © 2011-2022 走看看