前言
通常我们使用Visual Studio生成可执行文件的时候,编译器总会给我们文件里添加各种我们不想要的信息,Rich Header就是其中一种。
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 的有效性。
其他
除了清除Rich Headers 还需要清除编译时候的Debug信息来对抗分析,这类工具其实很多,我就不列举出来
参考项目
http://www.ntcore.com/files/richsign.htm
https://github.com/lordmulder/rich-header-eraser
https://github.com/mthiesen/link-patcher
参考文章
https://www.pediy.com/kssd/pediy12/125447.html