等效于 IDA 函数签名的 Java 字节码

逆向工程 爪哇 字节码
2021-06-24 04:50:30

我记得 IDA(交互式反汇编程序)有一个非常简洁的函数签名特性,您不必对标准库中的代码进行逆向工程。
Java字节码有没有类似的特性,尤其是混淆代码?

2个回答

Java 字节码没有类似的特性。

当您编译 C 程序并将其静态链接到标准库时,库代码将存在,或多或少未修改,在二进制文件中(除了会更改的地址),但不会有任何提示特定的函数在编译之前有一个特定的名称(除非在编译/链接时启用了调试)。但是,任何给定的函数都有或多或少固定的字节模式,通过识别这些字节,IDA 可以为函数分配原始名称。

在 Java 字节码中,没有必要这样做。无论如何,函数名、变量名和类似信息都存在于编译后的字节码中。“标准库”,rt.jar,也没有嵌入到字节码中,所以如果一个类使用ArrayListjava.util.ArrayList即使在混淆过程之后,该类也会有一个引用所以,对于签名分析器来说,这里没有什么可做的。

如果应用程序也选择混淆标准库,则需要将该混淆的库包含在其自己的 jar 文件中。这可能会引起一些许可问题,但除此之外,由于混淆器将重命名方法和字段,该混淆库的字节码将与原始字节码相差太大,无法被 IDA 的 FLIRT 等功能识别。此外,您不能只在 IDA 中重命名该方法,因为您还必须修改所有引用。

但是,至少有一个开源项目与您的问题类似,而且他们似乎已经很好地解决了这个问题。Minecraft是一款流行的游戏,它包含一个用 Java 编写(并经过混淆处理)的服务器;承插项目反编译该服务器,改变了一些东西,增加了一个API,并分发结果。具体来说,为了避免许可问题,他们分发了一个构建系统,该系统可以在用户的​​机器上下载、反编译、修补和重新编译 Minecraft 服务器。

他们使用fernflower反编译器,它可以选择在反编译时重命名符号,并包含一个广泛的混淆到可读符号名称的映射。例子:

AttributeInstance a ()LIAttribute; getAttribute
AttributeInstance a (D)V setValue
AttributeInstance e ()D getValue
AttributeMapServer b ()Ljava/util/Set; getAttributes
AxisAlignedBB b (DDD)LAxisAlignedBB; grow

使用这些地图生成的源代码显然比原始代码更具可读性。而且,至少对于 spigot 项目,重新编译这个源代码(在添加一些补丁以创建 API 之后)会产生一个工作的 Minecraft 服务器。

所以,也许,这可能是您继续的一种方式 - 使用 fernflower 反编译您的类,将它们加载到编辑器中以查找一些有用的代码并分配可读的类名,编写一个映射文件,然后重复几次。然后,当您想要进行一些动态分析时,重新编译反编译的 Java 代码并将其加载到 IDA 中。

当然,这种方式仍然存在一些问题:

  • 您仍然需要手动识别每个函数 - 但正如我所说,无论如何,您可能不会在代码中找到任何标准库函数。而且,无论您使用哪种方法,都没有现有的签名库,因此可能没有办法解决这个问题。
  • 反编译/重新编译的代码可能无法工作,因为 Java 反编译器本身存在错误/缺点
  • 由于混淆器,反编译/重新编译的代码可能无法工作;例如,混淆器可能会用类似的东西替换所有字符串常量Obfuscator::decode("some_crypted_stuff"),其中decode函数使用调用类的名称作为其解密密钥,这意味着当类重命名时解密失败
  • 混淆器可能会带来它自己的类加载器,它会在加载之前修改类名;例如,它可能知道将com.obfuscate.SOME_BASE_64_STRING类转换为解码的 base64 字符串,因此com.obfuscate.amF2YS51dGlsLkFycmF5TGlzdAo.something()会调用java.util.ArrayList.something(). 这打破了调用者和被调用者之间的可见连接(但名称映射可以很好地解决这个问题,你可以自动化很多)

即使您无法获得二进制文件的反编译、可重新编译版本,您也可以编写一些在反编译源代码上运行的代码,使用一些启发式方法通过它们的行为来识别函数,然后,让您的代码编写一个映射表,并编写一个python插件将该映射表作为注释导入到您的代码中。

这些方法是否能胜过 IDA 中的手动逆向工程是有问题的,但由于 IDA 没有此功能,并且没有通用签名库(无论如何它们都不起作用,见上文),这是我必须提供的最好的.

编辑 2016-04-17

结果我有一个小项目,我可以自己使用一些反混淆映射,并更多地检查 Minecraft 反编译步骤的反混淆过程。

结果他们甚至不使用 fernflowers 重新映射功能。相反,他们有自己的一套工具。可悲的是,这些文档记录不足,但似乎有一个GitHub Repository

第一个,SpecialSource.jar,将混淆的 jar 与未混淆的版本进行比较,并生成映射表。当混淆的原始版本的新版本出现时,这似乎允许快速构建新表。但是,没有关于确切比较什么的文件。

第二个,SpecialSource-2.jar直接在 jar 文件中重新映射类和方法引用和名称,没有反编译/编译步骤,因此它避免了随之而来的所有问题。所以一旦你有了一个映射文件,你就可以将它应用到 jar 以获得一个输出 jar,然后你可以将它输入 IDA 或你选择的反编译器。用法是

java -jar /path/to/SpecialSource-2.jar map -m mapping.csrg \
-i obfuscated.jar -o readable.jar

映射文件看起来像这样

# This renames a class, given the obfuscated name and the readable one
com/example/ab/a MyNiceClassName
# This renames a method, given the class, method, signature, and readable name.
MyNiceClassName a (D)I doubleToInt

如果控制流图没有被混淆,那么你可以使用这些来匹配方法。最大的障碍是建立图书馆签名数据库。

控制流图是基本块在被视为有向图时构成的结构。[1] 这些表示方法中可能的执行路径。它们相对容易从已编译的应用程序中解析出来和恢复。大多数 Android 和 Java 混淆都侧重于方法名称而不是控制流。此外,如果方法被修改或移动,Java / Dalvik 字节码可以在编译之间更改。这就是比较结构派上用场的地方,除非进行重大更改,否则控制流程可能会保持不变。

我在 Android 应用程序中做了一些控制流图匹配的工作。[2][3] 该项目的真正优势在于恶意软件菌株聚类。它将匹配结构相似的方法,您可以看到某些应用程序与其他应用程序共享方法,以及菌株如何进化。

如果您希望创建 Java 函数签名,结构匹配和字节基匹配之间的组合将非常强大。

  1. https://en.wikipedia.org/wiki/Control_flow_graph
  2. https://github.com/douggard/CFGScanDroid
  3. http://www.irongeek.com/i.php?page=videos/derbycon4/t420-control-flow-graph-based-virus-scanning-douglas-goddard