﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-溪流漫话-随笔分类-ASM &amp; Crack</title><link>http://www.cppblog.com/Streamlet/category/11945.html</link><description>请大虾们多多指教~</description><language>zh-cn</language><lastBuildDate>Tue, 29 Sep 2009 18:51:11 GMT</lastBuildDate><pubDate>Tue, 29 Sep 2009 18:51:11 GMT</pubDate><ttl>60</ttl><item><title>PE 文件的字串表格式分析</title><link>http://www.cppblog.com/Streamlet/archive/2009/09/23/97060.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Wed, 23 Sep 2009 14:57:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2009/09/23/97060.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/97060.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2009/09/23/97060.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/97060.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/97060.html</trackback:ping><description><![CDATA[<p>前几天公司里一个项目要做 MUI 支持，于是要生成一堆 XXX.dll.mui 的文件。如果这些 MUI DLL 的工程手动去建立、维护的话，那就太!@#@!#!了。当时是另外一个同事去做这方面的工作的，后来他给了个工具，按照它定义的简单格式来书写多语言字符串，这个工具会从一个已经设定好的 DLL 项目出发，更改 RC 文件里的字符串，然后调用 VS 的 IDE 来生成 DLL。再然后调用 MUIRCT.exe 来生成 MUI 文件。</p> <p>这可以节省很多时间。但是，由于是调用 VS IDE 来编译的，一个带有近百个 Project 的 Solution 编译起来并不快，需要一到两分钟。这让我有了另辟蹊径的念头。</p> <p>何不自己来“编译”生成 DLL 呢？</p> <p>不错，后来我就往这个方向琢磨了。之前曾写过一个修改 PE 文件版本号的小工具，所以现在对于 PE 的资源格式有点并不那么恐惧了。但是，往细处做下去，问题就来了。现在网上的关于 PE 格式的文章，对 NTHeader 解释得很详细，而资源段往往只讲到资源目录、资源项，具体各项的存储结构却没有详细说明了。</p> <p>这里，关于 PE 头等就不多说了，请参考网上的文章，特别是 <a title="http://bbs.pediy.com/showthread.php?threadid=21932" href="http://bbs.pediy.com/showthread.php?threadid=21932" target="_blank">http://bbs.pediy.com/showthread.php?threadid=21932</a>。本文将着眼于资源段。</p> <p>首先来看一下几个数据结构（这些内容好多文章也有提及）：</p> <p><font color="#0000ff">typedef struct _IMAGE_RESOURCE_DIRECTORY {<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; Characteristics;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; TimeDateStamp;<br>&nbsp;&nbsp;&nbsp; WORD&nbsp;&nbsp;&nbsp; MajorVersion;<br>&nbsp;&nbsp;&nbsp; WORD&nbsp;&nbsp;&nbsp; MinorVersion;<br>&nbsp;&nbsp;&nbsp; WORD&nbsp;&nbsp;&nbsp; NumberOfNamedEntries;<br>&nbsp;&nbsp;&nbsp; WORD&nbsp;&nbsp;&nbsp; NumberOfIdEntries;<br>} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;</font>  <p>这是资源目录，共 16 字节，其中最后两个 WORD 加起来是紧跟在后面的子项的数目。</p> <p><font color="#0000ff">typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {<br>&nbsp;&nbsp;&nbsp; union {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD NameOffset:31;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD NameIsString:1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; Name;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WORD&nbsp;&nbsp;&nbsp; Id;<br>&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp; union {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; OffsetToData;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; OffsetToDirectory:31;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; DataIsDirectory:1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp; };<br>} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY; </font> <p>这个就是紧跟在目录后面的资源目录项，共 8 字节。其中第一个成员为数据成员，最高位 1 表示数据是字符串，剩下 31 位是字符串的偏移；否则就是数值。第二个成员最高位为 1 表示下一层仍然是目录，后 31 位指向另一个 IMAGE_RESOURCE_DIRECTORY 结构；否则整个成员指向一个 IMAGE_RESOURCE_DATA_ENTRY 结构（这个马上会讲到）。需要注意的是，这里的两个 Offset 都表示从资源段开头到目标位置的偏移。</p> <p>最后来看 IMAGE_RESOURCE_DATA_ENTRY：</p> <p><font color="#0000ff">typedef struct _IMAGE_RESOURCE_DATA_ENTRY {<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; OffsetToData;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; Size;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; CodePage;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; Reserved;<br>} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY; </font> <p>这个结构是资源数据项，也就是资源树的叶子，共 16 字节。其中第一个成员 OffsetToData 指向具体的数据，这个偏移是个 RVA，跟前面两个不一样。Size 表示具体数据的总字节数。后两个成员可以为 0，CodePage 不建议使用。</p> <p>PE 文件中的资源就是通过这三个结构表示的，它们都在 WinNT.h 中定义。通常会有 3 层结构，第一层表示资源类型，第二层表示 ID，第三层标识语言。</p> <p>以上所说的是我能查到的资料里能够提到的最大程度的内容了。但是具体的数据如何存储，却几乎没有文章提及。于是，花了一两天时间来慢慢的看、加上试验，我认为我对字符串资源的格式基本清楚了。（下面内容是我自己分析得出，其正确性我并不保证）。</p> <p>我们先来看一个具体的例子。这是一个资源 DLL，用 Resource Hacker 查看如图：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/WindowsLiveWriter/PE_142C5/image_2.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/WindowsLiveWriter/PE_142C5/image_thumb.png" width="507" height="212"></a> </p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/WindowsLiveWriter/PE_142C5/image_4.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/WindowsLiveWriter/PE_142C5/image_thumb_1.png" width="405" height="228"></a> </p> <p></p> <p>&nbsp;</p> <p>其资源段数据如下：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/WindowsLiveWriter/PE_142C5/image_6.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/WindowsLiveWriter/PE_142C5/image_thumb_2.png" width="618" height="314"></a> </p> <p>我用桔色框起来的是资源目录，用粉色框起来的是资源目录项，用浅绿色框起来的是资源数据项。</p> <p>先看第一行，这是第一层目录，最后两个 WORD 是 0x0000 和 0x0001，表示后面“命名”的目录项有 0 个，使用 ID 的目录项有 1 个。第二行开头的 8 字节就是这个目录项，DWORD 0x00000006 表示资源类型是 6，也就是字串表，后面的地址是 0x80000018，最高位为 1，表示指向的仍然是一个目录，其偏移是 0x00000018，也就是 0218h 处。</p> <p>0218h 处这个资源目录是第二层了。最后仍然是 0 和 1，于是我们来看 0228h 处的目录项。第一个 DWORD 是 1，这个跟 ID 有关，稍候讨论。他的第二个 DWORD 是 0x80000030，仍然指向目录。</p> <p>0230 处的目录是第三层目录。注意到最后是 0 和 2，下面将有连续两个目录项。第一个目录项值为 0x00000409（1033，英语(美国)），偏移地址 0x00000050，最高位 0，表示指向的是数据项，而不是目录了。第二个目录项值为 0x00000804（2052，中文(中国)），偏移地址 0x0000009C。</p> <p>这三层结构和 Resource Hacker 中显示的是一一对应的。</p> <p>我们先来看英语的那个数据项，OffsetToData 是 0x00001060（RVA），Size 是 0x0000003C。这个 DLL 文件的资源段的 VirtualAddress 是 1000h，1060h-1000h+200h = 260h，我们来看 260h 处（其实就是紧接着的地方）。我第一次看这段数据的时候也很奇怪，为什么前面空了 2 个字节，后面有多出好多字节。于是我改它的 ID，试了好些次，终于找到规律了。资源目录第二层的 ID（下文称 ResID）和最终的字符串 ID（下文称 StrID）有这么一个对应关系：ResID = StrID / 16 + 1。StrID 0 到 15 所对应的 ResID 都是 1， StrID 16 到 31 对应 ResID 2，……。反过来说，资源目录中的 ResID 不能完全表达 StrID 的信息。所以，在 260h 开始的 3Ch 个字节的数据块里，其实要存储 16 个字符串，其 StrID 分别是 0，1，2，……，15。这 16 个字符串是连续存储的，结构是：字符串长度（WORD）+字符串内容（不含结束符 0）。那些空位就由一个 WORD 0 来填充（也可理解为长度为 0 的字符串）。我在图中用红褐色的竖线划出了这 16 个字符串的界限。后面那个中文的也是如此，就不重复说了。</p> <p>到现在为止，对于字串表的结构，应该说差不多清楚了。于是拿程序去生成似乎不是难事了，不过要注意的是，目录项必须紧跟在目录后面，目录项指向的位置可以随意。</p> <p>事实上上面这个 DLL 是我用程序生成的。我现在做到了从内部数据结构到资源 DLL 这个过程的实现。如果这也可以被称为“编译”的话，现在是实现了后端。至于前端，我还没想好原始资源格式。要想让这个工具有点用处，原始资源格式必须要：1、足够简单（至少比 RC 文件简单），并且维护方便；2、足够存储多语言字符串。这方面我希望大家能给我一些建议。</p> <p>当然，本文的主要内容还是讨论字串表的格式，这个已经讲完了，所以，over~ bow~</p><img src ="http://www.cppblog.com/Streamlet/aggbug/97060.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2009-09-23 22:57 <a href="http://www.cppblog.com/Streamlet/archive/2009/09/23/97060.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>