原文地址:http://hi.baidu.com/liguocan/blog/item/773786fd2627241308244d57.html
在原文中,实际上也是不全的,这篇文章我从其他地方剪切了点过来,补到尾上,没想到粘贴不下了,所以,只好放弃了,实际上我觉得这篇文章图4-1的图画得很形象,顺着这个图片去看就很容易理解了,,,呵呵,,感谢JIURL ,。。
程序所用到的各种资源,比如 bmp,cursor,menu,对话框等都存在PE文件中。
我们将详细介绍关于资源的各种结构,通过一个例子来说明资源及其相关结构是怎么放在PE文件中的。以及如何在遍历PE文件中的所有资源。我们只最终找到这些资源在文件中的位置和长度。而不具体分析某种资源的格式,比如有个BMP的资源,我们不分析BMP格式。
一 找到资源在文件中位置。
资源都放在PE文件的某个节中,该节的节表项中的PointerToRawData,就是资源节在文件中的位置。
1.1 得到PE Header在文件中的位置。
通过DOS Header结构的成员e_lfanew,可以确定PE Header的在文件中的位置。
1.2 得到文件中节的数目。
确定PE Header的在文件中的位置之后,就可以确定PE Header中的成员FileHeader和成员OptionalHeader在文件中的位置。根据 FileHeader 中的 成员NumberOfSections 的值,就可以确定文件中节的数目,也就是节表数组中元素的个数。
1.3 得到节表在文件中的位置。
PE Header在文件中的位置加上PE Header结构的大小就可以得到节表在文件中的开始位置。PE Header结构的大小可以由Signature的大小加上FileHeader的大小再加上FileHeader中的SizeOfOptionalHeade来确定。其实到目前为止SizeOfOptionalHeade也就是结构Optional Header的大小也是固定的,所以整个PE Header结构的大小也是固定。不过为了安全起见,还是用Signature的大小加上FileHeader的大小再加上FileHeader中的SizeOfOptionalHeade来确定比较保险。 网管u家u.bitscn@com
1.4 得到资源节在文件中的位置。
第1.2步中我们确定了文件中节的数目,第1.3步中我们确定了节表在文件中的位置。
现在有两种方法来确定资源在文件中的位置。
第一种方法,根据节的数目,遍历节表数组。也就是从0到(节表数-1)的每一个节表项。
比较每一个节表项的Name字段,看是否等于".rsrc"。如果等于。就找到了资源节的节表项。
这个节表项中的 PointerToRawData 中的值,就是资源节在文件中的位置。
第二种方法,取得PE Header中的Optional Header中的DataDirectory数组中的第三项,
也就是资源项。DataDirectory[]数组的每项都是IMAGE_DATA_DIRECTORY结构,该结构定义如下。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
取得DataDirectory数组中的第三项中的成员VirtualAddress的值。这个值就是在内存中资源节的RVA。
然后根据节的数目,遍历节表数组。也就是从0到(节表数-1)的每一个节表项。
每个节在内存中的RVA的范围是从该节表项的成员VirtualAddress字段的值开始(包括这个值),
网管bitscn_com
到VirtualAddress+Misc.VirtualSize的值结束(不包括这个值)。
我们遍历整个节表,看我们取得的资源节的RVA,在哪个节表项的RVA范围之内。
如果在范围之内,就找到了资源节的节表项。
这个节表项中的 PointerToRawData 中的值,就是资源节在文件中的位置。
如果这个PE文件没有资源的话,DataDirectory数组中的第三项内容为0。
这样我们就得到了资源在文件中开始的位置。
二 PE文件中的资源。
我们已经得到了资源节在文件中的位置。
资源节最开始是一个IMAGE_RESOURCE_DIRECTORY结构。
在WINNT.H中定义如下。
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;
这个结构长度为16字节,共有6个字段。
各字段含义如下:
Characteristics: Resource flags,保留用于以后使用,目前都为0。
网管网www_bitscn_com
TimeDateStamp:资源编译器产生资源的时间。
MajorVersion:
MinorVersion:
NumberOfNamedEntries:用字符串来标示IMAGE_RESOURCE_DIRECTORY_ENTRY项的,紧跟着本结构的IMAGE_RESOURCE_DIRECTORY_ENTRY数组的成员个数。
Number of ID Entries:用整形数字来表示IMAGE_RESOURCE_DIRECTORY_ENTRY项的,紧跟着本结构的IMAGE_RESOURCE_DIRECTORY_ENTRY数组的成员个数。
IMAGE_RESOURCE_DIRECTORY后面一定会紧跟着一个IMAGE_RESOURCE_DIRECTORY_ENTRY数组。
IMAGE_RESOURCE_DIRECTORY_ENTRY结构定义如下。
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;
这个结构长度为8个字节。共有两个字段,每个字段4个字节。
根据不同情况,这两个字段的含义不一样。这个结构的定义如果看不懂的话,后面的例子一看就会明白了。
网管联盟bitsCN@com
第一个字段,当第一个字段的最高位是1的时候,表示,这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容是一个IMAGE_RESOURCE_DIR_STRING,用里面的字符串来标明这个IMAGE_RESOURCE_DIRECTORY_ENTRY。当第一个字段的最高位是0的时候,表示,这个DWORD的低WORD中的值作为id标明这个IMAGE_RESOURCE_DIRECTORY_ENTRY。
第二个字段,当第二个字段的最高位是1的时候,表示,还有下一层的结构。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容会是一个下一层的IMAGE_RESOURCE_DIRECTORY结构,这个请看后面的例子中的说明。
当第二个字段的最高位是0的时候,表示,已经没有下一层的结构了。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容会是一个IMAGE_RESOURCE_DATA_ENTRY结构,IMAGE_RESOURCE_DATA_ENTRY结构会说明资源的位置。
标示一个IMAGE_RESOURCE_DIRECTORY_ENTRY一般都是使用id,就是一个整数。
但是也有少数的使用IMAGE_RESOURCE_DIR_STRING来标示一个IMAGE_RESOURCE_DIRECTORY_ENTRY。
IMAGE_RESOURCE_DIRECTORY_ENTRY结构定义如下。 网管网www_bitscn_com
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length;
WCHAR NameString[ 1 ];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
这个结构中将有一个Unicode的字符串,是字对齐的。所有这些用来标识的IMAGE_RESOURCE_DIR_STRING都放在一起,这个结构的长度是可变的,由第一个字段Length指明后面的Unicode字符串的长度。
经过3层IMAGE_RESOURCE_DIRECTORY_ENTRY(一般是3层,也有可能更少些。第一层资源类型bmp,menu等等,第二层资源名,第三层是资源的Language。)最终会找到一个IMAGE_RESOURCE_DATA_ENTRY结构,这个结构中存有相应(某资源类型,某资源名,某资源Language)资源的位置和大小,就真正找到资源了。IMAGE_RESOURCE_DATA_ENTRY定义如下。
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData;
DWORD Size;
DWORD CodePage;
DWORD Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
这个结构长16个字节,有4个字段。
OffsetToData:这是一个内存中的RVA,要转化成文件中的位置,需要用这个值减去资源节的开始RVA,
网管联盟bitsCN_com
资源节的开始RVA可以由Optional Header中的DataDirectory数组中的第三项中的VirtualAddress的值得到。
或者节表中,资源节那项中的VirtualAddress的值得到。相减之后,就可以得到相对于资源节开始的偏移。
再加上资源节在文件中的开始位置,节表中资源节那项中的PointerToRawData的值,就是资源在文件中的位置。
Size:资源的大小,以字节为单位。
CodePage:一般来说是Unicode code page。
Reserved:保留,值为0。
上面是资源各种结构的说明,知道这些结构还远远不够,下面我们通过一个例子来看如何通过这些结构找到资源。
我们的例子是Win2k中的可执行文件telnet.exe。为了防止大家版本不同,本文附带了这个PE文件。
PE文件的资源的各种结构放在一个树型结构中,这个结构一般有3层,如图4.1,就是telnet.exe中的情况。
图4.1
图中长的长方形表示一个IMAGE_RESOURCE_DIRECTORY结构,长16个字节,简称directory。
网管bitscn_com
图中短的长方形表示一个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,长8个字节,简称directory_entry。
图中圆圈表示一个IMAGE_RESOURCE_DATA_ENTRY结构,长16个字节,简称data_entry。
为了以后的叙述方便还给树的每一个节点起了名字,第一层的叫11,第二层的叫21,22,23,24,第三层的叫31,32,33,34,35,36,37,38,39,310,311,312。
在资源节开始处,是一个directory结构,这个结构中指明了紧跟在它后面的一个directory_entry结构数组中的元素的个数。这个directory结构之后,紧跟着的就是那个directory_entry结构数组。他们一起组成了11。就如图4.1中所示。其他的每个节点,21,22..31,32..312,都是这样,每个directory结构后面紧跟directory_entry结构数组。11中的directory_entry结构数组中的每一个元素,都存有到下一层某个节点的偏移。也就是通过directory_entry结构数组的每个元素可以找到21,22,23,24。其他的节点中情况也是一样。图中看不到的一点是,所有的节点之间都是紧紧的挨在一起存放的,11之后紧跟着的是21,21之后紧跟着的是22,22之后紧跟着的是23。依此类推。directory_entry结构数组中的每一个元素除了有到下一层某节点的偏移,(是下一层的节点,还是已经到了最终的data_entry,后面详细叙述)还有一个Name或者Id字段(是Name还是Id后面详细叙述),根据不同的层,代表的含义也不一样。第一层的每个directory_entry的这个值,代表类型。比如11的第一个directory_entry的Id值为3,3代表icon,从这个directory_entry往下的都是都是图标了(关于不同类型值的定义,后面详细叙述)。第二层每个directory_entry的这个值代表Name,第三层代表Language。11,21,31的左边那个data_entry,的三个值分别为3,1,409(都是16进制),就是说是一个图标类型,Name为1h,Language为409h的资源。
下面我们来通过telnet.exe中资源节的具体内容来看,用开始讲到的寻找资源节在文件中位置的方法,我们找到了资源节在文件中的位置为00013600h。
我们为了看起来清楚,每一行是一个结构,并且每个结构的不同成员用/分开,例如,
一个directory结构00000000/00000000/0400/0000/0000/0400
可以看到结构成员,Characteristics为0,TimeDateStamp为0,MajorVersion为4,(如果你不明白为什么是0004而不是0400的话,请看《JIURLPE格式学习总结(一)》中关于big-endian和little-endian的介绍),MinorVersion为0,NumberOfNamedEntries为0,NumberOfIdEntries为4。
一个directory_entry结构03000000/30000080
可以看到结构成员,第一个字段的第一个字节00h的二进制为00000000,最高位为0,所以低两个字节中的值为Id,Id为3。第二个字段的第一个字节80h(如果你不明白为什么第一个字节是80h而不是30h的话,请看《JIURLPE格式学习总结(一)》中关于big-endian和little-endian的介绍)的二进制为10000000,最高位为1所以说明还有下一层,还没有到叶子,所以第二字段代表到下一层某个节点的偏移OffsetToData值为30。
一个data_entry结构E0230300/30010000/E4040000/00000000
可以看到结构成员,OffsetToData为323E0h(这是一个内存中的RVA,要转化成文件中的位置,需要用这个值减去资源节的开始RVA,资源节的开始RVA可以由OptionalHeader中的DataDirectory数组中的第三项中的VirtualAddress的值得到。或者节表中,资源节那项中的VirtualAddress的值得到。相减之后,就可以得到相对于资源节开始的偏移。再加上资源节在文件中的开始位置,节表中资源节那项中的PointerToRawData的值,就是资源在文件中的位置。),Size为130h,CodePage为4E4h,Reserved为0。
下面就是telnet.exe中的内容,可以用16进制编辑器打开附带的telnet.exe对照着看。
00013600h:00000000/00000000/0400/0000/0000/0400
(directory结构,16字节长。图4.1中11中的directory。0个NamedEntries,4个IdEntries。)
00013610h:03000000/30000080
(directory_entry结构,8字节长。图4.1中11中的directory_entry数组第一个元素。第一个字段高位为0,说明第一个字段表示id,由于是第一层,所以类型id为3。第二个字段高位为1,说明还有下一层,第二字段中的低31位为到图4.1中21的偏移,30 00013600h=00013630h。)
00013618h:06000000/50000080
00013620h:0E000000/A0000080
00013628h:10000000/B8000080
00013630h:00000000/00000000/0400/0000/0000/0200(directory21)
00013640h:01000000/D0000080(d0 00013600h=000136d0h。)
00013648h:02000000/F0000080
00013650h:00000000/00000000/0400/0000/0000/0800 (directory22)
00013660h:08000000/10010080
00013668h:09000000/30010080
00013670h:0C000000/50010080
00013678h:0D000000/70010080
00013680h:10000000/90010080
00013688h:11000000/B0010080
00013690h:12000000/D0010080
00013698h:39000000/F0010080
000136a0h:00000000/00000000/0400/0000/0100/0000
(directory结构,16字节长。图4.1中23。1个NamedEntries,0个IdEntries。)
000136b0h:D0030080/10020080
(directory结构中已经表明这是一个NamedEntries,第一个字段中的高位为1,说明第一个字段中的值为一个指向IMAGE_RESOURCE_DIR_STRING结构的偏移,3D0 00013600h=000139D0h。)
000136b8h:00000000/00000000/0400/0000/0000/0100 (directory24)
000136c8h:01000000/30020080
000136d0h:00000000/00000000/0400/0000/0000/0200 (directory31)
000136e0h:09040000/50020000
(directory_entry结构,8字节长。第一个字段高位为0,说明第一个字段表示id,由于是第三层,所以Languageid为409h。第二个字段高位为0,说明已经是叶子了,第二字段中的低31位为到一个data_entry结构的偏移,250 00013600h=00013850h。)
000136e8h:04080000/60020000
000136f0h:00000000/00000000/0400/0000/0000/0200 (directory32)
00013700h:09040000/70020000
00013708h:04080000/80020000
00013710h:00000000/00000000/0400/0000/0000/0200(directory33)
00013720h:09040000/90020000
00013728h:04080000/A0020000
00013730h:00000000/00000000/0400/0000/0000/0200(directory34)
......................省了。。。没用,呵呵
本文详细解析了PE文件中资源的各种结构,并通过一个具体的例子展示了如何找到资源在文件中的位置。通过理解资源目录、目录条目和数据条目的结构,读者能够掌握在PE文件中查找特定资源的方法。

-- PE文件中的资源5&spm=1001.2101.3001.5002&articleId=6915094&d=1&t=3&u=88cf6d9254824a728062f7b0e5ce74dd)
2258

被折叠的 条评论
为什么被折叠?



