PE文件的块
PE文件的块
PE文件使用的是一个平面的地址空间,所有的代码和数据都被合并在一起,组成一个很大的结构,文件的内容被分为很多的块,块中包含代码和数据,在排列位置上,在PE文件的DOS头部和PE文件头之后就是块表和多个不同的块,(Section,又称为节,区块,区段)
Windows是如何将PE文件映射到内存中的:
在执行一个PE文件时,Windows并不在一开始就将整个文件读入内存,而是采用与内存映射文件类似的机制,也就是说windows装载器在装载的时候仅仅建立好虚拟地址与PE文件的映射关系,只有真正执行到某个内存页指令或者访问页中数据时,这个页面才会被从磁盘提交到物理内存
这种机制使文件装入的速度和文件大小没有关系,但是需要注意的是系统装载可执行文件的方法又不完全等同于内存映射文件,在装载前,有些数据会发生预处理,比如重定位,所以,数据之间的相对位置可能发生轻微变化
windows装载器在装载PE文件时的DOS头部,PE文件头和块表部分不会做任何处理,而在装载块时根据块的属性做不同的处理
块的偏移:块在磁盘文件中是按照可选映像头结构中的FileAlignment字段的值对齐的,而被装载到内存中是按照可选映像头结构中的SectionAlignment字段的值对齐的。所以一个块被装入内存后相对于文件头的偏移和1磁盘文件的偏移是不同的。
重点块♥:
.text:代码块,内容全是指令代码。在编译或者汇编结束产生的一种块
.data:读/写数据块,一般存放全局变量和静态变量,是初始化的数据块
.rdata:只读数据块,用于存放调试目录和说明字符串,是运行期只读的数据
.idata:输入表,包含其他的DLL的函数及数据信息,有的编译器会将这个块合并到.rdata块
.edata:输入表,创建一个输出API或数据的可执行文件时,会用到此块
.rsrc:资源快,包含模块的全部资源,如图标,菜单,位图等
IMAGE_SECTION_HEADER长度为40个字节
PE文件头的最后部分——可选映像头的数据目录表:位于128h到117h之间
可在LoadPE中选择区段来查看区段表
PE文件的输入和输出表
一个windows程序基本上所实现的所有功能几乎都是直接调用系统dll提供的API函数实现的,要使用任何一个dll所提供的函数,我们需要将它导入,也就是用到了输入表,输入表在软件外壳基础中十分重要对于那些提供了被导出函数的dll程序来说,它们必须使用输出表将函数输出之后别的程序才可以使用
无论是自己编写的dll还是系统提供的标准dll,只要想要提供函数给别人使用,就一定要建立输出表,一般的开发环境都能编写具有输出功能的程序,输出表都是由链接器之中建立的,我们只需要指定被输出函数的名称或者序号就可以了,输出表通常出现在dll文件的edata中。
IT:
可执行文件使用来自于其他DLL的代码或数据时,称为输入。当PE文件装载到内存时,windows加载器的工作之一就是定位所有被输入的函数和数据,并且让正在被装入的文件可以使用那些地址。这个过程是通过PE文件的输入表(Import Table ,简称IT,又称为导入表)来完成的,输入表中保存的是函数名和其驻留dll名等动态连接所需要的信息。
IAT:
在PE文件中,有一组数据结构,它们分别对应着每个被输入的dll,每一个这样的结构都给出了被输入的dll名称并指向一组函数指针。这组函数指针被称为输入地址表(Import Address Table,简称IAT)。每一个被引入的API在IAT里都有它自己的保留位置,在那里它将被windows装载器写入输入函数的地址,一旦模块被装入,IAT中包含所要调用输入函数的地址。
IID:
PE文件头的可选映像头中,数据目录表的第二个成员指向输入表,输入表以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始。每个被PE文件隐式地链接进来的DLL都有一个IID。在这个数组中,没有字段指出该数组结构的项数,但是它的最后一个单元是NULL,由此计算出该数组的项数。
例如,某个PE文件从两个DLL文件中引入函数,就存在两个IID结构来描述这些DLL文件,并在两个IID结构的最后一个内容全为0的IID结构作为结束。
每个IID结构的长度是5个双字,即20个字节。
每个IMAGE_IMPORT_DESCRIPOR(简称IID)结构定义如下:(部分)
IMAGE_IMPORT_DESCRIPOR STRUCT{
UNION
Characteristics dd?
OriginalFirstThunk dd? //指向INT(输入名称表),为RVA
Ends
TimeDateStamp dd? //32位
ForwaderChain dd? //转向索引,一般为0
Name dd? //指向DLL名称的RVA值,如“kernel32.dll”
FirstThunk dd? //指向IAT(输入地址表)的RVA值
IMAGE_IMPORT_DESCRIPOR STRUCT ENDS}
OriginalFirstThunk 与FirstThunk非常相似,它们指向两个本质上相同的IMAGE_THUNK_DATA的结构的数组。一般来说,OriginalFirstThunk 指向的 IMAGE_THUNK_DATA的结构数据称为输入名称表,(Import Name Table—-INT),FirstThunk指向的数据结构数组称为输入地址表(Import Address Table–IAT)
IMAGE_THUNK_DATA结构的数组中,每个元素对应于一个从可执行文件输入的函数,数组结构的结束也是通过一个全为0的元素作为标识。
在IMAGE_THUNK_DATA中的ForwarderString指向一个转向字符的RVA值,Function是被输入函数的内存地址,ordinal是被输入函数的API的序数值,AddressOfData指向IMAGE_IMPORT_BY_NAME
ordinal和addressofdata什么时候使用?
当IMAGE_THUNK_DATA类型的数据的最高位为1的时候,代表函数以序号的方式导入,ordinal的低31位就是输入函数在其DLL内的导出序号
当IMAGE_THUNK_DATA类型的数据最高位为0的时候,代表函数以字符串方式导入,这时AddressOfData就是一个指向用来导入函数名称的IMAGE_IMPORT_BY_NAME的数据结构的RVA
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //函数序号,不是必须的
BYTE Name[1];//导入函数名称,为以0结尾的ASCII字串
}
IMAGE_IMPORT_BY_NAME ENDS
这里的NAME大小是一个可变的尺寸域
数据目录表位于128h到1A7h之间,每个成员占8个字节,分别指向相关的结构,
第一个八个字节指向输出表,如果全部为0,这个文件的地址和大小都是0,表示没有输出表
地址130h-137h是数据目录表第二项,指向输入表,输入表的前四个字节是一个相对虚拟地址(注意小端序存放地址,这个地方不是实际地址也不是偏移量),后4个字节是大小
LordPE位置计算器可以用来计算偏移量