如何在重建C#应用程序时始终生成逐字节相同的.exe文件?

我先给大家介绍一下为什么我会问这个问题:

我目前正在一个严格监管的行业工作,因此我们的代码由官方测试机构仔细查看。 这些测试机构希望能够构建代码并生成.exe或.dll,每次都完全相同(显然不会更改任何代码!)。 他们检查MD5和他们创建的可执行文件的SHA1以确保这一点。

到目前为止,我主要使用C ++进行编码,其中(在几个项目设置调整之后)我设法让项目能够相同地重建到相同的MD5 / SHA1。 我现在正在一个项目中使用C#,并且在重建之后很难让MD5匹配。 我知道文件的PE头中有“Time-Stamps”,它们已经被清除为0.我也知道.exe有一个GUID,它已被清除为00 00 00 …等等但是文件仍然不匹配。

我正在使用CFF资源管理器查看和编辑PE标头以删除时间和日期戳。 使用二进制比较工具后,.exe中只有2个字节块不同(都非常小)。

其中一个不一致的块出现在一些二进制代码之前,在ASCII中详细说明了*Project*\obj\Release\xxx.pdb文件的路径。

编辑:现在已知这是* .pdb文件的GUID,但我仍然不知道是否可以修改它而不会导致任何错误!?

另一个块出现在看起来是函数名称的中间,即。 (典型部分) AssemblyName.GetName.Version.get_Version.System.IO.Ports.SerialPort.Parity.Byte.{

那么不同的代码块:

4A134ACE-D6A0-461B-A47C-3A4232D90816

其次是:

“} .ValueType .__ StaticArrayInitTypeSize = 7. $$ method0x60000ab-1.RuntimeFieldHandle.InitializeArray` …等。

任何想法或建议将是最受欢迎的!

更新:Roslyn似乎有一个/feature:deterministic编译器标志用于可重现的构建,尽管它还没有100%正常工作 。


您应该能够通过禁用PDB生成来摆脱调试GUID。 如果没有,将GUID设置为零是很好的 – 只有调试器查看该部分(您将无法再调试程序集,但它应该仍然运行正常)。

PrivateImplementationDetails有点困难 – 这些是编译器为某些语言结构生成的内部帮助器类(数组初始化器,使用字符串的switch语句等)。 因为它们仅在内部使用,所以类名称并不重要,因此您可以为它们分配一个运行编号。

我将通过浏览#Strings元数据流并将“ {GUID}”forms的所有字符串替换为“ {running number,填充到与GUID相同的长度}”来完成此操作。

#Strings元数据流只是元数据使用的字符串列表,以UTF-8编码并以\ 0分隔; 因此,一旦您知道#Strings流在可执行文件中的位置,查找和替换名称应该很容易。

不幸的是,包含此信息的“元数据流标题”非常隐藏在文件格式中。 您必须从NT可选标题开始,找到指向CLI运行时标题的指针,使用PE节表将其解析为文件位置(它是RVA,但您需要在文件中放置一个位置),然后转到元数据根并读取流标头。

我不确定这一点,但只是一个想法:你使用的是编译器可能在幕后生成名称的任何匿名类型,每次编译器运行时可能会有所不同吗? 只是有可能发生在我身上。 可能是Jon Skeet的一个;-)

更新:您也可以使用Reflector 插件进行比较和反汇编。

关于PDB GUID问题,如果指定不应在发布版本的编译时生成PDB,那么二进制文件是否仍包含PDB的文件系统GUID?

要禁用PDB生成:

  1. 在Solution Explorer中右键单击您的项目,然后选择Properties。
  2. 从左侧菜单中选择Build。
  3. 确保Configuration选项是Release(您仍然需要PDB进行调试)。
  4. 单击右下角的“高级”按钮。
  5. 在Output / Debug Info下,选择None。

如果您是从控制台构建的,请使用/ debug-来获得相同的结果。

看看这个问题的答案。 特别是在第3个提供的外部链接上。

编辑:

我实际上想要链接到这篇文章。

您说在经过一些项目调整后,您可以将C ++应用程序重复编译为相同的SHA1 / MD5值。 我和你在一个拥有第三方测试实验室的行业中的同一条船需要重复完全相同的可执行文件。

在研究如何在VS2005中实现这一点时,我在这里发现了你的post。 您是否可以分享您为使C ++应用程序一致地构建相同的SHA1 / MD5值所做的项目调整? 这对我自己以及任何其他有共同要求的人都有很大的帮助。

使用ildasm.exe完全反汇编这两个程序并比较IL。 然后,您可以使用基于文本的方法“清理”代码,并(可预测地)再次重新编译它。