说明:本文件中各类文件头格式截图基本都来自看雪的《加密与解密》;本文至关《加密与解密》的阅读笔记。windows
PE文件框架结构,就是exe文件的排版结构。也就是说咱们以十六进制打开一个.exe文件,开头的那些内容就是DOS头内容,下来是PE头内容,依次类推。数组
若是能认识到这样的内含,那么“exe开头的内容是否是就直接是咱们编写的代码”(不是,开头是DOS头内容)以及“咱们编写的代码被编排到了exe文件的哪里”(在.text段,.text具体地址由其相应的IMAGE_SECTION_HRADER指出)此类的问题答案就显而易见了。框架
exe文件从磁盘加载到内存,各部份的前后顺序是保持不变的,但因为磁盘(通常200H)和内存(通常1000H)区块的对齐大小不同,因此同一内容在磁盘和在内存中的地址是不同的。函数
换言之你在磁盘上看到一段内容一内容要到在内存中找到它--假设它是能映射到内容的部份--那么要作相应的地址转换。(好比你在Ultraedit中看到某几个字节而想在OllyDbg中找到这几个字节那么须要进行地址转换)工具
另外要注意,PE文件中存放的地址值都是内存中的地址,这些地址在OllyDbg中不须要转换到其指定的位置就能找到其指向的内容;这要根据这个地址找到内容在Ultraedit的地址,须要将此RVA址转换成文件偏移地址。加密
还要注意DOS头/PE头/块表,映射到内存时属同一区块并且是第一区块,因此此三者上的RVA和文件偏移地址是相等的。操作系统
最后的e_lfanew便是PE文件的RVA地址3d
咱们在前边已经提过,对于DOS头/PE头/区块表三部分RVA和文件偏移地址是相等的,因此上边在十六进制文本编缉器中,直接转向e_lfanew指向的000000B0能够正好找到PE头。指针
2.2DOS stuborm
DOS stub是当操做系统不支持PE文件时执行的部分,通常由编译器本身生成内容是输出“This program cannot be run in MS-DOS mode”等提示。
PE文件头的位置由e_lfanew指出而不是在固定位置,因此DOS stub容许你改为本身想要执行的代码,想写多长写多长;但通常直接不理会。
四个字节,内容“PE\0\0”,对应十六进制“50 45 00 00”
SizeOfOptionalHeader指了OptionalHeader的大小,NumberOfSections指出了文件的区块数;没有指向OptionalHeader和区块表的指针,这暗示区块表紧接在OpthionalHeader后,OpthonalHeader紧接在FileHeader扣,紧接的意思是没有空格的。
其中ImageBase指出程序装载的基地址
其中VirtualAddress指出了区块进入内存后的RVA地址(OD找区块用这个地址)PointerToRawDATA指出区块在磁盘文件中的地址(十六进制编缉器找区块用这个地址)
1. 各区块自身不论多大,其自身差值都不会爱影响
2. 但若是其大小大于200h那么会影响下一区块的差值:好比当.text大小大于200h那么.rdata的文件偏移量将会后移;若是.text大小大于1000h那么.rdata的RVA也会后移
3.也就说表10-7中的差值只是说通常是这样子,但当程序很大时各区块的差值仍是得从新计算;固然不管怎么样老是有:差值=区块RVA-区块文件偏移地址
数据目录表是IMAGE_OPTIONAL_HEADER结构的最后一个成员,类型为IMAGE_DATA_DIRECTORY * 16
数据目录表各成员位置由VirtualAddress指出,而且不是像PE头接在DOS头后不远处同样,通常都在很远的地址;因此通常都是跳过先讲完区块而后再回头讲,也所以初学者可能会感到有些混乱。
数据目录表的第一个成员指向输出表的RVA,指向的地址是IMAGE_EXPORT_DIRECTORY结构。
其中AddressofFunctions指向输出函数的地址数组,AddressOfNames指向输出函数的名称数组,AddressOfNameOrdinale指向输出函数的输出序数数组。
AddressOfNames和AddressOfNameOrdinale的顺序是相同的,也就是说AddressOfNameOrdinale第一个元素的值就是就是AddressOfNames第一个函数的输出序数,依次类推。
使用输出序数作为数组下标到AddressofFunctions指向的地址数组便可找到函数对应的地址。PE重写IAT时使用GetProcAddress经过函数名获取函数地址基本也就是这个流程。
数据目录表的第二个成员指向输入表的RVA;指向的地址是IMAGE_IMPORT_DESCRIPTOR(IID)结构,一个IID对应一个DLL,最后以一个全0的IID表示结束
OriginalFristThunk和FirstThunk都指向IMAGE_THUNK_DATA结构;IMAGE_THUNK_DATA都指向同一个IMAGE_IMPORT_BY_NAME
最重要的仍是要理解为何须要INT和IAT两个东西指向同一个东西;其流程是这样:
1.INT是不可写的,IAT是PE加载器可重写的
2.在编译的时候,编译器不懂IAT要填什么,就随便填成了和INT同样的内容(因此用十六进制编缉器查看时IAT和INT的内容是同样的)
3.PE装载器加载时根据INT找到IMAGE_IMPORT_BY_NAME中的函数名,而后使用GetProcAddress(HMODULE hModule,LPCSTR lpProcName)找到函数对应的地址(hModule是DLL的句柄,lpProcName是IMAGE_IMPORT_BY_NAME中的函数名)
4.PE装载器使用查找到地址重写IAT(因此用OllyDbg查看时IAT和INT的内容是不同的)
5.因此能够直接这样理解:IAT初始是什吐槽内容并没关系、INT就是为了重写IAT而存在的
windows系统中的各类可视元素叫作资源,包括快捷键(Accelerator)、位图(Bitmap)、光标(Cursor)、对话框(Dialog Box)、图标(Icon)、菜单(Menu)、字符串表(String Table)、工具栏(Toolbar)、版本信息(Version Information)等。
数据目录表的第三个成员指向资源结构的RVA,资源结构通常是相同的三层IMAGE_RESOURCE_DIRECTORY+n * IMAGE_RESOURCE_DIRECTORY_ENTRY加上指向最终资源代码的IMAGE_RESOURCE_DATA_ENTRY构成。
其中NumberOfNameEntries的值加上NumberOfIdEntries的值等于紧接在IMAGE_RESOURCE_DIRECTORY后边的IMAGE_RESOURCE_DIRECTORY_ENTRY的个数。
根据IMAGE_RESOURCE_DIRECTORY_ENTRY所在层级的不一样,Name和OffsetToData的含义不相同。
Name:
无论在哪层,当最高位为1时低位值作指针使用;当最高位为0时低位值作编号使用。
更具体的,通常在第一层时高位为0低位用作编号表示资源类型好比是对话框仍是菜单;第二高位为1低位为指向IMAGE_RESOURCE_DIR_STRING_U的指针该结构保存资源的名称;第三层时高位为0低位用作编号表示该资源中的语言好比是英语仍是汉语。
OffsetToData:
无论在哪层,当最高位为1时指向下一层目录块的起始地址;当最高位为0时指向IMAGE_RESOURCE_DATA_ENTRY。
更具体的,通常在第一和第二层时最高位为1,指向下一层目录块的起始地址;在第三层时最高位为0,指向IMAGE_RESOURCE_DATA_ENTRY。