利用 JVMTI 解密被加密的 class 文件

0x00:JVMTI 加密技术简介

JVMTI JVM Tool Interface 是 Java 虚拟机所提供的 native 编程接口,可以控制 JVM 应用程序的执行。

JVMTI 能够监听 class 加载事件,因此可以使用一套加密算法,对即将发布的字节码进行加密,然后在 JVM 加载这些类之前,使用 native 方式完成 class 的解密。

由于这部分代码最终会以动态库.dll、.so 文件的形式发布出去,动态库还可以另外加壳,不容易被破解,因此对源代码可以达到较好的保护效果。

0x01:分析解密过程

手里拿到一份网站源码,发现 /WEB-INF/classes 路径下的所有 .class 文件都无法被正常反编译,用十六进制编辑器打开,如下图:

熟悉 JVM 字节码结构的话,其实一眼就可以看出来,字节码文件幻数不是 CA FE BA BE ,意味着有程序对 .class 源文件做了额外的加密处理。

翻看 /scripts 目录下的文件,发现 .dll.so 文件,还有几个单独的没有加密 的.jar 文件。然后用自己写的工具 decompiler 全都反编译出源码。

在其中发现 catalina.jar 反编译后有段 org.fiberhome.core.FileCoding.java 代码其实不属于原来的 tomcat

通过查阅资料,可以发现在 static 代码块中定义 System.loadLibrary 是属于典型的利用 JVMTI 技术加密代码的情况。

/scripts 目录下搜索加载的 dll 的关键词 fhcrypt,找到存放 fhcrypt.dll 文件的目录:

跟入代码中的 public static native byte[] decrypt 方法,进入 org/apache/catalina/startup/ContextConfig.java 中,发现核心的解密 class 文件的代码如下:

protected void processAnnotationsFile(File file, WebXml fragment, boolean handlesTypesOnly) {
  int temp;
  if (file.isDirectory()) {
     String[] dirs = file.list();
     String[] var8 = dirs;
     int var7 = dirs.length;

     for(temp = 0; temp < var7; ++temp) {
        String dir = var8[temp];
        this.processAnnotationsFile(new File(file, dir), fragment, handlesTypesOnly);
     }
  } else if (file.canRead() && file.getName().endsWith(".class")) {
     FileInputStream fis = null;
     ByteArrayInputStream fis_new = null;

     try {
        fis = new FileInputStream(file);
        if ((file.getPath().contains("com/fiberhome") || file.getPath().contains("com.fiberhome")) && !file.getName().contains("AppSWVersion")) {
           int temp = false;
           ByteBuffer bs = new ByteBuffer();

           while((temp = fis.read()) != -1) {
              bs.append((byte)temp);
           }

           fis_new = new ByteArrayInputStream(FileCoding.decrypt(bs.toArray()));
           this.processAnnotationsStream(fis_new, fragment, handlesTypesOnly);
        } else {
           this.processAnnotationsStream(fis, fragment, handlesTypesOnly);
        }
     } catch (IOException var23) {
        log.error(sm.getString("contextConfig.inputStreamFile", new Object[]{file.getAbsolutePath()}), var23);
     } catch (ClassFormatException var24) {
        log.error(sm.getString("contextConfig.inputStreamFile", new Object[]{file.getAbsolutePath()}), var24);
     } finally {
        if (fis_new != null) {
           try {
              fis_new.close();
           } catch (Throwable var22) {
              ExceptionUtils.handleThrowable(var22);
           }
        }

        if (fis != null) {
           try {
              fis.close();
           } catch (Throwable var21) {
              ExceptionUtils.handleThrowable(var21);
           }
        }
     }
  }
}

在加载 com.fiberhome 包下的 .class文件时,先通过 FileCoding.decrypt(bs.toArray())进行解密。

那么分析到现在其实就清楚了,直接 System.loadLibrary("fhcrypt"); 调用 FileCoding.decrypt 对加密后的 .class 文件进行解密就行了。

0x02:解密代码

新建一个工程,目录结构如下:

  • /bin 目录拷贝相关的 dll 文件
  • /classes 目录复制原来的 .class 文件被加密的整个目录
  • /resource 目录存放将 classes 目录解密后的所有文件
  • src 下创建 org\fiberhome\core\FileCoding.java 和 org\fiberhome\core\classDecrypt.java

FileCoding.java 全部代码:

注意里面的 /path/to/project/bin 改成实际的存放 dll 文件的目录:

package org.fiberhome.core;

import java.lang.reflect.Field;

public class FileCoding {
    static {
        String lib = System.getProperty("java.library.path");
        lib = "/path/to/project/bin;" + lib;
        System.setProperty("java.library.path", lib);
        try{
            Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
            fieldSysPath.setAccessible(true);
            fieldSysPath.set(null, null);
        }catch (Exception e){
        }
        System.loadLibrary("fhcrypt");
    }

    public FileCoding() {
        super();
    }

    public static native byte[] decrypt(byte[] var0);
}

classDecrypt.java 核心代码:

public static void decrypt(String encrypt_class, String decrypt_class) throws Exception{
    int buff;
    java.io.FileInputStream encrypt_fis = new java.io.FileInputStream(encrypt_class);
    com.sun.corba.se.impl.ior.ByteBuffer encrypt_buf = new com.sun.corba.se.impl.ior.ByteBuffer();
    while ((buff = encrypt_fis.read()) != -1) {
        encrypt_buf.append((byte) buff);
    }
    ByteArrayInputStream decrypt_stream = new ByteArrayInputStream(FileCoding.decrypt(encrypt_buf.toArray()));

    File new_file = new File(decrypt_class);
    File parent = new_file.getParentFile();
    if(!parent.exists()) {
        parent.mkdirs();
    }
    FileOutputStream fos = new FileOutputStream(new_file);
    byte[] bytes = new byte[1024*10];
    while((buff = decrypt_stream.read(bytes))!= -1) {
        fos.write(bytes,0,buff);
    }
    System.out.println("[+] Decrypt to [" + decrypt_class +"] success!");
}

注意事项:

创建的新项目包名要和原来的包名 org.fiberhome.core 一致

参考链接

使用 JVMTI 实现 jar 包字节码加密

标签 

评论