【问题标题】:Get code from C# DLL Dynamically从 C# DLL 动态获取代码
【发布时间】:2012-08-16 12:12:58
【问题描述】:

我想看看 DLL(可能在不同的机器上编译)是否相同。为此,我正在做的是加载 DLL 并计算 MD5,因为 DLL 在不同的机器上失败(但具有相同的源)而失败。这似乎是由于在编译时添加了其他元数据(正如有人提到的here)。

我想过对整个 DLL 进行逆向工程,看看代码是否匹配,但是,我有两个问题:

  • 我只能找到执行此操作的工具,我似乎找不到 C# 库或类似的东西来满足我的需要。
  • 我不能 100% 确定反编译的源代码是否与跨不同机器编译的源代码相同。

任何提示、提示和指点将不胜感激。

【问题讨论】:

  • 要让它 100% 工作,这将是一项极其艰巨的任务。但祝你好运,期待可能的解决方案;p
  • 有趣的问题。我不认为编译器可以保证为相同的代码生成相同的IL,即使在同一台机器上,所以甚至Reflector也不会给出相同的结果。
  • “我想看看 DLL 的 [...] 是否相同。” - 你也可以改进你的发布计划。或者,您想解决什么问题?
  • 编译器不能保证两次产生相同的结果,即使在同一台机器上来自同一个源。见blogs.msdn.com/b/ericlippert/archive/2012/05/31/…
  • @mikez:最重要的部分:“C# 编译器在每次运行时都会在每个程序集中嵌入一个新生成的 GUID,从而确保没有两个程序集是逐位相同的”

标签: c# .net reverse-engineering


【解决方案1】:

您可能是对的 - 它可能是元数据。不过,我认为这不一定是最有可能的可能性。

DLL 不同的另一个原因可能是它们是针对不同版本的 .NET 编译的,或者可能是 MONO。

无法保证反编译 DLL 会产生相同的代码,即使它们是从同一源编译的;实际上,考虑到编译器的优化特性,稍微不同的源代码可以编译为相同的可执行文件的可能性很小,理论上(但存在) - 通常,循环将被展开 - 即变成顺序的非循环指令- 当这会节省内存使用或 CPU 时间时。

如果程序员手动展开循环并重新编译,这就是编译器一直在做的优化 - presto,两个不同的源具有相同的输出。

一个更好的问题是您希望通过比较这两个 DLL 来了解什么。如果纯粹是为了学习,那就太棒了,值得称赞——但是,要对此进行有意义的研究,您需要的知识量是相当高的。通过学习通用的、更适用的 C#/.net 技术,您可能会找到更好的结果。

【讨论】:

  • 这与优化无关。这就是 C# 编译器发出“编译器生成的代码”的方式。这几乎可以保证是唯一的,即使您在同一台 PC 上编译相同的代码一秒钟。问题不在于代码布局甚至方法体,而是元数据指向的内容,元数据的顺序可以而且可能会在 2 次编译之间发生变化。
  • 我们面临的问题是我们在不同的机器上拥有相同 DLL 的不同版本(同名,不同的功能,随着 DLL 的改进而改变)。我们想创建一个 DLL 及其版本的列表,这就是为什么我想知道我所追求的是否可能。
  • @npinti 检查我的答案,我相信你应该这样做。给他们起一个强有力的名字。
【解决方案2】:

使用强名称对该程序集进行签名,您将能够绝对确定两个或多个程序集只是同一个-或不同的-,因为它们具有相同的程序集版本,相同的公钥令牌等等。

如果代码源和 Visual Studio 项目不是同一个,我怀疑两个不同的开发人员会有重复的私钥。

【讨论】:

  • 即使程序集是强命名的并且来自相同的来源,程序集哈希也会不同。
  • @leppie 是的,我知道。但是,如果可以使用强名称,为什么还要检查使用哈希?
  • 您是否建议为每个版本/编译/构建使用不同的私钥?这将是非常痛苦的 IMO。
  • @leppie 不,我建议使用相同的私钥,但更改程序集版本。也许我错了,但似乎这可能是解决这个问题的正确方法。
  • @leppie 没有计算机工具可以取代人类的组织和知识。你懂的。告诉我“对于不同的树干或分支或其他任何东西,相同的程序集具有相同的版本号是否合理?”。 2 个不同的团队使用相同的公用私钥,但如果这些团队使用不同的代码库开发,则“SomeAssembly”的“1.0”版本可能不一样!那么问题是版本控制架构错误。
【解决方案3】:
  • 这些库是你的,我是说,是你的代码吗?
  • 它们是由您的安装人员安装的,还是独立安装的,您只需检查它们?

