PE加载过程 FileBuffer-ImageBuffer

PE加载过程 FileBuffer-ImageBuffer

一、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为加载到内存中时节的未对齐的大小,那么这个值就需要算上给未初始化留出来空间后的整个节的大小,故在内存中的节本身的总大小可能会大于硬盘中的此节文件对齐后的大小。

二、模拟PE加载过程

img

  1. 我们先在硬盘上找一个可执行文件,将文件的数据复制到内存中,即FileBuffer中(前面的练习做过很多次了)
  2. 根据SizeOfImage的大小,再使用malloc开辟一块ImageBuffer(SizeOfImage即为文件加载到4GB内存的大小)
  3. 因为NT头和节表文件对齐后的这段数据经过PE loader加载到ImageBuffer是不会变的,所以直接可以将NT头和节表文件对齐后的这块数据从FileBuffer中复制到ImageBuffer中
  4. 接着就是复制所有节的数据:需要使用循环,先复制第一个节的内容。通过第一个节对应节表中的PointerToRawData的值确定第一个节的起始地址;再通过SizeOfRawData的值得到从起始地址开始需要复制多少字节的数据到ImageBuffer中;再接着将这些数据复制到ImageBuffer中的哪个位置呢? 就需要再通过此节对应的节表中的VirtualAddress决定将数据从ImageBuffer中的哪个地址开始赋值,由于是相对地址,所以还需要知道ImageBase,但是!!我们是模拟PE的加载过程,此时ImageBuffer的首地址是由malloc申请的,不是ImageBase!所以我们需要使用malloc申请的首地址 + VirtualAddress就是最终将第一节数据复制到ImageBuffer中的起始地址。后面的节的数据以此类推从FileBuffer复制到ImageBuffer中

为什么选择SizeOfRawData,不选择Misc.VirtualSize来确定需要复制的节的大小?因为上面说过,Misc.VirtualSize的值由于节中有未初始化的数据且未使用而计算出预留的空间装入内存后的总大小的值可能会很大,如果这个值大到已经包含了后面一个节的数据,那么按照这个值将FileBuffer中的数据复制到ImageBuffer中很可能会把下一个节的数据也复制过去,所以直接用SizeOfRawData就可以了。但是如果节中包含未初始化数据,这样做起始就不太准确了,但是可以大致模拟这个过程即可

img

三、内存偏移到文件偏移计算

比如一个文件加载到4GB内存中的某一个数据地址为0x501234,那么怎么算出这个内存地址对应到文件在硬盘上时的地址是多少,即算出相对于文件的偏移地址?

  1. 先算出此内存地址相对于文件在内存中的起始地址的偏移量
  2. 接着通过这个偏移量循环和每一个节的VirtualAddress做比较,当此偏移量大于某一个节的VirtualAddress并且小于此VirtualAddress + Misc.VirtualSize,就说明这个内存地址就在这个节中
  3. 再用此偏移量 - (此节的VirtualAddress + 文件在内存中的起始地址)得到这个内存地址相对于所在节的偏移量
  4. 接着找内存地址所在节的PointerToRawData,通过PointerToRawData + 聂村地址相对于所在节的偏移量来得到此内存地址在硬盘上时相对于文件的偏移量
  • 举例:现在我们要找0x501234对应的文件偏移是多少?
  • 0x501234 - 0x500000 = 0x1234
  • 因为0x1000 < 0x1234 < 0x1000 + Misc.VirtualSize,所以0x501234在可执行文件的第一个节中
  • 0x501234 - (0x50000 + 0x1000) = 0x234
  • 由于第一个节的PointerToRawData为0x400,文件在硬盘上的起始地址为0(相对的),所以0x400 + 0x234 = 0x634

img