如果在内存中建立Module(Module这一术语通常用来表示已装入内存的可执行文件或DLL的代码、数据及资源,除了程序中直接用到的代码和数据外,一个Module也指用于判定代码及数据在内存中位置的支撑数据结构),则这个Module 中的代码、数据、输入表、输出表以及其他有用的数据结构等使用的内存都放在一个连续的内存块中。编程人员只要知道装载程序文件映像到内存后的地址,即可通过映像后面的各种指针找到Module中的所有内容。具体来说,PE格式中的许多项是以RVA(相对虚拟地址)方式指定的,RVA就是某个项相对于文件映像地址的偏移。例如,装载程序将一个PE文件装入到虚拟地址空间,从0x10000开始的内存中,如果PE文件中某个表在映像中的起始地址是0x10800,那么该表的RVA就是0x800。将RVA转化为可用的指针,只要将RVA的值加上Module的基地址即可。基地址是指装入到内存中的EXE或DLL程序的开始地址,它是Windows编程中的一个重要概念。为了方便起见,Win32将Module的基地址作为Module的实例句柄(Instance Handle)。在Win32 中,你可以直接调用GetModuleHandle取得指向DLL的指针,通过该指针访问该DLL Module的内容。
PE文件格式可执行文件共有五部分组成:MS-DOS首部、PE 首部、信息块表、信息块、辅助信息。MS-DOS首部是一个极小的DOS 程序,一般是为了显示像“Thisprogram cannot be run in MS-DOS mode”这类的信息。在Delphi 中,其定义在PImageDosHeader 结构中。该首部还给出了PE首部结构的起始地址,_lfanew字段就是真正PE 首部的相对偏移。PE 首部在Delphi 中定义为PImageNTHeaders结构,该结构是由一个双字的标志项和两个子结构构成:
Signature : DWORD;
FileHeader : TImageFileHeader;
OptionalHeader : TImageOptionalHeader;
标志项是为了说明该可执行文件是“PEOO”、“NE”还是“LE”。TImageFileHeader包含了编译器产生的COFF OBJ信息。TImageOptionalHeader包含有堆栈初始大小、块表大小、数据目录(Data Directory)及其他一些重要信息。你并不需要知道TImageOptionalHeader的所有字段。最重要的两个字段是I m a g e B a s e 和S u b s y s t e m 。其中还有一个重要的字段——IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]。数组一开始的元素内含可执行文件重要部位的RVA及大小。数组的第一个元素代表exported function table(如果有的话)的地址和大小,第二个元素代表imported functiontable的地址和大小,依此类推。在我们的程序里,用它得到RVA:
RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
这样,我们也得到了exported function table 和imported function table。这里有必要