文件偏移地址

更新时间:2022-04-14 23:26

文件偏移地址是指数据在PE文件中的地址,是文件在磁盘上存放时相对于文件开头的偏移。文件偏移地址从pe文件的第一个字节开始计数,起始值为0。用十六进制工具(如WINHEX)打开文件所显示的地址就是文件偏移地址。

名词解释

文件偏移:静态反汇编工具看到的PE文件中某条指令的位置是相对于磁盘文件的。IDA Pro虽然是静态反汇编工具,不过出来的是VA。

装载基址(Image Base):PE文件装入内存的 基地址。默认情况下,EXE文件的基址为0x00400000,DLL文件的基址为0x10000000。

虚拟内存地址(VA):PE文件中的指令被装入内存后的地址,OllyDbg动态反汇编产生。

相对虚拟地址(RVA):内存地址相对与映射基址的偏移量。

VA = Image Base + RVA。

PE文件中的数据按照磁盘数据标准存放,以0x200字节为基本单位进行组织。 代码装入内存后,将按照内存数据标准存放,以0x1000字节为基本单位进行组织。

以下内容将十分有助于理解上述抽象名词:

文件组织

在Windows系统下,当一个PE应用程序运行时,这个PE文件在磁盘中的数据结构布局和内存中的数据结构布局是一致的。系统在载入一个可执行程序时,首先是Windows装载器(又称PE装载器)把磁盘中的文件映射到进程的地址空间,它遍历PE文件并决定文件的哪一部分被映射。其方式是将文件较高的偏移位置映射到较高的内存地址中。磁盘文件一旦被装入内存中,其某项的偏移地址可能与原始的偏移地址有所不同,但所表现的是一种从磁盘文件偏移到内存偏移的转换。

PE文件内存映射

当PE文件被加载到内存后,内存中的版本称为模块(Module),映射文件的起始地址称为模块句柄(hModule),可以通过模块句柄访问内存中的其他数据结构。这个初始内存地址也称为文件映像基址(ImageBase)。载入一个PE程序的主要步骤如下:

(1)当PE文件被执行时,PE装载器首先为进程分配一个4GB的虚拟地址空间,然后把程序所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下,会映射到虚拟地址空间中0x400000的位置。装载一个应用程序的时间比一般人所设想的要少,因为装载一个PE文件并不是把这个文件一次性地从磁盘读到内存中,而是简单地做一个内存映射,映射一个大文件和映射一个小文件所花费的时间相差无几。当然,真正执行文件中的代码时,操作系统还是要把存在于磁盘上的虚拟内存中的代码交换到物理内存(RAM)中。但是,这种交换也不是把整个文件所占用的虚拟地址空间一次性地全部从磁盘交换到物理内存中,操作系统会根据需要和内存占用情况交换一页或多页。当然,这种交换是双向的,即存在于物理内存中的一部分当前没有被使用的页,也可能被交换到磁盘中。

(2)PE装载器在内核中创建进程对象和主线程对象以及其他内容。

(3)PE装载器搜索PE文件中的Import Table(引入表),装载应用程序所使用的动态链接库。对动态链接库的装载与对应用程序的装载方法完全类似。

(4)PE装载器执行PE文件首部所指定地址处的代码,开始执行应用程序主线程。

2.2.3 Big-endian和Little-endian

PE Header中IMAGE_FILE_HEADER的成员Machine 中的值,根据winnt.h中的定义,对于Intel CPU应该为0x014c。但是用十六进制编辑器打开PE文件时,看到这个WORD显示的却是4c 01。其实4c 01就是0x014c,只不过由于Intel CPU是Little-endian,所以显示出来是这样的。对于Big-endian和Little-endian,请看下面的例子。一个整型int变量,长度为4个字节。当这个整形变量的值为0x12345678时,对于Big-endian来说,显示的是{12,34,45,78},而对于Little-endian来说,显示的却是{78,45,34,12}。注意Intel使用的是Little-endian。

2.2.4 3种不同的地址

PE文件的各种结构中,涉及到很多地址、偏移。有些是指在文件中的偏移,有些 是指在内存中的偏移。以下的第一种是指在文件中的地址,第二、三种是指在内存中的地址。

第一种,文件中的地址。比如用十六进制编辑器打开PE文件,看到的地址(偏移)就是文件中的地址,使用某个结构的文件地址,就可以在文件中找到该结构。

第二种,当文件被整个映射到内存时,例如某些PE分析软件,把整个PE文件映射到内存中,这时是内存中的虚拟地址(VA)。如果知道在这个文件中某一个结构的内存地址的话,那么它等于这个PE文件被映射到内存的地址加上该结构在文件中的地址。

第三种,当执行PE时,PE文件会被载入器载入内存,这时经常需要的是RVA。例如知道一个结构的RVA,那么程序载入点加上RVA就可以得到该结构的内存地址。比如,如果PE文件装入虚拟地址(VA)空间的0x400000处,某一结构的RVA 为0x1000,那么其虚拟地址为0x401000。

PE文件格式要用到RVA,主要是为了减少PE装载器的负担。因为每个模块都有可能被重载到任何虚拟地址空间,如果让PE装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用RVA,那么PE装载器就不必操心那些东西了,即它只要将整个模块重定位到新的起始VA。这就像相对路径和绝对路径的概念:RVA类似相对路径,VA就像绝对路径。

注意,RVA和VA是指内存中,不是指文件中。是指相对于载入点的偏移而不是一个内存地址,只有RVA加上载入点的地址,才是一个实际的内存地址。

免责声明
隐私政策
用户协议
目录 22
0{{catalogNumber[index]}}. {{item.title}}
{{item.title}}