侧边栏壁纸
博主头像
胜星的博客博主等级

行动起来,活在当下

  • 累计撰写 23 篇文章
  • 累计创建 38 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

PE文件格式

胜星
2021-03-30 / 0 评论 / 2 点赞 / 608 阅读 / 5889 字

183352bbbh2r00byh2z5n0.jpg

PE文件结构

0. 基本概念

  • PE文件

    • 是一种文件格式,目前所学的 exe,dll,sys等都是PE文件
    • 通过检查DOS头的"MZ"(0x5A4D)和NT头的"PE"(0x00004550)**来判断一个文件是不是有效的PE文件
  • 术语解释

    • RVA: 数据在虚拟内存中的偏移地址
    • VA: 数据在虚拟内存中的绝对地址
      • VA = 实际加载基址 + RVA
    • FOA: 数据在文件中偏移地址
      • FOA = 需要转换的RVA - 所在的区段RVA + 所在区段的FOA

1. PE 头信息

  • IMAGE_DOS_HEADER

    • e_magic: "MZ" - 0x5A4D
    • e_lfanew:指向NT头的偏移
  • IMAGE_NT_HEADERS

    • signature: "PE" - 0x00004550
    • IMAGE_FILE_HEADER
      • NumberOfSections:区段的数量
      • SizeOfOptionalHeader: 扩展头的大小,会被用于查找区段表
      • Characteristics:可以判断当前是不是DLL是不是32位重定位已分离(表示不需要重定位)
    • IMAGE_OPTIONAL_HEADER32
      • Magic:CPU架构:x32(0x10B)x64(0x20B)
      • AddressOfEntryPoint:程序的入口点(OEP) RVA
      • ImageBase:镜像的默认加载基址
      • SectionAlignment :内存对齐粒度,通常是0x1000,要求大于文件粒度
      • FileAlignment:文件对齐粒度,通常是0x200, 拉伸
      • SizeOfImage:镜像大小,即内存中的大小最后一个区段的RVA + 最后一个区段的大小
      • SizeOfHeaders:整个PE头部大小,通常是 0x400
      • DllCharacteristics :特征标识0x40 有随机基址
      • NumberOfRvaAndSizes:数据目录表个数 0x10
      • DataDirectory:数据目录表
    • IMAGE_SECTION_HEADER(重要)

      • Name[8]:区段名
      • virtualSize:内存中大小
      • VirtualAddress:内存位置RVA
      • SizeOfRawData:文件中大小
      • PointerToRawData:文件位置FOA
      • Characteristics:区段属性(可读可写可执行),是否有代码

2. 导入表

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;		// INT
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;			//时间戳
    DWORD   ForwarderChain; 
    DWORD   Name;				// 模块名称
    DWORD   FirstThunk;				// IAT 
} IMAGE_IMPORT_DESCRIPTOR;
  • 导入表中保存的是一组 以全0为结尾MAGE_IMPORT_DESCRIPTOR 结构。

    • INT:导入名称表,指向了一组以0结尾IMAGE_THUNK_DATA 结构,可以找到函数的ID或名称,但是有些时候其中会被填充为0,在文件和内存中它们保存的值是相同的。
      • 当 IMAGE_THUNK_DATA 字段的最高位为1,表示这是一个序号导入的函数
        • 序号的值为 0x0000???? & 0x0000FFFF = 0x0000????
      • 最高位为0,表示这是一个名称导入的函数,
        • 名称和序号保存在 0x???????? 的位置 IMAGE_IMPORT_BY_NAME
    • IAT:导入地址表,在文件中存放的是INT的数据,当程序被加载到内存时,会被填充为函数的真实地址。

    1546962820709

3. 导出表

typedef struct _IMAGE_EXPORT_DIRECTORY {
    // 省略了无意义的字段
    DWORD   Name;			// 模块名RVA
    DWORD   Base;			// 序号的起始数值
    DWORD   NumberOfFunctions;		// 函数的数量
    DWORD   NumberOfNames;		// 有名字的函数的数量
    DWORD   AddressOfFunctions;     	// 导出地址表的的RVA 【DWORD】
    DWORD   AddressOfNames;         	// 导出名称表的RVA【DWORD-RVA】
    DWORD   AddressOfNameOrdinals;  	// 导出序号表的RVA【WORD】
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
  • 导出表只能有一个
  • 名称表的数量和序号表的数量相同
  • 导出函数的序号 = 基址(Base) + 索引(函数地址表下标)

1546963200361

4. 重定位表

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;	//需重定位的地址
    DWORD   SizeOfBlock;	//重定位块大小
//  WORD    TypeOffset[1];
} IMAGE_BASE_RELOCATION;
  • 重定位表由多组重定位块组成,每个重定位块保存的是一个分页(RVA)中需要重定位的所有数据。
  • 重定位项的个数 = (SizeOfBlock– sizeof(IMAGE_BASE_RELOCATION)) / 2
  • 每一个重定位项都是一个WORD类型的值,标识了当前数据的类型以及偏移(相对VirtualAddress)
  • 需要重定位的数据所在的位置 = 实际加载基址 + VirtualAddress + 重定位数据的偏移,其中保存的是一个偏移
  • 重定位公式:需要重定位的地址 - 默认加载基址 + 实际加载基址

5. 资源表

  • 三层结构

    • 一层:资源种类;二层:此种资源的个数,名称; 三层:资源数据
    • 每一层都是 IMAGE_RESOURCE_DIRECTORY 开始,后面跟着**一组 ** IMAGE_RESOURCE_DIRECTORY_ENTRY结构体,保存的是资源的类型和名称
    • 结构体个数 = NumberOfNamedEntries + NumberOfIdentries​
  • 第一层:第二层:

    • 资源的类型名称,当 NameIsString 为1时,标识这是一个自定义的名称,使用 NameOffset字段可以找到 IMAGE_RESOURCE_DIR_STRING_U 字段,其中保存了字符串名称。
  • 第三层:

    • 指向 IMAGE_RESOURCE_DATA_ENTRY,保存了数据的偏移大小

6. 其他表

  • TLS表 IMAGE_TLS_DIRECTORY

    • 使用 __declspec(thread) int nNum = 99; 定义TLS变量

    • TLS 函数的原型如下,和DllMain相比,它们只有返回值类型不同

      Void NTAPI t_TlsCallBackA(PVOID DllHandle,DWORD Reason,PVOID Red);
      
    • TLS 函数的调用顺序位于main函数前,通常用于进行反调试

2

评论区