[逆向] PE文件学习

世上唯一不能复制的是时间,唯一不能重演的是人生,唯一不劳而获的是年龄。该怎么走,过什么样的生活,全凭自己的选择和努力。人生很贵,请别浪费!与智者为伍,与良善者同行。[逆向] PE文件学习,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

概述

花费了好长时间,总算弄清楚了PE文件

学完了之后,总的感觉对PE文件的学习,就是学习各种各样的结构体,以及各种结构体关系

正是因为学会不易,所以做这个记录,基本是各种结构体的定义说明,已经找函数的方法什么的,,,以便下次忘记了能很快想起来

PE文件基本结构


image-20200302193444845

PE内存映射关系


image-20200302193734251

MS-DOS 头部


IMAGE_DOS_HEADER

IMAGE_DOS_HEADER STRUCT 
{ 
+0h WORD e_magic   // Magic DOS signature MZ(4Dh 5Ah)     DOS可执行文件标记 
+2h   WORD  e_cblp  //    文件最后页的字节数
+4h WORD  e_cp   // 					//文件页数
+6h WORD  e_crlc   //重定义元素个数
+8h WORD  e_cparhdr    //头部尺寸 以段落为单位
+0ah WORD  e_minalloc  //所需的最小附加段
+0ch WORD  e_maxalloc //所需的最大附加段
+0eh WORD  e_ss    //DOS代码的初始化堆栈SS 
+10h WORD  e_sp    //DOS代码的初始化堆栈指针SP 
+12h WORD  e_csum    //校验和
+14h WORD  e_ip    // DOS代码的初始化指令入口[指针IP] 
+16h WORD  e_cs    //  DOS代码的初始堆栈入口 
+18h WORD  e_lfarlc    //重分配表文件地址
+1ah WORD  e_ovno        //覆盖号
+1ch WORD  e_res[4]    // 保留字
+24h WORD  e_oemid    //OEM标识符
+26h WORD      e_oeminfo     //OEM信息
+29h WORD  e_res2[10]  //保留字
+3ch DWORD   e_lfanew     //指向PE文件头 
} IMAGE_DOS_HEADER ENDS

没什么重要信息,需要注意的是 标志字,还有指向PE头的指针

PE头


IMAGE_NT_HEADER

