处理严重混淆的 Java,可能在字节码级别

逆向工程 混淆 爪哇 去混淆
2021-06-25 20:38:50

我从一个朋友那里得到了一个 jarfile,他告诉我通过反编译器把它放出来。所以我做了,但是:

  • JD-GUI 基本上只吐出导入并没有显示任何类数据
  • CFR 转储了某些堆栈跟踪并创建了基本无法使用的 Java 代码
  • 我什至没有尝试使用 JAD,因为它使用模式匹配进行反编译
  • 根据规范,字节码无效,因为它包含不正确的常量表条目
  • JBE 吐出几个错误并崩溃,fernflower 也是如此

尽管如此,我还是尝试在虚拟机上执行它 - 它有效!看着那里发生的事情,我简直惊呆了,我相信这可能是一段无法反编译的代码片段。我也确实手动分析了类文件,但一切似乎都很矛盾,我最终放弃了。而且,更有趣的是,我尝试将它与从 Java 13 开始到 Java 8 结束的多个 JRE 版本一起使用,它在实践中无处不在,我简直不敢相信

jar 文件

我还上传了CFR 输出(这很有趣)。我对混淆器不太了解,寄给我罐子的人拒绝提供进一步的信息,但我已经获得重新分发和显然反编译它的许可。我得到的最后一点信息是混淆器应该是公开的和免费使用的,但我拒绝相信它(尽管如此,除非我是虚张声势的受害者)。

我该如何解决?到目前为止,jar 文件几乎完全被混淆了,现在存在的所有工具甚至在输出字节码程序集方面都存在严重问题,这是怎么回事?这不是一段非常保密的代码,我很可能会把它扔掉然后忘记它,但我很好奇它是如何工作的,我花了大约两天的时间试图弄清楚。

在此先感谢您的帮助。

2个回答

首先,在处理混淆应用程序时永远不要使用javap,因为javap它不是为处理恶意字节码而设计的,并且很容易以多种方式受阻。幸运的是,Krakatau 反汇编器是专门为处理混淆字节码而设计的,几乎可以处理任何事情。(如果您发现任何在 JVM 上运行且无法反汇编的内容,请提交错误)。幸运的是,在这种情况下,它没有错误地反汇编。

除了字节码反汇编器和汇编器,Krakatau 还包含一个 Java 反编译器,专门用于反编译混淆的字节码。它不是特别用户友好,但它通常可以反编译其他反编译器无法处理的东西。在这种情况下,Krakatau 能够在进行一些小修复后无错误地反编译您的应用程序。

为了使用 Krakatau 反编译您的应用程序,我必须做两件事:

  • 修正了一个Python 2的unicode处理错误('ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)通过改变你得到消息).decode().decode('utf8')
  • 提供带有-path选项的 Spigot 库我必须先稍微修改 Spigot jar,将 bukkit 部分移动到正确的路径(它期望类位于org/bukkit/craftbukkit/entity/CraftPlayer,但该类位于bukkit/craftbukkit/v1_15_R1/entity/CraftPlayer我下载的 Jar 中)。

无论如何,一旦完成,您提供的混淆jar就会反编译而不会出错。请注意,jar 使用了invokedynamic,它通常无法反编译,因为它是一个字节码功能,没有 Java 等效项。为了警告您这一点,Krakatau 在反编译源中输出注释,如下所示

                label12: {
                    try {
                        if (!/*invokedynamic*/false) {
                            break label12;
                        }
                        if (b) {
                            break label12;

在这种情况下,您需要查阅反汇编的字节码以确切了解它在做什么

L14:    iload 6 
L16:    ifne L8 
L19:    invokedynamic InvokeDynamic invokeStatic Method [c24] '\u200c\u2001\u2005\u2001' [u26] : '\ufb05\u58d7\ufb06\u0226\udb3d\u78ef\ufb09\u0226\ufb00\u58d5\udb3d\u221f\udb32' '()L\u2004\u2006\u2007\u2007;' 
L24:    invokedynamic [id154] 
L29:    invokedynamic InvokeDynamic invokeStatic Method [c24] '\u200c\u2001\u2005\u2001' [u26] : [nat157] 
L34:    invokedynamic InvokeDynamic invokeStatic Method [c24] '\u200c\u2001\u2005\u2001' [u26] : '\ufb05\u58d7\ufb06\u0226\udb3d\u78ef\ufb09\u0226\ufb00\u58d5\udb3d\u221f\udb32' '()L\u2004\u2006\u2007\u2007;' 
L39:    new '\u200b\u2007\u2006\u200d' 
L42:    dup 
L43:    aload 4 

就混淆而言,我注意到的主要混淆是 a) 将所有内容重命名为 unicode,以及 b) 插入虚拟字段和一堆在这些字段上分支的假控制流。这是反编译输出中后者的示例:

    boolean b = \u200e\u200a;
    label0: {
        if (b) {
            break label0;
        }
        if (b) {
            break label0;
        }
        \u2005\u200c\u2005\u2009 = new java.util.HashMap();
        if (!b) {
        }
    }

请注意,Krakatau 反编译器进行了大量分析以简化控制流程,因此这是Krakatau 的简化通过剩下的。难怪其他反编译器看到这样复杂的控制流程就会放弃。

还值得注意的是,这是一个相当弱的混淆。特别是,它创建了一个静态字段并在该值上进行了分支,但它实际上从未分配给该字段,因此可以将该字段替换为常量以简化字节码。这让我想起了一些我见过的被 ZKM 混淆的字节码,所以你的朋友可能正在使用 ZKM。

例如,在我修补 jar 以将静态字段替换为常量false并再次反编译之后,Krakatau 的常量传播能够完全简化假控制流。这是将字段替换为与上述相同功能的反编译版本false

static {
    \u2005\u200c\u2005\u2009 = new java.util.HashMap();
}

在将其扔到反编译器之前,您应该使用java-deobfuscator 之类的自动化工具来清理非法名称并删除一些控制流混淆。

这可以通过这个配置来完成(假设你有一个 bukkit jar 方便)

input: unnamedObf.jar
output: unnamedObf-clean.jar
path: [rt.jar, bukkit.jar]
transformers:
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.IllegalSignatureRemover
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.LineNumberRemover
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.LocalVariableRemover
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.SyntheticBridgeRemover
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.PackageNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.ClassNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.FieldNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.MethodNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.general.peephole.PeepholeOptimizer

这是使用 CFR 之前和之后的比较。 应用java-deobfuscator前后反编译对比

它并没有清除所有内容,仍然有大量混淆器生成的动态调用调用和不透明谓词,但它绝对是比原始 jar 更好的起点。