2.5 PE文件中的资源
![]()
一些PE格式(Portable
Executable)的EXE文件常常存在很多资源,如图标、位图、对话框、声音等。若要把这些资源取出为自己所用,或修改这些文件中的资源,则需要对PE文件中资源数据结构有所了解。
2.5.1 查找资源在文件中的起始位置
要找出一个PE文件中的某种资源,首先需要确定资源节在PE文件中的起始位置。有两种方法来确定资源在文件中的起始位置。
第一种方法,首先根据FileHeader中的成员NumberOfSections的值,确定文件中节的数目,再根据节的数目,遍历节表数组。也就是从0到(节表数–1)的每一个节表项。比较每一个节表项的Name字段,看看是否等于“.rsrc”,如果是,就找到了资源节的节表项。这个节表项的PointerToRawData 中的值,就是资源节在文件中的位置。
第二种方法,取得PE Header中的IMAGE_OPTIONAL_HEADER中的DataDirectory数组中的第三项,也就是资源项。DataDirectory[]数组的每项都是IMAGE_DATA_ DIRECTORY结构,该结构定义如下:
l
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
l
从以上结构对象取得DataDirectory数组中的第三项中的成员VirtualAddress的值。这个值就是在内存中资源节的RVA。然后根据节的数目,遍历节表数组,也就是从0~(节表数–1)的每一个节表项。每个节在内存中的RVA的范围是从该节表项的成员VirtualAddress字段的值开始(包括这个值),到VirtualAddress+Misc.VirtualSize的值结束(不包括这个值)。遍历整个节表,看看所取得的资源节的RVA是否在那个节表项的RVA范围之内。如果在范围之内,就找到了资源节的节表项。这个节表项中的PointerToRawData 中的值,就是资源节在文件中的位置。如果这个PE文件没有资源 的话,DataDirectory数组中的第三项内容为0。这样也可以得到了资源在文件中开始的位置。
2.5.2 确定PE文件中的资源
得到了资源节在文件中的位置后,就可以确定某个资源类型及其二进制数据在PE文件中的位置和数据块的大小。
资源节最开始是一个IMAGE_RESOURCE_DIRECTORY结构,在winnt.h文件中有这个结构的定义。这个结构长度为16字节,共有6个参数,其结构的原型如下:
l
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries;
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
l
其中各个参数的含义如下所述
Characteristics: 标识此资源的类型。
TimeDateStamp:资源编译器产生资源的时间。
MajorVersion:资源主版本号。
MinorVersion:资源次版本号。
NumberOfNamedEntries和NumberofIDEntries:分别为用字符串和整形数字来进行标识的IMAGE_RESOURCE_DIRECTORY_ENTRY项数组的成员个数。
紧跟着IMAGE_RESOURCE_DIRECTORY后面的是一个IMAGE_RESOURCE_ DIRECTORY_ENTRY数组。这个结构长度为8个字节,共有两个字段,每个字段4个字节。其结构原型如下:
l
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31;
DWORD NameIsString:1;
};
DWORD Name;
WORD Id;
};
union {
DWORD OffsetToData;
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1;
};
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
l
其中,对于第一个字段,当其最高位为1(0x80000000)时,这个DWORD剩下的31位表明相对于资源开始位置的偏移,偏移的内容是一个IMAGE_RESOURCE_DIR_ STRING_U,用其中的字符串来标明这个资源类型;当第一个字段的最高位为0时,表示这个DWORD的低WORD中的值作为Id标明这个资源类型。
对于第二个字段,当第二个字段的最高位为1时,表示还有下一层的结构。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容将是一个下一层的IMAGE_RESOURCE_DIRECTORY结构;当第二个字段的最高位为0时,表示已经没有下一层的结构了。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容会是一个IMAGE_RESOURCE_DATA _ENTRY结构,此结构会说明资源的位置。对于资源标示号Id,当Id等于1时,表示资源为光标,等于2时表示资源为位图等,等于3时表示资源为图标等。在winuser.h文件中有定义。
标识一个IMAGE_RESOURCE_DIRECTORY_ENTRY一般都是使用Id,就是一个整数。但是也有少数使用IMAGE_RESOURCE_DIR_STRING_U来标识一个资源类型。这个结构定义如下:
l
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length;
WCHAR NameString[1];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
l
这个结构中将有一个Unicode的字符串,是字对齐的。这个结构的长度可变,由第一个字段Length指明后面的Unicode字符串的长度。
经过3层IMAGE_RESOURCE_DIRECTORY_ENTRY(一般是3层,也有可能更少些)最终可以找到一个IMAGE_RESOURCE_DATA_ENTRY结构,这个结构中存有相应资源的位置和大小。这个结构长16个字节,有4个参数,其原型如下:
l
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData;
DWORD Size;
DWORD CodePage;
DWORD Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
l
其中各个参数的含义如下所述。
OffsetToData:这是一个内存中的RVA,可以用来转化成文件中的位置。用这个值减去资源节的开始RVA,就可以得到相对于资源节开始的偏移。再加上资源节在文件中的开始位置,即节表中资源节中PointerToRawData的值,就是资源在文件中的位置。注意,资源节的开始RVA可以由Optional Header中的DataDirectory数组中的第三项中的VirtualAddress的值得到,或者节表中资源节那项中的VirtualAddress的值得到。
Size:资源的大小,以字节为单位。
CodePage:代码页。
Reserved:保留项。
总之,资源一般使用树来保存,通常包含3层,最高层是类型,其次是名字,最后是语言。在资源节开始的位置,首先是一个IMAGE_RESOURCE_DIRECTORY结构,后面紧跟着IMAGE_RESOURCE_DIRECTORY_ENTRY数组,这个数组的每个元素代表的资源类型不同;通过每个元素,可以找到第二层另一个IMAGE_RESOURCE_ DIRECTORY,后面紧跟着IMAGE_RESOURCE_DIRECTORY_ENTRY数组。这一层的数组的每个元素代表的资源名字不同;然后可以找到第三层的每个IMAGE_ RESOURCE_DIRECTORY,后面紧跟着IMAGE_RESOURCE_DIRECTORY_ENTRY数组。这一层的数组的每个元素代表的资源语言不同;最后通过每个IMAGE_RESOURCE_ DIRECTORY_ENTRY可以找到每个IMAGE_RESOURCE_DATA_ENTRY。通过每个IMAGE_RESOURCE_DATA_ENTRY,就可以找到每个真正的资源。