typedef struct _IMAGE_NT_HEADERS {		//由 e_lfanew指向
    +0h		DWORD Signature;						//标志位 PE00
    +4h 	IMAGE_FILE_HEADER FileHeader;			//20字节
    +18h 	IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

标志位 PE00 基本上通过十六进制编辑器很容易找到

紧接着20字节的 IMAGE_FILE_HEADER PE文件头结构

然后就是可选头,虽然是可选但是可选头实际上是PE文件最重要的地方

IMAGE_FILE_HEADER


typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;					//运行平台
    WORD    NumberOfSections;			//文件的区块数目 节的数目
    DWORD   TimeDateStamp;				//文件创建日期和时间
    DWORD   PointerToSymbolTable;		//指向符号表(调试)
    DWORD   NumberOfSymbols;			//符号表中符号个数(调试)
    WORD    SizeOfOptionalHeader;		//IMAGE_OPTIONAL_HEADER32结构大小 一般是224 (E0)
    WORD    Characteristics;			//文件属性 具体查表
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine 运行平台


Value   Meaning
IMAGE_FILE_MACHINE_I386  0x014c  x86
IMAGE_FILE_MACHINE_IA64  0x0200  Intel Itanium
IMAGE_FILE_MACHINE_AMD64 0x8664  x64

Characteristics 文件属性



#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM                    0x1000  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

IMAGE_OPTIONAL_HEADER32


typedef struct_IMAGE_OPTIONAL_HEADER
{
	//Standard fields
	
	+18h WORD Magic;			//标志字 ROM映像文件(0107h) 32位PE是0x10b 64位PE是0x20b
	+1Ah BYTE MajorLinkerVersion;	//主版本号 连接器的版本号
	+1Bh BYTE MinorLinkerVersion;	//次版本号 连接器的版本号
	+1Ch DWORD SizeOfCode;			//所有含代码的节的总大小 (代码段的长度,如果有多个代码段,则是代码段长度的总和。)
	+20h DWORD SizeOfInitializedData;	//所有含已初始化数据的节的总大小 eg.0e00(3584)
	+24h DWORD SizeOfUninitializedData;	//所有含未初始化数据的节的大小	eg. 1400(5120)
	+28h DWORD AddressOfEntryPoint;		//程序执行入口RVA		(为啥我的程序是00 00 00 00呢)
	+2Ch DWORD BaseOfCode;				//代码段起始地址的RVA
	+30h DWORD BaseOfData;				//数据段起始地址的RVA
	
	//NT additional fields	//以下是属于NT结构增加的领域
	+ DWORD   ImageBase; //可执行文件默认装入的基地址 可以由编译器指定 默认40 00 00
    DWORD   SectionAlignment; //内存中块的对齐值(默认的块对齐值为1000H,4KB个字节)
    DWORD   FileAlignment;//文件中块的对齐值(默认值为200H字节,为了保证块总是从磁盘的扇区开始的)
    WORD    MajorOperatingSystemVersion;//要求操作系统的最低版本号的主版本号
    WORD    MinorOperatingSystemVersion;//要求操作系统的最低版本号的次版本号
    WORD    MajorImageVersion;//该可执行文件的主版本号
    WORD    MinorImageVersion;//该可执行文件的次版本号
    WORD    MajorSubsystemVersion;//要求最低之子系统版本的主版本号 默认 0004
    WORD    MinorSubsystemVersion;//要求最低之子系统版本的次版本号 默认 0000
    DWORD   Win32VersionValue;//保留字                            默认00000000
    DWORD   SizeOfImage;//映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
    DWORD   SizeOfHeaders;//所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。
    DWORD   CheckSum;//CRC检验和                                   一般为00000000
    WORD    Subsystem;//程序使用的用户接口子系统
    WORD    DllCharacteristics;//DLLmain函数何时被调用,默认为0
    DWORD   SizeOfStackReserve;//初始化时堆栈大小
    DWORD   SizeOfStackCommit;//初始化时实际提交的堆栈大小
    DWORD   SizeOfHeapReserve;//初始化时保留的堆大小
    DWORD   SizeOfHeapCommit;//初始化时实际提交的对大小
    +72 DWORD   LoaderFlags;//与调试有关,默认为0 92h
    +74h DWORD   NumberOfRvaAndSizes;//数据目录结构的数目 16 96h
    +78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
};

DllCharacteristics (仅仅相对于DLL文件来说)



#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040     // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY    0x0080     // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT    0x0100     // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200     // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH       0x0400     // Image does not use SEH.  No SE handler may reside in this image
#define IMAGE_DLLCHARACTERISTICS_NO_BIND      0x0800     // Do not bind this image.
//                                            0x1000     // Reserved.
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER   0x2000     // Driver uses WDM model
//                                            0x4000     // Reserved.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE     0x8000

运行方式 Subsystem


#define IMAGE_SUBSYSTEM_UNKNOWN              0   // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE               1   // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Image runs in the Windows character subsystem.
#define IMAGE_SUBSYSTEM_OS2_CUI              5   // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI            7   // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Image runs in the Windows CE subsystem.
#define IMAGE_SUBSYSTEM_EFI_APPLICATION      10  //
#define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER  11   //
#define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER   12  //
#define IMAGE_SUBSYSTEM_EFI_ROM              13
#define IMAGE_SUBSYSTEM_XBOX                 14
#define IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16
DllCharacteristics:DLL的文件属性,只对DLL文件有效,可以是下面定义中某些的组合:
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040     // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY    0x0080     // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT    0x0100     // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200     // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH       0x0400     // Image does not use SEH.  No SE handler may reside in this image
#define IMAGE_DLLCHARACTERISTICS_NO_BIND      0x0800     // Do not bind this image.
//                                            0x1000     // Reserved.
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER   0x2000     // Driver uses WDM model
//                                            0x4000     // Reserved.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE     0x8000

数据目录列表含义


typedef struct _IMAGE_DATA_DIRECTORY {                
    DWORD   VirtualAddress;                //内存偏移
    DWORD   Size;                //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;                

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16 

分别是:导出表、导入表、资源表、异常信息表、安全证书表、重定位表、调试信息表、版权所以表、全局指针表 TLS表、加载配置表、绑定导入表、IAT表、延迟导入表、COM信息表 最后一个保留未使用。

image-20200307022107718

数据目录 共16个条目

节属性



IMAGE_SECTION_HEADER STRUCT
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 8个字节的节区名称
union Misc
  DWORD PhysicalAddress;       
  DWORD VirtualSize;            //节区的尺寸
ends
DWORD VirtualAddress;         // 节区的 RVA 地址
DWORD SizeOfRawData;            // 在文件中对齐后的尺寸 (节处理后大小)
DWORD PointerToRawData;        // 在文件中的偏移量 (节数据地址)
DWORD PointerToRelocations;     // 在OBJ文件中使用,重定位的偏移
DWORD PointerToLinenumbers;   // 行号表的偏移(供调试使用地)
WORD NumberOfRelocations;      // 在OBJ文件中使用,重定位项数目
WORD NumberOfLinenumbers;    // 行号表中行号的数目
DWORD Characteristics;       // 节属性如可读,可写,可执行等
IMAGE_SECTION_HEADER ENDS

在找数据在文件中的位置的时候 需要先减去VirtualAddress 然后再加上 PointerToRawData

节属性 Characteristics


IMAGE_SCN_CNT_CODE 0x00000020  
The section contains executable code.
包含代码,常与 0x10000000一起设置。

IMAGE_SCN_CNT_INITIALIZED_DATA 
0x00000040
The section contains initialized data.
该区块包含以初始化的数据。

IMAGE_SCN_CNT_UNINITIALIZED_DATA
0x00000080
The section contains uninitialized data.
该区块包含未初始化的数据。

IMAGE_SCN_MEM_DISCARDABLE
0x02000000
The section can be discarded as needed.
该区块可被丢弃,因为当它一旦被装入后,
进程就不在需要它了,典型的如重定位区块。

IMAGE_SCN_MEM_SHARED
0x10000000
The section can be shared in memory.
该区块为共享区块。

IMAGE_SCN_MEM_EXECUTE
0x20000000
The section can be executed as code.
该区块可以执行。通常当0x00000020被设置
时候,该标志也被设置。

IMAGE_SCN_MEM_READ
0x40000000
The section can be read.
该区块可读,可执行文件中的区块总是设置该
标志。

IMAGE_SCN_MEM_WRITE
0x80000000
The section can be written to.
该区块可写。

参考https://www.cnblogs.com/qintangtao/archive/2013/01/11/2857180.html

PE文件到内存的映射


windows装载器装载时只是建立好虚拟地址和PE文件之间的映射关系

需要时才加载到内存,只有真正执行到某个内存也中的指令或者数据的时候,这个页面才会从磁盘提交到物理内存中。

系统装载可执行文件的方式和内存映射还不一样

导入表 IMAGE_IMPORT_DESCRIPTOR (IID)


由可选头结构的数据目录的[1]指向

每一个动态链接库对应一个这样的结构,为0的结构表示结束

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;  //指向上面的INT,是一个结构数组,里面保存了导入函数的信息(例如Msg的实际地址)       
    };                   //最后会以全0的结构为结束,其中每一项是一个结构,一项8个字节,是指向
                       //IMAGE_THUNK_DATA 看下面详解  
    DWORD   TimeDateStamp;           //时间,一般不用      

    DWORD   ForwarderChain;           //链表前一个结构,一般不用            
    DWORD   Name;            //上面说的DLL名称的RVA偏移通过偏移可以找到DLL名称
    DWORD   FirstThunk;               // IAT 的RVA偏移.和originalFirstThunk不同
} IMAGE_IMPORT_DESCRIPTOR;

