使用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
一致
评论