利用 MD5 冲突(在 C# 中)






4.73/5 (36投票s)
2005年9月14日
5分钟阅读

370233

3937
本文展示了如何利用 MD5 碰撞来篡改软件分发方案。
引言
在我之前的文章 告别 MD5 中,我向您介绍了密码学和 MD5 碰撞检测的最新研究成果。一场辩论开始了,大多数人认为这些发现不是一个严重的问题。
微软也承认这是一个重要的问题
“根据微软公司一位高管的说法,微软正在禁止在新的计算机代码中使用某些加密函数,原因是这些函数日益复杂的攻击使其安全性降低。”
“这家位于华盛顿州雷德蒙德的软件公司对所有开发人员实施了一项新政策,禁止使用 DES、MD4、MD5 以及在某些情况下使用 SHA1 加密算法的函数,因为这些算法正变得‘边缘老化’,”该公司高级安全计划经理 Michael Howard 表示。来源:微软在新代码中弃用旧加密方式
现在我们有一些概念验证,例如 一对 X.509 碰撞证书 以及一个精彩的例子,一对具有相同 MD5 哈希值的 Postscript 文档,您可以在优秀的论文 “通过被污染的消息攻击哈希函数:爱丽丝和她的老板的故事” 中阅读到相关内容。
如何利用碰撞
关于 MD5 哈希函数有一个已知的结论:
If MD5(x) == MD5(y) then MD5(x+q) == MD5(y+q)
因此,如果您有一对消息 `x` 和 `y`,它们的 MD5 值相同,您可以附加一个载荷 `q`,MD5 值保持不变,`q` 的大小是任意的。您需要一对向量 `x` 和 `y` 来进行利用。您可以尝试自己寻找一对,但我们已经有了一对值,由中国研究人员 Joux 和 Wang 提供。这对向量值的实际应用在 Dan Kaminsky 的论文 《MD5 总有一天会受到谴责》 中得到了解释。
在我的博客中,我曾 写过关于这些问题,现在我想向您展示如何利用这些向量来制造一个利用。
破解软件分发
本文中将展示的概念验证具有以下场景:
- 这是一个模拟的软件分发机制。
- 软件以二进制格式分发,文件扩展名为 .bin。
- 存在一个提取程序,用于检查和提取 .bin 文件中的软件。
- 为验证目的,我们使用 MD5 值来检查 .bin 文件的完整性。
这张图片展示了一个场景,生成了一对具有相同 MD5 的二进制文件,MD5(good.bin) == MD5(evil.bin)
攻击分发软件
首先,我们将构建一个生成器程序。该程序接收一对可执行文件,第一个是无害程序,第二个是恶意文件,是一个有害程序,然后生成一对二进制分发文件(.bin 文件)。这些是 good.bin 分发文件和 evil.bin 分发文件。
无害程序
无害程序的代码很简单:
namespace GoodExe
{
/// <summary>
/// A Harmless program
/// </summary>
class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Console.WriteLine ("this is a good executable");
}
}
}
恶意程序
这是恶意程序的代码,它模拟了有害行为:
using System;
using System.Threading;
namespace EvilExe
{
/// <summary>
/// A harmfull program
/// </summary>
class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Console.WriteLine ("This is an evil file");
Console.WriteLine ("Formatting your hard drive...");
Thread.Sleep(2000);
Console.WriteLine ("Just joking...");
}
}
}
碰撞生成器
我们有一对具有相同 MD5 值的向量。
/// <summary>
/// First prefix vector
/// Taken from stripwire by Dan Kaminsky
/// http://www.doxpara.com/md5_someday.pdf
/// </summary>
static byte[] vec1 =
{
0xd1, 0x31, 0xdd, 0x02, 0xc5, 0xe6
, 0xee , 0xc4 , 0x69 , 0x3d, 0x9a , 0x06
, 0x98 , 0xaf , 0xf9 , 0x5c , 0x2f , 0xca
, 0xb5 , /**/0x87 , 0x12 , 0x46 , 0x7e
, 0xab , 0x40 , 0x04 , 0x58 , 0x3e , 0xb8
, 0xfb , 0x7f , 0x89 , 0x55 , 0xad
, 0x34 , 0x06 , 0x09 , 0xf4 , 0xb3 , 0x02
, 0x83 , 0xe4 , 0x88 , 0x83 , 0x25
, 0x71 , 0x41 , 0x5a, 0x08 , 0x51 , 0x25
, 0xe8 , 0xf7 , 0xcd , 0xc9 , 0x9f ,
0xd9 , 0x1d , 0xbd , 0xf2 , 0x80 , 0x37
, 0x3c , 0x5b , 0xd8 , 0x82 , 0x3e
, 0x31 , 0x56 , 0x34 , 0x8f , 0x5b , 0xae
, 0x6d , 0xac , 0xd4 , 0x36 , 0xc9
, 0x19 , 0xc6 , 0xdd , 0x53 , 0xe2 , 0xb4
, 0x87 , 0xda , 0x03 , 0xfd , 0x02
, 0x39 , 0x63 , 0x06 , 0xd2 , 0x48 , 0xcd
, 0xa0 , 0xe9 , 0x9f , 0x33 , 0x42
, 0x0f , 0x57 , 0x7e , 0xe8 , 0xce , 0x54
, 0xb6 , 0x70 , 0x80 , 0xa8 , 0x0d
, 0x1e , 0xc6 , 0x98 , 0x21 , 0xbc , 0xb6
, 0xa8 , 0x83 , 0x93 , 0x96 , 0xf9
, 0x65 , 0x2b , 0x6f , 0xf7 , 0x2a , 0x70
};
/// <summary>
/// Second prefix vector
/// Taken from stripwire by Dan Kaminsky
/// http://www.doxpara.com/md5_someday.pdf
/// </summary>
static byte[] vec2 =
{
0xd1 , 0x31, 0xdd , 0x02 , 0xc5 , 0xe6
, 0xee , 0xc4 , 0x69 , 0x3d , 0x9a , 0x06
, 0x98 , 0xaf , 0xf9 , 0x5c, 0x2f , 0xca
, 0xb5 , /**/0x07 , 0x12 , 0x46 , 0x7e
, 0xab , 0x40 , 0x04 , 0x58 , 0x3e , 0xb8
, 0xfb , 0x7f , 0x89 , 0x55 , 0xad
, 0x34 , 0x06 , 0x09 , 0xf4 , 0xb3 , 0x02
, 0x83 , 0xe4 , 0x88 , 0x83 , 0x25
,/**/ 0xf1 , 0x41 , 0x5a , 0x08 , 0x51 , 0x25
, 0xe8 , 0xf7 , 0xcd , 0xc9 , 0x9f
, 0xd9 , 0x1d , 0xbd , /**/0x72 , 0x80
, 0x37 , 0x3c , 0x5b, 0xd8 , 0x82
, 0x3e , 0x31 , 0x56 , 0x34 , 0x8f , 0x5b
, 0xae , 0x6d , 0xac , 0xd4 , 0x36
, 0xc9 , 0x19 , 0xc6 , 0xdd , 0x53 , 0xe2
, /**/0x34 , 0x87 , 0xda , 0x03 , 0xfd
, 0x02 , 0x39 , 0x63 , 0x06 , 0xd2 , 0x48
, 0xcd , 0xa0, 0xe9 , 0x9f , 0x33
, 0x42 , 0x0f , 0x57 , 0x7e , 0xe8 , 0xce
, 0x54 , 0xb6 , 0x70 , 0x80 , /**/ 0x28
, 0x0d , 0x1e, 0xc6 , 0x98 , 0x21 , 0xbc
, 0xb6 , 0xa8 , 0x83 , 0x93 , 0x96
, 0xf9 , 0x65 , /* flag byte*/0xab
, 0x6f , 0xf7 , 0x2a , 0x70
};
请记住,给定这对向量,如果我们有一个任意大小的载荷,那么 MD5(vec1+payload) == MD5(vec2+payload)。载荷的构建方式是,good 文件的大小,evil 文件的大小,good 文件的内容,以及 evil 文件的内容。
生成程序的核心代码如下所示:
[STAThread]
static void Main(string[] args)
{
if (args.Length != 2)
Usage();
byte[] goodFile = ReadBinaryFile(args[0]);
byte[] evilFile = ReadBinaryFile(args[1]);
WriteBinary("good.bin", vec1, goodFile, evilFile);
WriteBinary("evil.bin", vec2, goodFile, evilFile);
ShowMD5("good.bin");
ShowMD5("evil.bin");
}
private static void Usage ()
{
Console.WriteLine("Usage: md5gen good_file evil_file");
Environment.Exit (-1);
}
private static void WriteBinary (string sOutFileName,
byte[] prefix, byte[] goodFile, byte[] evilFile)
{
using (FileStream fs = File.OpenWrite (sOutFileName))
{
using (BinaryWriter writer = new BinaryWriter(fs))
{
writer.Write(prefix);
writer.Write ( goodFile.Length );
writer.Write ( evilFile.Length );
fs.Write (goodFile, 0, goodFile.Length);
fs.Write (evilFile, 0, evilFile.Length);
fs.Close ();
}
}
}
如果我们对这对程序应用生成器程序,我们将生成一对文件 good.bin 和 evil.bin,如下所示:
C:\TEMP>md5clone goodexe.exe evilexe.exe
MD5 Hash for good.bin is 1D8EE13FBA00DD022F002AAD0E3EF9C7
MD5 Hash for evil.bin is 1D8EE13FBA00DD022F002AAD0E3EF9C7
现在,我们可以将 good.bin 发布到互联网供人们下载,稍后,我们可以用 evil.bin 替换它。现在,用户将在不知情的情况下感染病毒,并相信没有被篡改,因为两个文件的 MD5 签名是相同的,换句话说,我们有 MD5(good.bin) == MD5(evil.bin)。
提取程序
现在,假设我们已经更改了提取程序,使用了我们自己的版本。我们的提取器接收 .bin 分发文件,并根据 .bin 文件开头的前缀向量提取无害程序或恶意程序。我们使用第 123 个字节来检测用作前缀的向量。
提取程序的代码非常简单:
[STAThread]
static void Main(string[] args)
{
if (args.Length == 0)
Usage();
ExtractFile(args[0], args[1]);
}
private static void ExtractFile (string sfilename,
string soutputfile)
{
using (BinaryReader reader =
new BinaryReader(File.OpenRead (sfilename)))
{
byte[] vec = reader.ReadBytes (128);
int goodSize = reader.ReadInt32 ();
int evilSize = reader.ReadInt32 ();
/// open evil file
if (vec[123] == 0xab)
{
reader.ReadBytes (goodSize);
byte[] evil = reader.ReadBytes (evilSize);
using (BinaryWriter writer =
new BinaryWriter(File.OpenWrite (soutputfile)))
{
writer.Write (evil);
writer.Close ();
}
}
else
{
byte[] good = reader.ReadBytes (goodSize);
using (BinaryWriter writer =
new BinaryWriter(File.OpenWrite (soutputfile)))
{
writer.Write (good);
writer.Close ();
}
}
Console.WriteLine ("File written on {0}",
soutputfile);
}
}
private static void Usage ()
{
Console.WriteLine(
"Usage: md5extract file.bin output_file.exe");
Environment.Exit (-1);
}
假设您收到 good.bin 文件,然后您将提取器应用于 good.bin,并将提取 good.exe 文件。但是如果您收到 evil.bin 文件,那么提取器将提取 evil.exe,即恶意可执行文件。请记住 MD5(evil.bin) == MD5(good.bin)。
结论
最近,加密哈希函数领域陷入了危机。许多研究人员宣布了对 MD5 和 SHA-1 等常用哈希函数进行碰撞“攻击”。“对于密码学家来说,这些结果令人兴奋——但许多所谓的‘从业者’却将其视为‘实践无关紧要’而置之不理。”
我希望这个概念验证能让您相信 MD5 存在一个严重的问题。
“针对 MD5 的碰撞查找攻击已经出现了一些利用实例: Kaminski [Ka] 和 Mikle [Mi] 展示了具有相同 MD5 哈希值的不同可执行文件。Kaminski 的一个可执行文件是完全无害的,而另一个则非常有害。Mikle 的可执行文件是自解压存档,解压出不同的内容。Lenstra、Wang 和 de Weger [LWdW,LdW] 构建了碰撞的 X.509 证书。”
本文展示了软件分发链中的一个故障,如何利用当前密码学领域关于 MD5 哈希函数的最新发现。
历史
- 2005 年 9 月 20 日:进行了一些语法更正,并修改了标题。