📄 jiurl pe 格式学习总结(四)-- pe文件中的资源.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0062)http://jiurl.cosoft.org.cn/jiurl/document/jiurlpe/jiurlpe4.htm -->
<HTML><HEAD><TITLE>JIURL's PE 4</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type>
<META content=黑客,安全,网络,编程,黑客软件,安全软件,hacker,UNIX,Linux,FreeBSD name=keywords>
<META content=黑客,安全,网络,编程,黑客软件,安全软件,hacker,UNIX,Linux,FreeBSD name=description>
<STYLE type=text/css>.title {
FONT-FAMILY: "黑体", Arial, sans-serif; FONT-SIZE: 21px; FONT-WEIGHT: bold; LINE-HEIGHT: 48px; TEXT-DECORATION: none
}
.author {
FONT-FAMILY: "宋体"; FONT-SIZE: 12px; LINE-HEIGHT: 16px
}
.content {
FONT-SIZE: 14px; LINE-HEIGHT: 20px
}
</STYLE>
<META content="MSHTML 5.00.2614.3500" name=GENERATOR></HEAD>
<BODY bgColor=#f7f7f7 topMargin=5>
<DIV align=center>
<CENTER>
<TABLE border=0 cellPadding=0 cellSpacing=0 height=29 width="96%">
<TBODY>
<TR>
<TD class=title height=41 width="100%">
<P align=center><FONT face=宋体>JIURL PE 格式学习总结(四)--
PE文件中的资源</FONT></P></TD></TR></CENTER>
<TR>
<TD class=author height=9 width="100%">
<P align=center>作者: <A href="mailto:jiurl@mail.china.com">JIURL</A>
</P></TD></TR>
<TR>
<TD class=author height=6 width="100%">
<P
align=center>
主页: <A href="http://jiurl.yeah.net/">http://jiurl.yeah.net/</A> </P></TD></TR>
<TR>
<TD class=author height=2 width="100%">
<P align=center> 日期: 2003-4-24
</P></TD></TR></TBODY></TABLE></DIV>
<DIV align=center>
<CENTER>
<TABLE border=0 cellPadding=0 cellSpacing=0 height=1 width="96%">
<TBODY>
<TR>
<TD height=1 width="100%">
<HR color=#396da5 SIZE=3>
</TD></TR></TBODY></TABLE></CENTER></DIV>
<DIV align=center>
<TABLE border=0 cellPadding=0 cellSpacing=0 class=content height=3194
width="96%">
<TBODY>
<TR>
<TD height=7500 vAlign=top width="131%"> 程序所用到的各种资源,比如
bmp,cursor,menu,对话框等都存在PE文件中。<BR>
我们将详细介绍关于资源的各种结构,通过一个例子来说明资源及其相关结构是怎么放在PE文件中的。以及如何在遍历PE文件中的所有资源。我们只最终找到这些资源在文件中的位置和长度。而不具体分析某种资源的格式,比如有个BMP的资源,我们不分析BMP格式。
<P><B>一 找到资源在文件中位置。</B></P>
<P>
资源都放在PE文件的某个节中,该节的节表项中的PointerToRawData,就是资源节在文件中的位置。</P>
<P>1.1 得到PE Header在文件中的位置。<BR> 通过DOS
Header结构的成员e_lfanew,可以确定PE Header的在文件中的位置。</P>
<P>1.2 得到文件中节的数目。<BR> 确定PE Header的在文件中的位置之后,就可以确定PE
Header中的成员FileHeader和成员OptionalHeader在文件中的位置。根据 FileHeader 中的
成员NumberOfSections 的值,就可以确定文件中节的数目,也就是节表数组中元素的个数。<BR><BR>1.3
得到节表在文件中的位置。<BR> PE Header在文件中的位置加上PE
Header结构的大小就可以得到节表在文件中的开始位置。PE
Header结构的大小可以由Signature的大小加上FileHeader的大小再加上FileHeader中的SizeOfOptionalHeade来确定。其实到目前为止SizeOfOptionalHeade也就是结构Optional
Header的大小也是固定的,所以整个PE
Header结构的大小也是固定。不过为了安全起见,还是用Signature的大小加上FileHeader的大小再加上FileHeader中的SizeOfOptionalHeade来确定比较保险。</P>
<P>1.4 得到资源节在文件中的位置。<BR>
第1.2步中我们确定了文件中节的数目,第1.3步中我们确定了节表在文件中的位置。<BR>
现在有两种方法来确定资源在文件中的位置。<BR>
第一种方法,根据节的数目,遍历节表数组。也就是从0到(节表数-1)的每一个节表项。<BR>比较每一个节表项的Name字段,看是否等于".rsrc"。如果等于。就找到了资源节的节表项。<BR>这个节表项中的
PointerToRawData 中的值,就是资源节在文件中的位置。<BR> 第二种方法,取得PE
Header中的Optional
Header中的DataDirectory数组中的第三项,<BR>也就是资源项。DataDirectory[]数组的每项都是IMAGE_DATA_DIRECTORY结构,该结构定义如下。<BR>typedef
struct _IMAGE_DATA_DIRECTORY {<BR>DWORD VirtualAddress;<BR>DWORD
Size;<BR>} IMAGE_DATA_DIRECTORY,
*PIMAGE_DATA_DIRECTORY;<BR>取得DataDirectory数组中的第三项中的成员VirtualAddress的值。这个值就是在内存中资源节的RVA。<BR>然后根据节的数目,遍历节表数组。也就是从0到(节表数-1)的每一个节表项。<BR>每个节在内存中的RVA的范围是从该节表项的成员VirtualAddress字段的值开始(包括这个值),<BR>到VirtualAddress+Misc.VirtualSize的值结束(不包括这个值)。<BR>我们遍历整个节表,看我们取得的资源节的RVA,在哪个节表项的RVA范围之内。<BR>如果在范围之内,就找到了资源节的节表项。<BR>这个节表项中的
PointerToRawData
中的值,就是资源节在文件中的位置。<BR>如果这个PE文件没有资源的话,DataDirectory数组中的第三项内容为0。<BR><BR>这样我们就得到了资源在文件中开始的位置。<BR><BR><B>二
PE文件中的资源。</B></P>
<P>
我们已经得到了资源节在文件中的位置。<BR>资源节最开始是一个IMAGE_RESOURCE_DIRECTORY结构。<BR>在WINNT.H中定义如下。</P>
<P>typedef struct _IMAGE_RESOURCE_DIRECTORY {<BR>DWORD
Characteristics;<BR>DWORD TimeDateStamp;<BR>WORD MajorVersion;<BR>WORD
MinorVersion;<BR>WORD NumberOfNamedEntries;<BR>WORD
NumberOfIdEntries;<BR>// IMAGE_RESOURCE_DIRECTORY_ENTRY
DirectoryEntries[];<BR>} IMAGE_RESOURCE_DIRECTORY,
*PIMAGE_RESOURCE_DIRECTORY;<BR>这个结构长度为16字节,共有6个字段。<BR>各字段含义如下:<BR>Characteristics:
Resource
flags,保留用于以后使用,目前都为0。<BR>TimeDateStamp:资源编译器产生资源的时间。<BR>MajorVersion:<BR>MinorVersion:<BR>NumberOfNamedEntries:用字符串来标示IMAGE_RESOURCE_DIRECTORY_ENTRY项的,紧跟着本结构的IMAGE_RESOURCE_DIRECTORY_ENTRY数组的成员个数。<BR>Number
of ID
Entries:用整形数字来表示IMAGE_RESOURCE_DIRECTORY_ENTRY项的,紧跟着本结构的IMAGE_RESOURCE_DIRECTORY_ENTRY数组的成员个数。<BR><BR>
IMAGE_RESOURCE_DIRECTORY后面一定会紧跟着一个IMAGE_RESOURCE_DIRECTORY_ENTRY数组。<BR>IMAGE_RESOURCE_DIRECTORY_ENTRY结构定义如下。<BR><BR>typedef
struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {<BR>union {<BR>struct {<BR>DWORD
NameOffset:31;<BR>DWORD NameIsString:1;<BR>};<BR>DWORD Name;<BR>WORD
Id;<BR>};<BR>union {<BR>DWORD OffsetToData;<BR>struct {<BR>DWORD
OffsetToDirectory:31;<BR>DWORD DataIsDirectory:1;<BR>};<BR>};<BR>}
IMAGE_RESOURCE_DIRECTORY_ENTRY,
*PIMAGE_RESOURCE_DIRECTORY_ENTRY;<BR><BR>这个结构长度为8个字节。共有两个字段,每个字段4个字节。<BR>根据不同情况,这两个字段的含义不一样。这个结构的定义如果看不懂的话,后面的例子一看就会明白了。<BR>第一个字段,当第一个字段的最高位是1的时候,表示,这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容是一个IMAGE_RESOURCE_DIR_STRING,用里面的字符串来标明这个IMAGE_RESOURCE_DIRECTORY_ENTRY。当第一个字段的最高位是0的时候,表示,这个DWORD的低WORD中的值作为id标明这个IMAGE_RESOURCE_DIRECTORY_ENTRY。<BR>第二个字段,当第二个字段的最高位是1的时候,表示,还有下一层的结构。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容会是一个下一层的IMAGE_RESOURCE_DIRECTORY结构,这个请看后面的例子中的说明。<BR>当第二个字段的最高位是0的时候,表示,已经没有下一层的结构了。这个DWORD的剩下31位表明一个相对于资源开始位置的偏移,这个偏移的内容会是一个IMAGE_RESOURCE_DATA_ENTRY结构,IMAGE_RESOURCE_DATA_ENTRY结构会说明资源的位置。</P>
<P>
标示一个IMAGE_RESOURCE_DIRECTORY_ENTRY一般都是使用id,就是一个整数。<BR>但是也有少数的使用IMAGE_RESOURCE_DIR_STRING来标示一个IMAGE_RESOURCE_DIRECTORY_ENTRY。<BR>IMAGE_RESOURCE_DIRECTORY_ENTRY结构定义如下。<BR>typedef
struct _IMAGE_RESOURCE_DIR_STRING_U {<BR>WORD Length;<BR>WCHAR NameString[
1 ];<BR>} IMAGE_RESOURCE_DIR_STRING_U,
*PIMAGE_RESOURCE_DIR_STRING_U;<BR>这个结构中将有一个Unicode的字符串,是字对齐的。所有这些用来标识的IMAGE_RESOURCE_DIR_STRING都放在一起,这个结构的长度是可变的,由第一个字段Length指明后面的Unicode字符串的长度。<BR><BR>
经过3层IMAGE_RESOURCE_DIRECTORY_ENTRY(一般是3层,也有可能更少些。第一层资源类型bmp,menu等等,第二层资源名,第三层是资源的Language。)最终会找到一个IMAGE_RESOURCE_DATA_ENTRY结构,这个结构中存有相应(某资源类型,某资源名,某资源Language)资源的位置和大小,就真正找到资源了。IMAGE_RESOURCE_DATA_ENTRY定义如下。<BR>typedef
struct _IMAGE_RESOURCE_DATA_ENTRY {<BR>DWORD OffsetToData;<BR>DWORD
Size;<BR>DWORD CodePage;<BR>DWORD Reserved;<BR>}
IMAGE_RESOURCE_DATA_ENTRY,
*PIMAGE_RESOURCE_DATA_ENTRY;<BR>这个结构长16个字节,有4个字段。<BR>OffsetToData:这是一个内存中的RVA,要转化成文件中的位置,需要用这个值减去资源节的开始RVA,<BR>资源节的开始RVA可以由Optional
Header中的DataDirectory数组中的第三项中的VirtualAddress的值得到。<BR>或者节表中,资源节那项中的VirtualAddress的值得到。相减之后,就可以得到相对于资源节开始的偏移。<BR>再加上资源节在文件中的开始位置,节表中资源节那项中的PointerToRawData的值,就是资源在文件中的位置。<BR><BR>Size:资源的大小,以字节为单位。<BR>CodePage:一般来说是Unicode
code
page。<BR>Reserved:保留,值为0。<BR><BR>上面是资源各种结构的说明,知道这些结构还远远不够,下面我们通过一个例子来看如何通过这些结构找到资源。<BR><BR>我们的例子是Win2k中的可执行文件telnet.exe。为了防止大家版本不同,本文附带了这个PE文件。</P>
<P>PE文件的资源的各种结构放在一个树型结构中,这个结构一般有3层,如图4.1,就是telnet.exe中的情况。</P>
<P align=center><IMG border=0 height=332
src="JIURL PE 格式学习总结(四)-- PE文件中的资源.files/4.1.gif" width=624> <BR></P>
<P align=center>图4.1</P>
<P
align=left>图中长的长方形表示一个IMAGE_RESOURCE_DIRECTORY结构,长16个字节,简称directory。<BR>图中短的长方形表示一个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,长8个字节,简称directory_entry。<BR>图中圆圈表示一个IMAGE_RESOURCE_DATA_ENTRY结构,长16个字节,简称data_entry。</P>
<P
align=left>为了以后的叙述方便还给树的每一个节点起了名字,第一层的叫11,第二层的叫21,22,23,24,第三层的叫31,32,33,34,35,36,37,38,39,310,311,312。</P>
<P
align=left>在资源节开始处,是一个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的资源。</P>
<P
align=left>下面我们来通过telnet.exe中资源节的具体内容来看,用开始讲到的寻找资源节在文件中位置的方法,我们找到了资源节在文件中的位置为00013600h。</P>
<P align=left>我们为了看起来清楚,每一行是一个结构,并且每个结构的不同成员用 / 分开,例如,<BR>一个directory结构 00
00 00 00 / 00 00 00 00 / 04 00 / 00 00 / 00 00 / 04
00 <BR>可以看到结构成员,Characteristics为0,TimeDateStamp为0,MajorVersion为4,(如果你不明白为什么是0004而不是0400的话,请看
《JIURL PE 格式学习总结(一)》中关于
big-endian和little-endian的介绍),MinorVersion为0,NumberOfNamedEntries为0,NumberOfIdEntries为4。</P>
<P align=left>一个directory_entry结构 03 00 00 00 / 30 00 00
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -