0x00 前言
前面了解了PE文件的输入和输出,今天来看看另一个重要的结构——资源。资源结构是很典型的树形结构,层层查找,最终找到资源位置。
0x01 资源结构介绍
Windows程序的各种界面成为资源,包括加速键,位图,光标,对话框,图标,菜单,串标,工具栏,版本信息等等,在所有的PE文件中资源结构是最为复杂的。下图为资源的树形结构图:
通常来讲,资源的目录为三层结构。最上面的为根目录,它存储了资源的类型同时存储指向下一级的指针,二级目录包含资源id(至于啥事资源ID后面会讲到)和指向第三级的指针,三级目录存储资源代码和指向真正资源的指针。这里最要理解的是这三级目录用的都是同一结构。这个结构包含两个子结构——资源目录结构(IMAGE_RESOURCE_DIRECTORY)和资源目录入口地址结构(IMAGE_RESOURCE_DIRECTOTY_RNTRY)。理解了这两个结构各自的作用就能明白整个资源结构。下面分别从这两个结构讲起。
0x02 资源目录结构(IMAGE_RESOURCE_DIRECTORY)
资源目录结构是由数据目录表的第三个子项(Resource Table)所指向的(想想数据目标表有多重要把,基本上后面所讲都要从它开始)。该结构包含16个字节,共6个字段,下面的该结构的定义:
// 【资源表位于数据目录表的第三项,共动态分配字节, 其中结构体中的成员指出的RVA偏移量都是对于此结构体的地址作为基地址】
typedef struct _IMAGE_RESOURCE_DIRECTORY
{
DWORD Characteristics; //理论上为资源的属性,不过事实上总是0
DWORD TimeDateStamp; //资源的产生时刻
WORD MajorVersion; //理论上为资源的版本,不过事实上总是0
WORD MinorVersion
WORD NumberOfNamedEntries; //以名称(字符串)命名的入口数量
WORD NumberOfIdEntries; //以ID(整型数字)命名的入口数量
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
这六个字段真正有用的是最后两个字段:
WORD NumberOfNamedEntries:标明入口数量,所谓入口数量,资源目录入口地址结构(IMAGE_RESOURCE_DIRECTOTY_RNTRY)结构的数量,这个结构都是紧紧跟着资源目录结构(IMAGE_RESOURCE_DIRECTORY),不过它标记数量的方式是用字符串。
WORD NumberOfIdEntries:这个字段本质上和前一个字段一模一样,不过它是用id来标明数量。注意:最后统计资源入口地址结构数量的时候是这两个的相加的和。
0x03 资源目录入口地址结构(IMAGE_RESOURCE_DIRECTORY_ENTRY)
资源目录入口地址结构虽然很简单,但是却非常重要。它不仅包含了指向下一级目录的地址,好包含了指向真正资源的数据入口地址。
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY
{
DWORD Name; //目录项的名称字符串指针或者ID
DWORD OffsetToData; //资源数据偏移地址或者子目录偏移地址
}_IMAGE_RESOURCE_DIRECTORY_ENTRY, *P_IMAGE_RESOURCE_DIRECTORY_ENTRY;
这两个字段都是极为重要的,下面将详细讲解这两个字段的含义:
Name:大小为双字,该字段定义了目录项的名称或者ID。当用于第一级目录时,它表示的是资源的类型,用于第二级目录时表示资源的名称,用于第三级目录时,用于表示资源的代码页号编号。当最高位是0的时候,表示字段的值作为ID来使用(前面说了该字段可以表示资源的类型,资源的类型就是用ID来区分的,不同资源类型ID不一样)。当最高位是1时,地位字段表示一个指针,这个指针就是指向了资源的名称的UNICODE编码(前面也讲到该字段可以表示目录的名称)。下表统计了系统已经定义的资源类型。表一
类型ID值 |
资源类型 |
类型ID值 |
资源类型 |
01h |
光标(Cursor) |
08h |
字体(Font) |
02h |
位图(Bitmap) |
09h |
加速键(Accelerator) |
03h |
图标(Icon) |
0ah |
未格式资源(Unformatted) |
04h |
菜单(Menu) |
Obh |
消息表(MessageTable) |
05h |
对话框(Dialog) |
0ch |
光标组(Group Cursor) |
06h |
字符串(Stiring) |
0eh |
图标组(Group Icon) |
07h |
字体目录(Font Directory) |
10h |
版本信息(Version Information) |
OffsetToData:双字结构,是一个指针。当最高为为1时,低位指向的是下一级目录的起始地址,当最高为是0时,指向的是真正的数据资源的目录入口结构(IMAGE_RESOURCR_DIRCTORY_DATA)。
重要说明:当字段Name和Offset作为指针时,该指针是从资源区块开始的地方算起的偏移量,不是RVA,即根目录的起始位置偏移量(就是从第一级资源目录结构(IMAGE_RESOURCE_DIRECTORY)的起始地址算起)
0x03 实例讲解资源的结构
工具:hexworkshop,lordPE,目标PE文件:pediy.exe。
下面我将一级级逐级追击资源:
1)根目录
首先找到数据目录表的第三项,它指向了第一级目录即根目录地址。它在PE文件头偏移88h处。地址为:0ch+88h=148h。直接跳转至148h处,如下图:
标明RVA=4000h,这里本来如果要跳转至根目录是要进行地址转化,但是这里是特殊情况,由于这个PE文件磁盘分页和内存分页的值相等,故RVA=Offset,缘由可看下图:
我们现在直接跳转至4000h处,该处即为根目录起始地址,如下图:
现在我们按照顺序以西读出这个第一个结构IMAGE_RESOURCE_DIRECTORY的六个字段的值。记在下表中:表二
Charateristics |
TimeStamp |
MajorVersion |
MinnorVersion |
NumberOfEntris |
NumberOfIdEntries |
|
0000 0000 |
0000 0000 |
0000 |
0000 |
0000 |
0003 |
由最后两个字段可知紧随IMAGE_RESOURCE_DIRECTORY的共有三个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,如下图:
依次将数据读出登记在下表:表三
第一个Dir_entry结构 |
第二个Dir_entry结构 |
第三个Dir_entry结构 |
|
偏移地址 |
4010h |
4018h |
4020h |
字段Name |
0000 0003h |
0000 0004h |
0000 000Eh |
字段OffsetToData |
8000 0028h |
8000 0040h |
8000 0058h |
由于有三个下级目录,我们就以第二个IMAGE_RESOURCE_DIRECTORY_ENTRY结构来分析下一级资源结构。
2)二级目录
根目录的IMAGE_RESOURCE_DIRECTORY_ENTRY结构的字段Name=0000 0004h,最高为0,所以它表示资源类型,低位值为0004h,这个资源的ID号,查询表一可得这是个菜单(Mune资源)。再来看字段OffsetToData=8000 0040h,明显最高为是1,所以这个指针指向的是下一级资源目录。其低31位值是40h,这个就是下级目录距离根目录的偏移地址,地址为:
4000h+40h=4040h。我们跳往地址4040h处,得到下图:
依次读出IMAGE_RESOURCE_DIRECTORY结构的六个字段统计在下表:
Charateristics |
TimeStamp |
MajorVersion |
MinnorVersion |
NumberOfEntris |
NumberOfIdEntries |
0000 0000 |
0000 0000 |
0000 |
0000 |
0000 |
0000 0001 |
根据最后一个字段可知,紧跟这结构IMAGE_RESOURCE_DIRECTORY的结构IMAGE_RESOURCE_DIRECTORY_ENTRY只有一个,如下图:
我们依次读出两个字段的值:Name=8000 00E8h OffsetToData=8000 0088h。由于字段Name的最高位是1,所以低位字段表示指向结构IMAGE_RESOURCE_DATA_STRING_U结构,这个结构存储了资源名的unicod值,我们跳往40E8h处,如下图:
由上图可知资源名是PEDIY。第二个字段OffsetToData的最高位是1,低位表示指向下级目录的地址,我们跳往4088h。
3)第三级目录
跳转至地址4088h,我们来到了第三级目录,如下图:
依次读出六个字段统计在下表:
Charateristics |
TimeStamp |
MajorVersion |
MinnorVersion |
NumberOfEntris |
NumberOfIdEntries |
0000 0000 |
0000 0000 |
0000 0000 |
0000 0000 |
0000 0000 |
0000 0001 |
根据最后一个字段可知紧随结构IMAGE_RESOURCE_DIRECTORY只有一个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,如下图:
字段Name=0000 0409h,字段OffsetToData=0000 00C8h。在第三级目录中Name字段表示代码页编号,这里是409表示英语,OffsetToData最高位是0,低31位作为指针指向资源数据入口结构(IMAGE_RESOURCE_DATA_ENTRY)。该结构的定义如下:
IMAGE_RESOURCE_DATA_ENTRY STRUCT
{
OffsetToData DWORD //资源数据的RVA
Size DWORD//资源数据的长度
CodePage DWORD//代码页,一般为0
Reserved DWORD//保留字段
};IMAGE_RESOURCE_DATA_ENTRY ENDS
我们直接跳转至40c8h处,如下图:
逐个读出各个字段的值统计在下表中:
OffsetToData |
Size |
CodePage |
Reserved |
0000 4400 |
0000 005a |
0000 0000 |
0000 0000 |
至此,我们总算找到了资源的真正地址即在字段OffsetToData=4400h,大小为字段Size=5ah。
如下图就是资源: