目录预备知识一、相关实验二、章节概述三、Python的struct模块1.struct.pack(fmt,v1,v2,)2. struct.unpack (fmt,string )3.
知识一、相关实验
本实验要求认真学习并完成《IMAGE_DOS_HEADER解析》、《PE头之IMAGE_FILE_HEADER解析》、《PE头之IMAGE_OPTIONAL_HEADER解析》、0103010。
二、部分概述PE文件的DOS标题和PE文件的标题部分之后的数据与部分表是多个不同的部分,部分表包含多个部分标题,每个部分标题对应于PE文件的一个部分
PE文件通常至少包含两个部分。 一个是数据块,另一个是代码块。 每个部分都有一个用于区分部分的名称。 例如,数据块的名称通常为. data,代码块的名称通常为. text。 使用节区名称只是为了方便人们区分不同的节区,实际上节区名称可以自由修改。 因为这对操作系统来说无关紧要。
在PEiD上查看PE文件的部分时,通常看起来如下所示。
三、Python的struct模块使用Python的struct模块将数据序列转换为指定类型的数据,可以指定是大字节序还是小字节序。 struct模块还支持将指定类型的数据转换为数据序列。
结构模块中最常用的两个函数是pack和unpack。
1.struct.pack(fmt,v1,v2,……)将v1、v2等数据按照fmt指定的格式转换为数据序列,函数返回字符串序列。
2.struct.unpack(fmt,string ),以fmt指定的格式分割数据序列string。 是pack函数的逆操作,函数返回一个列表(即使只返回一个数据也返回一个列表)。
3.fmt支持的部分格式如下图所示
fmt的第一个字符用于指定是大字节序还是小字节序。 小字节序使用字符“”,大字节序使用字符“”。
例如,如果需要将x4Dx5A转换为小字节序的WORD类型,即unsigned short数据,请执行struct.unpack(h ),“x4Dx5A”)0)
实验目的1 )学习PE文件的章节标题及章节知识;
2 )掌握RVA和文件偏移地址的转换算法
3 )编程实现RVA和文件偏移地址的转换。
实验环境
服务器: Windows XP,IP地址:随机分配
辅助工具: C32Asm、PEiD、Python
实验步骤节表头以及节区分析。
前面的实验已经介绍了PE文件开头的IMAGE_DOS_HEADER、DosStub以及IMAGE_NT_HEADERS,紧跟在IMAGE_NT_HEADERS之后的是节标题,即IMAGE_NT_HEADERS
每个节都有一个节标题,节标题和节数由image _ nt _ headers.file header.number of sections字段中的值决定。 与节标题相对应的结构为IMAGE_SECTION_HEADER,在WinNT.h头文件中定义如下:
typedef struct _ image _ section _ header { byte name [ image _ sizeof _ short _ name ]; union { DWORD PhysicalAddress; DWORD VirtualSize; (} Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER; 各字段的说明如下。
1.Name,相应部分的名称。 其中IMAGE_SIZEOF_SHORT_NAME的值固定为8。 如果名称长度小于8,则以空字符结束。 如果名称长度为8,则数组长度为8,因此没有空字符。 节的名称通常以. text、 data等点开头。 通常,可以自由修改此名称。
2.VirtualSize,如果未对齐,则块中所有数据的大小。
3.VirtualAddress,将块加载到内存中时的RVA,该值始终是SectionAlignment的整数倍。
4.SizeOfRawData,块数据在磁盘文件中通过文件对齐的大小。
5.PointerToRaw
Data,区块数据在磁盘文件中的偏移地址。6.PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers:不是很重要的字段,这里不作研究。
7.Characteristics,区块的属性值,表明区块的可读、可写、可执行等相关属性。
现在我们使用C32Asm来分析节表头中的一个元素,操作过程如下:
1.使用C32Asm打开C:PEnotepad.exe文件;
2.根据IMAGE_DOS_HEADER的e_lfanew字段,可以知道PE头的文件偏移地址为0xE0;
3.根据IMAGE_FILE_HEADER的SizeOfOptionalHeader字段,可以知道可选头的大小为0xE0,因为Signature占用4字节,FileHeader占用20字节,所以IMAGE_NT_HEADERS的大小为:0xE0+4+20=248字节;
4.PE头的偏移为0xE0,大小为248字节,因此可以计算出节表头所在的文件偏移值为0xE0+248=472,即十六进制的0x1D8;
5.IMAGE_SECTION_HEADER结构体占用40字节,因此第一个节表头的数据范围是从0x1D8开始的40字节数据,如下图所示:
将这些数据内容对应到IMAGE_SECTION_HEADER各个字段的值,如下图所示(删除了其中不重要的字段PointerToRelocations等):
这与在PEiD中看到的解析结果是一致的:使用PEiD打开C:PEnotepad.exe,然后查看节区数据,看到的第一个区块.text与我们分析的结果是一致的,如下图所示:
实验步骤二
RVA与文件偏移地址的转换。
节区的大小是需要进行对齐处理的,而且在文件中的对齐值与在内存中的对齐值是不一样的,在IMAGE_NT_HEADERS的OptionalHeader中指明了这两个值(FileAlignment和SectionAlignment)。节区的起始地址总是进行对齐后的值,下面以文件偏移值为例进行讲解。
首先使用PEiD打开C:PEnotepad.exe文件,点击主界面中“子系统”右侧的“>”按钮来查看更详细的信息,从中可以看到PE文件节区的数目为3,文件对齐值为0x200,节区(内存)对齐值为0x1000,如下图所示:
在实验步骤一中,我们已经计算出了节表头的文件偏移值为0x1D8,而且知道每个节表头的大小为40字节,那么3个节表头的大小就是120字节,因此可以计算出节表头结束的偏移值为:0x1D8+40*3=0x250。
在PEiD中可以看到区块的文件对齐值为0x200,从文件偏移的0x250开始第一个对齐的文件偏移地址是0x400(如果n%m等于0,则认为n是关于m对齐的)。在实验步骤一中,我们已经计算出了第一个区块.text的PointerToRawData值为0x400,与我们这里得到的对齐值是一样的。
那么,在文件数据偏移0x250至0x400之间就存在一个间隙,这个间隙通常使用0x00进行填充。同样的,节区与节区之间也会存在因为对齐而导致的间隙。
可以想象,当PE文件被映射到内存时,也会存在同样的间隙,只不过间隙的大小是不一样的,因为FileAlignment和SectionAlignment的大小不一样,如下图所示:
正是因为FileAlignment和SectionAlignment的不一致,造成了PE文件数据在磁盘上的分布与在内存中的分布存在差异,最终导致了文件偏移值和相对虚拟地址(RVA)之间的差异。从图中可以看到PE文件在磁盘文件中和在内存映像中的前面一部分是一样的,即对于在DOS头、PE文件头、节表头中的数据而言,RVA与文件偏移值是一样的,在区块中的数据由于FileAlignment和SectionAlignment不一样,其RVA与文件偏移地址是不一样的,但是两者之间可以进行相互转换。
节区中RVA值转换为Offset(文件偏移)的步骤如下:
1.通过节表头数组可以遍历所有节区的信息,包括节区的起始RVA地址、起始的文件偏移地址、以及节区的大小;
2.判断给定的RVA值落在哪个节区上,对应的,可以计算出这个RVA与节区起始RVA的差值diff;
3.查看包含待转换的RVA的节区的文件偏移起始地址,加上差值diff便是转换得到的文件偏移地址。
对于C:PEnotepad.exe而言,RVA值0x9100转换为文件偏移值是多少呢?下面通过实验来讲解RVA转换为文件偏移地址的过程:
1.使用PEiD打开C:PEnotepad.exe,查看各个节区的相关参数,如下图所示:
2.从图中可以看出,0x9100这个RVA位于.data节区,且该节区的起始RVA为0x9000,因此计算出差值为0x9100-0x9000=0x100;
3…data节区的文件偏移地址为0x7C00,加上上面计算出来的差值,结果为0x7C00+0x100=0x7D00,这个就是转换得到的文件偏移地址。
编程实现RVA到文件偏移值的转换。
要实现RVA到文件偏移值的转换,最主要的工作就是找到节表头的位置并对其进行遍历,之后进行一些转换工作即可,这里我们将使用Python实现这个转换工作。
几个关键步骤的描述:
1.如何找到IMAGE_NT_HEADERS?因为IMAGE_DOS_HEADER的大小固定为0x40,而e_lfanew为DosHeader的最后一个DWORD类型的数据,所以e_lfanew的偏移值为0x3C,根据e_lfanew的值可以找到NtHeaders的位置;
2.如何找到区块的数量?NumberOfSections位于FileHeader中偏移0x02的位置,而FileHeader位于NtHeaders偏移0x04的位置,所以NumberOfSections相对于NtHeaders偏移6;
3.如何找到节表头的位置?根据FileHeader的SizeOfOptionalHeader字段可以确定OptionalHeader的大小,进而确定整个NtHeaders的大小,FileHeader在NtHeaders中的偏移值为20;
4.通常而言,第一个区块的起始文件偏移地址为0x400,所以只需要读取头部0x400大小的数据即可;
5.通过DosHeader的e_magic以及NtHeaders的Signature字段,可以判断PE文件的合法性。
进行RVA到文件偏移地址转换的Python脚本的代码如下(实验机器上的C:PERvaToOffset.py):
使用RvaToOffset.py时需要通过命令行参数传入PE文件的路径以及16进制格式的RVA值(0xXX形式),如果RVA转换失败,将返回-1。下面我们使用C:PEnotepad.exe进行测试:
1.通过“开始菜单”、“运行”来打开cmd,使用cd C:PE进行目录切换;
2.选取notepad.exe的RVA值0x0000B000,这个RVA是.rsrc区块的起始RVA地址;
3.在命令行下输入“RvaToOffset.py notepad.exe 0xb000”来进行转换。
转换得到的结果为0x00008400,如下图所示:
这与在PEiD中看到的notepad.exe的.rsrc节区的起始文件偏移地址是一致的,如下图所示: