题目:Microsofts Rich Header
作者:Peter Kleissner
译者:Cryin'(http://hi.baidu.com/justear)

概述

Rich Header是PE文件DOS Header和NT Header之间的一个结构(在PE Header和DOS stub之间)。它包含链接库版本信息以及链接器版本。

The Rich Header

Rich Header是由微软编译器创建,通常看起来像如下:

WSNPOEM Decryption Code analyzed:

00000080  54 62 EF 9B 10 03 81 C8 10 03 81 C8 10 03 81 C8  Tb??...è...è...è
00000090  37 C5 EF C8 11 03 81 C8 37 C5 FC C8 12 03 81 C8  7??è...è7?üè...è
000000A0  37 C5 FA C8 0B 03 81 C8 10 03 80 C8 C9 03 81 C8  7?úè...è..èé..è
000000B0  37 C5 EC C8 33 03 81 C8 37 C5 FD C8 11 03 81 C8  7?ìè3..è7?yè...è
000000C0  37 C5 F9 C8 11 03 81 C8 52 69 63 68 10 03 81 C8  7?ùè...èRich...è

这是notepad.exe的Rich Header信息,起始偏移地址为80h,紧跟在DOS stub之后。紧跟在Rich Header之后的是PE Header。从上面的Rich Header信息你大概可以看出Rich Header是被加密过的。

解密Rich Header

Rich Header信息是被异或操作加密过的,观察上面Rich Header数据你会发现一个重复多次的DWORD值即"Rich"之后的10 03 81 c8。将Rich Header信息的起始位置一直到关键字"Rich"的数据与这个DWORD值进行异或操作,操作后的Rich Header如下所示:

00000080  44 61 6e 53 00 00 00 00 00 00 00 00 00 00 00 00  DanS............
00000090  27 c6 6e 00 01 00 00 00 27 c6 7d 00 02 00 00 00  '?n.....'?}.....
000000A0  27 c6 7b 00 1b 00 00 00 00 00 01 00 d9 00 00 00  '?{.........ù...
000000B0  27 c6 6d 00 23 00 00 00 27 c6 7c 00 01 00 00 00  '?m.#...'?|.....
000000C0  27 c6 78 00 01 00 00 00 52 69 63 68 10 03 81 c8  '?x.....Rich...è

我们看到第一个DWORD值为"DanS",这个好像正是Dan Ruder的开头部分,Dan Ruder正是"Mechanics of Dynamic Linking" in 1993(MSDN Library Archive)的作者之一。

格式解析

从lifewire的文章详见参考[6]获知Rich Header结构的格式如下:

'DanS'^b, b, b, b         -- 身份认证头
compid^b, r^b           -- 从0开始
..                     --      :
compid^b, r^b          -- 直到n

'Rich', b              -- 结束位置

后面是一些无用的信息


上面所有的值都是DWORD型,b是异或值,是一个校验和值。^是异或操作符。compid 是编译器id(compiler id)。最小的Rich Header的大小为8*4(至少包含一个编译器id)。可以通过关键字"Rich"和"DanS"验证Rich Header信息。

编译器id是编译库文件所使用的连接器版本。Rich Header包含一个所有被链接的库文件的链表,这个正是compid的值。compid最后的值是链接文件的连接器版本号。comp.id 的值是按照DWORD型存储。LWORD包含了组建号,HWORD的低位(low 4 bits  )包含主版本号,高位(high 4 bits)包含子版本号。

校验和与异或值b通过如下步骤计算得出:

b=sizeof(dos_stub)              // 一般情况均为0x80

for (int i = 0; i < sizeof(dos_stub); i++)
{
    b += dos_stub[i] ROL i;     // ROL 循环左移操作
}

for (int i = 0; i < n; i++)
{
    b += compid[i] ROL r[i];
}


第一个循环根据DOS stub生成一个校验和,第二个循环遍历所有库版本。具体算法介绍可参考[6]。

@comp.id值

正如前面描述,compid 的值为连接器版本号。连接器包含所有静态链接库的comp.id值(例如kernel32.lib)并存储在一个列表中。连接器将自己的版本信息添加到这个列表的末尾。你可以在一个lib文件中查找"@comp.id",紧跟着的下一个DWORD就是连接器版本号(即编译器id)。

通过列表里面的最后一个compid 值,你可以确定编译器版本、连接器版本以及编译应用程序所使用的开发环境。例如编译器id值为0x0078C627就意味着"Version 8.00.50727, Microsoft Visual Studio 2005"。这里要注意微软针对不同编译器版本可能不尽相同。你可以通过组建号来识别使用的是编译器(cl.exe)还是连接器(link.exe)版本(组建号相同,编译器和连接器的主版本和字版本号不同)。

你可以创建并使用一个@comp.id转换表(编译器/连接器版本号值的转换)来利用Rich Header 的有效性。

参考链接

[1] http://www.woodmann.com/forum/archive/index.php/t-11367.html
[2] http://www.donationcoder.com/Forums/bb/index.php?action=printpage;topic=11956.0
[3] http://trendystephen.blogspot.com/2008/01/rich-header.html
[4] http://www.ntcore.com/Files/richsign.htm
[5] http://computer.forensikblog.de/en/2008/03/more_about_the_rich_header.html
[6] http://www.woodmann.net/forum/attachment.php?attachmentid=1050

关于翻译

翻译的可能不是很清楚(Don't blame me),如果想自己进行解密实验,可以使用我在http://bbs.pediy.com/showthread.php?t=125416上传的工具中就有DWORD异或解密工具自己解密探个究竟。另外可以参见原文及参考文献进一步阅读。