我不是在问一般逆向工程的难度,而是在问对特定假设目标进行逆向工程的难度。我们假设拥有源代码不需要逆向工程,因此是最简单的。目标代码、字节码和机器代码的逆向工程如何比较?混淆将如何影响四种格式中的每一种的难度?我正在寻找试图量化这些比较的答案。
基于目标的逆向工程难度
恕我直言,一般来说,难度顺序:
- 目标代码
- 机器码
- 字节码
我的推理是这样的:
目标代码是最简单的,因为它通常包含大量符号或调试信息。即使您没有一点额外的知识,因为您知道该文件中的所有代码都与一个编译对象相关。了解这一点非常有用,并且有助于分析(除非它是一个巨大的单体程序)。
机器代码是下一个最简单的方法,因为机器架构通常定义得非常好,文档也非常齐全。您所需要的只是一个反汇编器,代码由您支配。
字节码在简单和超难之间有所不同,因为它基本上可以是任何东西。如果不了解为其编写的虚拟机,字节码本身是无用的。例如,JVM 的字节码很容易,因为我们有一个明确定义的 JVM 规范。但是,为 Themida VM 生成的字节码很困难,因为它是随机的,您必须先分析该特定的 VM,然后才能以有意义的方式解释字节码(这本身就很困难)。
混淆几乎总是使分析底层代码变得更加困难。但显然这取决于混淆的程度。有些可以通过简单的脚本轻松绕过,有些可以通过跳过混淆代码(即垃圾代码)来绕过,而有些则非常复杂,需要复杂的模拟器或逐步分析。
如果不设计特定的多标准分析框架,就很难量化这些差异。
只是为了从混淆的角度添加一些东西,我想通过@fileoffset 添加一个很好的答案。
从源代码到生产可执行文件(可能被混淆)的过程中的每个代码转换(例如编译、链接和混淆)都会丢失一些信息,并有选择地添加一些白噪声。此类信息的最简单示例是代码中的注释。最简单的噪声示例是通过 PLT/GOT 的间接调用。
这些信息可以分类如下:
- 整个程序的语义,例如算法、算法执行顺序、模块、修订、注释以及所有这些与所有程序的关系等。这些信息在从程序编译到它所在平台的本机代码时会被删除将在平台不需要时执行(例如,Java、.NET 和 ActionScript 并非总是如此,因为模块和类是平台低级语言的一部分,但相反,这对于编译来说绝对是正确的到 ARM、IA32-64 和其他现实世界的处理器)。只有当此类信息作为平台语言的一部分存在于可执行文件中时,才会被混淆破坏,而这正是 java 混淆器的工作方法之一。
基本算法构建块,例如循环、函数、条件表达式、结构、全局变量、对其他函数的调用、函数序言和结尾,以及您可以在函数中看到的几乎所有其他内容。编译将丢失此信息的重要部分,但可以从上下文中恢复。通过对标准任务使用创造性和非标准模式(例如控制流变化,如控制流展平、循环展开、不需要的堆栈操作等)进行混淆,此类代码工件可能会被严重破坏。 请注意混淆强度这里取决于平台要求。
平台级抽象,例如寄存器、堆栈、单指令、标志等。这可能会被混淆所掩盖,例如基于虚拟机的混淆器只是在运行中发明随机虚拟机。此外,大多数混淆器都在添加不需要的指令。
逆向工程是从代码中恢复丢失信息的过程,如果可能的话,从最低级别到最高级别。该过程基于对标准模式的识别。如果删除标准模式,这个过程会变得更加困难。移除/更改/损坏/模糊/白噪声标准结构的数量或数量级可用作混淆强度的不完整和不良度量。 不幸的是,我不知道其他合理的措施。
让我们尝试从这个角度重新分类您的格式,以便消除混淆所需的工作。
- 源代码
- 目标代码
- 未混淆高级知名平台(如 .NET、ActionScript 和 Java)的生产代码
- .NET、ActionScript 和 Java 等高级知名平台的混淆生产代码
- 未混淆的本机代码
- 像 VM 一样没有Themida 的本地代码混淆
- 像 VM 一样使用Themida混淆的本机代码
检查和证明上述顺序的最佳(也是最有趣的)启发式方法是相应平台的反编译器(它是自动逆向工程工具)的成本、其存在性和质量。
例如,一些 Java 反编译器可以进行自动部分反混淆,并且几乎总是可以在类方法级别重建代码,许可成本为 0 美元
相反,本机代码反编译要么运行得不够好(它们都没有达到 JAD 的质量,非常老的 Java 反编译器)要么成本很高,例如 Hex Rays 反编译器,这是我所知道的唯一稳定工作的反编译器(并且我尝试过)很多其他人)。
顺便说一句,有一些文章试图衡量混淆强度,例如this(该文章对于现实生活中的二进制文件几乎没有用,但参考列表很有趣)。我所知道的没有一个可以处理合理大小的被测可执行文件。他们中的大多数人使用不切实际的假设和/或利用特定的混淆算法缺陷。