一、FileBuffer到ImageBuffer常见的误区
1.文件执行的总过程
- 一个硬盘上的文件读入到虚拟内存中(FileBuffer),是原封不动的将硬盘上的文件数据复制一份放到虚拟内存中
- 接着如果文件要运行,需要先将FileBuffer中的文件数据"拉伸",重载到每一个可执行文件的4GB虚拟内存中!此时称文件印象或者内存印象,即ImageBuffer
- 但是ImageBuffer就是文件运行时真正在内存中状态吗?或者说文件在ImageBuffer中就是表示文件被执行了吗?不!!!!!!
- 在ImageBuffer中的文件数据由于按照一定的规则被"拉伸",只是已经无线接近于可被windows执行的文件格式了!但是此时还不代表文件已经被执行了,因为此时文件也只是处在4GB的虚拟内存中,如果文件被执行操作系统还需要做一些事情,将文件真正的装入内存中,等待CPU的分配执行
- 所以不要理解为ImageBuffer中的状态就是文件正在被执行,后面操作系统还要做很多事情才能让ImageBuffer中的文件真正执行起来的
2.SizeOfRawData一定大于Misc.VirtualSize?
-
SizeOfRawData表示此节在硬盘上经过文件对齐后的大小;Misc.VirtualSize表示此节在内存中没有对齐的大小。那么是不是说SizeOfRawData一定大于等于Misc.VirtualSize呢?不一定!!!!!!!
-
我们写C语言的时候知道如果你定义一个数组已经初始化,比如
int arr[1000] = {0};,此时编译成.exe文件存放在硬盘上时,这1000个int类型的0肯定会存放在某一个节中,并且分配1000个0的空间,这个空间大小是多少,最后重载到ImageBuffer时还是多少,即Misc.VirtualSize不管文件在硬盘上还是内存中的值都是一致的。所以,SizeOfRawData一般都是大于等于Misc.VirtualSize的 -
但是如果我们定义成
int arr[1000];,表示数据还未初始化,并且如果程序中没有使用过或初始化过这块内存空间,那么我们平时看汇编会发现其实编译器还没有做任何事情,这就只是告诉编译器需要预留出1000个int宽度大小的内存空间。所以如果某一个节中存在已经被定义过但还未初始化的数据,那么文件在硬盘上不会显式的留出空间,即SizeOfRawData中不会算上未初始化数据的空间;但是此节的Misc.VirtualSize为加载到内存中时节的未对齐的大小,那么这个值就需要算上给未初始化留出来空间后的整个节的大小,故在内存中的节本身的总大小可能会大于硬盘中的此节文件对齐后的大小。
二、手动模拟FileBuffer到ImageBuffer过程
-
先在硬盘上找一个可执行文件,将文件的数据复制到内存中,即FileBuffer中(前面的练习做过很多次了)
-
根据SizeOfImage的大小,再使用malloc开辟一块ImageBuffer,用0x00初始化ImageBuffer(SizeOfImage即为文件加载到4GB虚拟内存的大小)
-
因为所有头和节表经过文件对齐后的这段数据经过PE loader加载到ImageBuffer是不会变的,所以直接可以将所有头和节表经过文件对齐后的这块数据从FileBuffer中复制到ImageBuffer中
-
接着就是复制所有节的数据:需要使用循环,先复制第一个节的内容。通过第一个节对应节表中的PointerToRawData的值确定第一个节的起始地址;再通过SizeOfRawData的值得到从起始地址开始需要复制多少字节的数据到ImageBuffer中;再接着将这些数据复制到ImageBuffer中的哪个位置呢?就需要通过此节对应的节表中的VirtualAddress决定将数据从ImageBuffer中的哪个地址开始赋值,由于是相对地址,所以还需要知道ImageBase,但是!!我们是用C语言模拟PE的加载过程,此时ImageBuffer的首地址是由malloc申请的,不是真正的ImageBase!(只有当文件真正执行时,操作系统把文件拉伸装入虚拟内存时,才是ImageBase)所以malloc申请的首地址 + VirtualAddress就是最终将第一节数据复制到ImageBuffer中的起始地址。后面的节的数据以此类推从FileBuffer复制到ImageBuffer中
为什么选择SizeOfRawData,不选择Misc.VirtualSize来确定需要复制的节的大小?因为上面说过,Misc.VirtualSize的值由于节中有未初始化的数据且未使用而计算出预留的空间装入内存后的总大小的值可能会很大,如果这个值大到已经包含了后面一个节的数据,那么按照这个值将FileBuffer中的数据复制到ImageBuffer中很可能会把下一个节的数据也复制过去,所以直接用SizeOfRawData就可以了。但是如果节中包含未初始化数据,这样做其实就不太准确了,但是可以大致模拟这个过程即可。(更好一点的做法是比较SizeOfRawData和VirtualSize,选择较小值)
三、内存偏移地址与文件偏移地址换算
-
比如一个文件加载到4GB内存中的某一个数据地址为0x501234,那么怎么算出这个内存地址对应到文件在硬盘上时的地址是多少,即算出文件偏移地址?
- 先算出此内存地址相对于文件在内存中的起始地址的偏移量
- 接着通过这个偏移量循环和每一个节的VirtualAddress做比较,当此偏移量大于某一个节的VirtualAddress并且小于此VirtualAddress + Misc.VirtualSize,就说明这个内存地址就在这个节中
- 再用此偏移量 - 此节的VirtualAddress得到这个内存地址相对于所在节的偏移量
- 接着找内存地址所在节的PointerToRawData,通过PointerToRawData + 内存地址相对于所在节的偏移量来得到此内存地址在硬盘上时相对于文件的偏移量
-
举例:现在我们要找0x501234对应的文件偏移是多少?
- 0x501234 - 0x500000 = 0x1234
- 因为0x1000 < 0x1234 < 0x1000 + Misc.VirtualSize,所以0x501234在可执行文件的第一个节中
- 0x1234 - 0x1000 = 0x234
- 由于第一个节的PointerToRawData为0x400,且假设FileBuffer的起始地址为0(相对的),则0x501234对应的文件偏移地址为0x400 + 0x234 = 0x634
四、作业
1.C语言实现如下功能
-
如图:
-
代码如下:
#include "stdafx.h" #include <stdlib.h> #include <string.h> typedef unsigned short WORD; typedef unsigned int DWORD; typedef unsigned char BYTE; //宏定义MZ标记和PE标记,方便后面判断 #define MZ 0x5A4D #define PE 0x4550 #define IMAGE_SIZEOF_SHORT_NAME 8 //DOS头 struct _IMAGE_DOS_HEADER { WORD e_magic; //MZ标记 WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; //PE文件真正开始的偏移地址 }; //标准PE头 struct _IMAGE_FILE_HEADER { WORD Machine; //文件运行平台 WORD NumberOfSections; //节数量 DWORD TimeDateStamp; //时间戳 DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; //可选PE头大小 WORD Characteristics; //特征值 }; //可选PE头 struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //文件类型 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; //代码节文件对齐后的大小 DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小 DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小 DWORD AddressOfEntryPoint; //程序入口点(偏移量) DWORD BaseOfCode; //代码基址 DWORD BaseOfData; //数据基址 DWORD ImageBase; //内存镜像基址 DWORD SectionAlignment; //内存对齐粒度 DWORD FileAlignment; //文件对齐粒度 WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; //文件装入虚拟内存后大小 DWORD SizeOfHeaders; //DOS、NT头和节表大小 DWORD CheckSum; //校验和 WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; //预留堆栈大

本文详细介绍了PE文件的结构,包括DOS头、PE头、可选头和节表等,并探讨了从FileBuffer到ImageBuffer再到NewBuffer的过程,以及内存偏移地址与文件偏移地址的转换。此外,提供了C语言实现的相关函数,用于模拟PE文件在内存中的转换和计算内存地址对应的文件偏移地址。
最低0.47元/天 解锁文章
947

被折叠的 条评论
为什么被折叠?