OriginalFirstThunk -> 指向 INT

FirstThunk -> 指向IAT

image-20200304192507311

IMAGE_THUNK_DATA


最高位为1时 表示函数以序号方式输入,低31位看作一个函数序号

最高位为0时 表示函数以字符串类型的函数名方式输入,这时双字的值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString;
        PDWORD Function;
        DWORD Ordinal;
        PIMAGE_IMPORT_BY_NAME  AddressOfData;
    } u1;
} IMAGE_THUNK_DATA32;

IMAGE_IMPORT_BY_NAME


IMAGE_IMPORT_BY_NAME struct
{
	Hint WORD;	//也表示函数的序号 不过这个字段是可选的 有些编译器总是将他设置为0
	Name BYTE;	//定义了导入函数的名字字符串 这是一个以0为结尾的字符串
}

IAT


INT 是死的,但是IAT可以被装载器重写

装载器从 IMAGE_IMPORT_DESCRIPTOROriginalFirstThunk 指向 INT 通过里面的 IMAGE_THUNK_DATA 结构指向 IMAGE_IMPORT_BY_NAME 结构找到指定的函数,然后把函数地址放到IAT中

image-20200304193011477

加载器操作之后

真正的找导入表的方法

先根据 PE之后紧跟的节区表 解析好RVA地址 和 节区地址