如果您可以通过任何方式监督它们在目标机器上的初始安装,您可以使用普通的旧 DLL 资源做一些穷人的水印。

将包含您自己内容的二进制资源附加到已安装的每个版本的 DLL,然后检查该文件。如果您在每个代码中嵌入 public static readonly class Something{ public static SomeData MyImportantInformation = ...; } 并在运行时读取它,或者您将 [Attributes] 与某些类上的数据一起使用并通过反射读取它们,那就更棒了——但使用二进制资源有两个微小的优势:

  • 您可以在 DLL 在构建之后从 DLL 添加/删除资源(有点像使用 ILMerge 工具)
  • 您可以像从托管代码中一样轻松地从本机代码中读取资源,并且要读取它们,您可以以非常有限且节省资源的方式加载 DLL

请注意,我的意思是“低级资源”,例如 Manifest,它通常位于插槽 #0 上的资源,或 .exe/.dll 图标。

关于二进制资源:

http://www.codeproject.com/Articles/4221/Adding-and-extracting-binary-resources

以及更易于使用的托管嵌入式资源:

http://keithelder.net/2007/12/14/how-to-load-an-embedded-resource-from-a-dll/ https://stackoverflow.com/a/7978410/717732

您可以在构建脚本中添加/修改资源,以确保发布的每个版本都添加了不同/正确的信息。当然,如果您控制构建过程,那么您也可以启动前面提到的 ILmerge 以将任何内容放入任何 DLL 中。虽然大部分都可以工作,但总的来说,我认为这是一种矫枉过正,如果做得不正确的话如果它在签名后修改 DLL,则会破坏任何安全签名。它必须在它之前完成..

如果您控制构建过程,您可以将必要的版本信息作为类静态数据或简单地作为程序集级别的属性放入代码中,或者 (...)

或者你为什么不直接使用版本号来区分版本? :) IE。 semantic versioning?

另一方面,如果您正在使用非您的 DLL,并且您无法控制它们的部署,那么您的处境就很艰难。正如其他人所说,编译器在编译过程中可能会应用许多不同的技巧,但是 - 请注意 - 他们对编译后的代码有一些法律和逻辑限制。

“逻辑”约束示例:
- 他们可能会改变说明,但可能不会改变整体含义和(副作用) - 他们可能会改变代码和数据布局/结构,但不会改变处理它们的算法 等等

“法律”限制示例:
- 不允许删除任何公共符号(公共 = 其他代码模块可见,即在 .Net 中涵盖:公共和受保护,有时甚至是内部和私有) - 不允许更改任何公共符号的名称 - 不允许他们更改任何公共符号的签名 等等

现在,如果您将自己仅限于此类信息,您可以以一种有机会独立于编译器和平台的方式收集/计算任何代码的哈希值/签名。您不会得到相同或不同的明确答案,但您会了解它们的可能性有多大。

举个最简单的例子:通过反射加载 DLL 并扫描所有类的公共和非公共成员名称。然后,要么计算该字符串集的哈希值,要么只使用整个字符串集,我可能最多以千字节计。如果对代码进行了较大的更改,几乎可以肯定会添加或删除某些字段/方法。对于较小的更改,您还可以扫描方法的签名:添加参数列表和参数类型并将值返回到池中。更多的工作和更多检测到变化的可能性。

对于一个重要的更改:您可以尝试扫描方法的 ILCode 并检测其中的结构。编译器可能会内联,有时会删除方法/循环/等,但会保留整体结构。特定的代码块在这里或那里执行 n 次,分支在它们的位置但可能交换边等。但是,检测控制结构并不容易,比较代码更加困难。对于某些代码,它可能会给你一个“完全相同”的明确答案,但很多时候你会得到“不一样”,即使它们是。该主题的一些关键字是......重复或抄袭检测器。这就是对此类事物的研究的开始:) 参见 ie。 https://stackoverflow.com/questions/546487/tools-to-identify-code-duplications 虽然我不知道提到的工具是扫描代码还是“字节”..

【讨论】:

    【解决方案4】:

    我们确实设法找到了解决这个问题的方法...我们所做的是添加了一个预构建事件,该事件会遍历一些相关文件(我们更改的文件,例如 .CS 文件)并计算哈希每个的价值。每个散列值最终都会对 DLL 的 global 散列有所贡献。由于我们只有少量文件,因此发生冲突的可能性很小。

    然后我们在 DLL 的描述中添加校验和。这使我们能够在不同的机器上编译 DLL,但由于它们的源相同,因此产生了相同的校验和。

    感谢您提供的所有答案,它们很有帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-06
      • 2015-01-13
      • 2012-09-06
      • 1970-01-01
      相关资源
      最近更新 更多