从正在运行的 JVM 中提取类

逆向工程 爪哇 去混淆
2021-06-21 06:21:54

我正在尝试 RE 一个 Java 应用程序,该应用程序使用混淆的加载程序从第二个混淆的类似存档文件加载类。我正在尝试获取这些加载的类以进行进一步分析,但到目前为止,我在确定存档文件的格式方面还没有取得任何成功。由于这些类无论如何都加载到内存中,并且我使用 Java 分析器来获取最有可能包含我正在寻找的特定类和对象的简短列表,有没有办法从内存中拦截这些类,一个核心转储,当它们被加载时,或通过任何其他方式,并检查/保存它们?假设我可以完全控制系统,并且可以以任何方式停止或修改程序。

3个回答

我建议使用 Java 代理从正在运行的 JVM 实例中提取类。代理是一种为应用程序提供检测功能的工具。说到代理,可以通过两种广泛的方式开发它们:

  1. 在纯Java中
  2. 在 C/C++ 中以原生代理的形式存在。

本机代理比纯 Java 代理具有更多功能,但对于您的目的来说已经足够了。在执行流程的某个时刻,类加载器必须加载和解密加密的类。

下面提供了示例代码。代理注册一个回调(transform方法)以在加载新类时收到通知。在回调中,我们只是将类的内容转储到磁盘。

import java.io.*;
import java.lang.instrument.*;
import java.security.*;

public class dumper
{
  //A java agent must have a premain method which acts as the entry-point
  public static void premain(String agentArgs, Instrumentation inst)
  {
    System.out.println("agent loaded");

    // Register our transformer
    inst.addTransformer(new transformer());    
  }
}

class transformer implements ClassFileTransformer
{
   // The transform method is called for each non-system class as they are being loaded  
   public byte[] transform(ClassLoader loader, String className, 
                           Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
                           byte[] classfileBuffer) throws IllegalClassFormatException
   {
     if (className != null)
     {
       // Skip all system classes
       if (!className.startsWith("java") && 
           !className.startsWith("sun") && 
           !className.startsWith("javax") && 
           !className.startsWith ("com") && 
           !className.startsWith("jdk") && 
           !className.startsWith("org"))
       {
         System.out.println("Dumping: " + className);

         // Replace all separator charactors
         String newName = className.replaceAll("/", "#") + ".class";

         try
         {
           FileOutputStream fos = new FileOutputStream(newName);
           fos.write(classfileBuffer);
           fos.close();
         }
         catch (Exception ex)
         {
           System.out.println("Exception while writing: " + newName);
         } 
       }
     }
     // We are not modifying the bytecode in anyway, so return it as-is
     return classfileBuffer;
   }
 }

我在这里写了一篇关于这个过程的文章:Reversing An Obfuscated Java Malware

您可以在运行时使用 HotSpot 工具转储字节码,并使用反编译器来反转字节码。我做了一个概念证明,可在此处获得

它需要 3 个依赖项:

  • 用于转储字节码的 JDK 库(sa-jdi.jar、tools.jar)
  • Fernflower将字节码反编译为 java 代码
  • RSyntaxTextArea显示java源代码

您还可以查看 HSDB 实用程序

我认为最简单的方法是在加载类时转储类,假设您知道混淆的类加载器在哪里并且可以编辑它。

混淆后的类加载器最终将不得不在某个时刻调用 ClassLoader.defineClass。如果你有类文件,你可以将它分解,找到所有对defineClass的调用(或可能调用defineClass的反射方法),并在调用之前插入一个日志函数来转储结果。您可以为此使用Krakatau 反汇编器/汇编器

最大的风险是,如果被混淆的代码使用自省,它可能会把事情搞砸。例如,代码可能会计算自身的校验和,在这种情况下,完全修改它会导致问题。

如果是这种情况,您有两个选择:1) 找到所有内省检查并删除/修改/模拟它们,或者 2) 修改 JVM 本身。后一种方法对混淆代码完全不可见,但更难做到。

要尝试的另一件事是查看 Java 代理界面。我自己没有使用过它,所以我不能说太多,但这可能会让您在运行时访问所有加载的类,而无需修改代码。