根据 数据目录 找到导入表的地址

然后减去 节区的 RVA 地址 得到相对地址

相对地址加上在文件中的偏移量 得到 IID的位置(也即是保存导入的DLL的位置)

然后取得其中一个的OriginalFirstThunk 找到其中的地址

地址减去 节区的 RVA 地址 加上在文件中的偏移量 得到 INT的地址

通过INT地址找到INT, 判断值的最高位 为0则为指向IMAGE_IMPORT_BY_NAME结构的RVA指针

这个指针减去 节区的 RVA 地址 就是函数所在位置 前两个字节是序号 和软件对比 没有问题就找对了

今天最美的图片 (哭死)

image-20200305011214689

根据地址判断导入表在那个区 2554在2000 – 3000 所以是 .rdata

导出表 IMAGE_EXPORT_DIRECTORY (IED)


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;
Characteristics:现在没有用到,一般为0。
TimeDateStamp:导出表生成的时间戳,由连接器生成。
MajorVersion,MinorVersion:看名字是版本,实际貌似没有用,都是0。
Name:模块的名字。
Base:序号的基数,按序号导出函数的序号值从Base开始递增。
NumberOfFunctions:所有导出函数的数量。
NumberOfNames:按名字导出函数的数量。
AddressOfFunctions:一个RVA,指向一个DWORD数组,数组中的每一项是一个导出函数的RVA,顺序与导出序号相同。
AddressOfNames:一个RVA,依然指向一个DWORD数组,数组中的每一项仍然是一个RVA,指向一个表示函数名字。
AddressOfNameOrdinals:一个RVA,还是指向一个WORD数组,数组中的每一项与AddressOfNames中的每一项对应,表示该名字的函数在AddressOfFunctions中的序号。

导出函数结构图


image-20200305221522119

20130929222914343

来自: https://blog.csdn.net/adam001521/article/details/84658708**


在上图中,AddressOfNames指向一个数组,数组里保存着一组RVA,每个RVA指向一个字符串,这个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals中的对应项。获取导出函数地址时,先在AddressOfNames中找到对应的名字,比如Func2,他在AddressOfNames中是第二项,然后从AddressOfNameOrdinals中取出第二项的值,这里是2,表示函数入口保存在AddressOfFunctions这个数组中下标为2的项里,即第三项,取出其中的值,加上模块基地址便是导出函数的地址。如果函数是以序号导出的,那么查找的时候直接用序号减去Base,得到的值就是函数在AddressOfFunctions中的下标。

找导出函数的练习

随便找了一个DLL Binglib.dll 手动解析结果如下(不熟练 花费了足足一个半小时 不过已经会找了)

运行平台: 01 4C -> x86
文件区块数目: 0x 00 05 -> 5个区块
文件属性: 21 02 -> ‭0010000100000010‬ ->
	1. #define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable
	2. #define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
	3. #define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
	
标志字: 01 0B -> 表示32位PE
连接器版本: 0A 00 -> 10.0
程序执行入口: 27 4D
代码段起始RVA地址: 10 00
数据段起始RVA地址: 40 00
默认装入地址: 10 00 00 00
内存中块的对齐值: 10 00
文件对齐值: 02 00
映像大小: 90 00
所有文件头大小: 04 00
程序使用的用户接口子系统: 00 02 -> 
	#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI 
	
节信息:
	.text
	节区尺寸: 20 75
	节区RVA: 10 00
	对齐之后大小: 22 00
	文件中偏移量: 04 00
	节的属性: 60 00 00 20 -> ‭01100000000000000000000000100000‬
		1. IMAGE_SCN_CNT_CODE 0x00000020  包含代码,常与 0x10000000一起设置。
		2. IMAGE_SCN_MEM_EXECUTE 0x20000000 该区块可以执行。通常当0x00000020被设置时候,该标志也被设置。
		3. IMAGE_SCN_MEM_READ 0x40000000 该区块可读,可执行文件中的区块总是设置该标志。
		
	.rdata
	节区尺寸: 17 7C
	节区RVA: 40 00
	对齐之后大小: 18 00
	文件中偏移: 26 00
	节的属性: 40 00 00 40 -> ‭01000000000000000000000001000000‬
		1. IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 该区块包含以初始化的数据。
		2. IMAGE_SCN_MEM_READ 0x40000000 该区块可读,可执行文件中的区块总是设置该标志。
	
	.data
	节区尺寸: 06 94
	节区RVA: 60 00
	对齐之后大小: 02 00
	文件中偏移: 3e 00
	...
	
	rsrc
	节区尺寸: 05 c0
	节区RVA: 70 00
	对齐之后大小: 06 00
	文件偏移: 40 00
	...
	
	.reloc
	节区尺寸: 07 80
	节区RVA: 80 00
	对齐之后大小: 08 00
	文件偏移: 46 00
	...
	
导出表信息: 55 30 (由上面节区信息可得在 .rdata 节区)
文件中位置: 55 30 - 40 00 + 26 00 = 3B 30
DLL实际名称: 55 E4 - 40 00 + 26 00 = 3B E4 (42 69 6e 67 6c 69 62 2e 64 6c 6c 00) Binglib.dll 
起始序号: 01
导出函数总数: 0e -> 14个
以名称方式导出: 0e -> 14个
函数地址数组RVA: 55 58 - 40 00 + 26 00 = 3B 58(既然是RVA 则应该是DWORD) 例如此时值是19 a0 根据节区范围得知是在 .text 节区计算得 DA0就是函数真实位置
函数名称数组RVA: 55 90 - 40 00 + 26 00 = 3B 90(值仍然是RVA)55 f0 -> 3B F0 (??0MyFileW@@QAE@XZ) 第一项
函数序号数组RVA: 55 C8 - 40 00 + 26 00 = 3B C8(WORD类型的值)

软件结果

image-20200307041051993

完美 非常谨慎的结果就是 没有差错 找到了函数所在位置 哈哈

重定位

Windows使用重定位机制保证以上代码无论模块加载到哪个基址都能正确被调用

为什么需要重定位?我的理解是,假设说一个dll文件被加载,里面的函数或者常数地址是不确定的,不顺利的情况下,模块的基址会变,那么以找基址和模块内偏移寻址的方式就是出错,就会出现找不到函数地址的情况。

过程

  1. 编译的时候由编译器识别出哪些项使用了模块内的直接VA,比如push一个全局变量、函数地址,这些指令的操作数在模块加载的时候就需要被重定位。

  2. 链接器生成PE文件的时候将编译器识别的重定位的项纪录在一张表里,这张表就是重定位表,保存在DataDirectory中,序号是 IMAGE_DIRECTORY_ENTRY_BASERELOC。

  3. PE文件加载时,PE 加载器分析重定位表,将其中每一项按照现在的模块基址进行重定位。

_IMAGE_BASE_RELOCATION


typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;	//页起始地址RVA。
    DWORD   SizeOfBlock;	//表示该分组保存了几项重定位项。
//  WORD    TypeOffset[1];	//这个域有两个含义,大家都知道,页内偏移用12位就可以表示,剩下的高4位用来表示重定位的类型。而事实上,Windows只用了一种类型IMAGE_REL_BASED_HIGHLOW  数值是 3。
} IMAGE_BASE_RELOCATION;

总结


需要重定位的

  1. 代码中使用全局变量的指令,因为全局变量一定是模块内的地址,而且使用全局变量的语句在编译后会产生一条引用全局变量基地址的指令。

  2. 将模块函数指针赋值给变量或作为参数传递,因为赋值或传递参数是会产生mov和push指令,这些指令需要直接地址。

  3. C++中的构造函数和析构函数赋值虚函数表指针,虚函数表中的每一项本身就是重定位项

完结撒花 ★,°:.☆( ̄▽ ̄)/$:.°★

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/225594.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!