0x01
用普通的tomcat启动后报错ClassFormatError: Incompatible magic value 3405691583,idea反编译失败


010打开文件头不对,数据也是一堆乱码,回头重新看了下配套的tomcat,lib下面多了javassist-3.21.0-GA.jar

同时生产环境的启动命令发现没有 -javaagent 参数,说明解密不是通过 Java Agent 实现的,解密只能在 ClassLoader 层面完成
catalina.jar > org.apache.catalina.loader.WebappClassLoaderBase#findClassInternal

第一阶段:对特定框架类(Spring ASM、AspectJ、Javassist)注入解密代码
第二阶段:对所有 class 文件执行解密
0x02 解密类分析
CF类
对特定框架类注入解密代码
tomcat-util.jar > org.apache.tomcat.util.enc.CF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
|
package org.apache.tomcat.util.enc;
import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javassist.ClassClassPath; import javassist.ClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtMethod; import javassist.bytecode.FieldInfo; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory;
public class CF { private static final Log log = LogFactory.getLog(CF.class); private static final List<Ac> acs = Arrays.asList(new Ac1(), new Ac2(), new Ac3()); private String n;
public CF(String n) { this.n = n; }
public byte[] f(byte[] bc) { for(Ac ac : acs) { String t = ac.t(); if (t.equals(this.n)) { return ac.f(bc); } }
return bc; }
private static final class Ac1 implements Ac { private static final String tcn = "org.springframework.asm.ClassReader";
private Ac1() { }
public String t() { return "org.springframework.asm.ClassReader"; }
public byte[] f(byte[] bc) { InputStream oldBinaryContentInputStream = new ByteArrayInputStream(bc); ClassPool pool = new ClassPool(true); CF.CU.l(pool);
try { CtClass cs = pool.makeClass(oldBinaryContentInputStream); CF.FFRU.rf(cs, "header"); CF.FFRU.rf(cs, "maxStringLength"); CF.FFRU.rf(cs, "strings"); CF.FFRU.rf(cs, "items"); CF.FFRU.rf(cs, "b"); CtConstructor ct = cs.getConstructor("([BII)V"); String code = "java.io.ByteArrayInputStream byteArrayInputStream = new java.io.ByteArrayInputStream(b);org.apache.tomcat.util.enc.CFE cfe = new org.apache.tomcat.util.enc.DCFE((java.io.InputStream)byteArrayInputStream,new org.apache.tomcat.util.enc.SCFES());byte[] array = null;try { array = cfe.de(); b = array;} catch (java.lang.Exception e) { e.printStackTrace();}"; ct.insertAt(2, code); byte[] nbc = cs.toBytecode(); return nbc; } catch (Exception e) { e.printStackTrace(); CF.log.error(e); return null; } } }
private static final class Ac2 implements Ac { private static final String tcn = "org.aspectj.apache.bcel.classfile.ClassParser";
private Ac2() { }
public String t() { return "org.aspectj.apache.bcel.classfile.ClassParser"; }
public byte[] f(byte[] bc) { InputStream oldBinaryContentInputStream = new ByteArrayInputStream(bc); ClassPool pool = new ClassPool(true); CF.CU.l(pool);
try { CtClass cs = pool.makeClass(oldBinaryContentInputStream); CF.FFRU.rf(cs, "file"); CtMethod m = cs.getDeclaredMethod("parse"); String code = "org.apache.tomcat.util.enc.CFE cfe = new org.apache.tomcat.util.enc.DCFE((java.io.InputStream)file,new org.apache.tomcat.util.enc.SCFES());byte[] array = null;try { array = cfe.de(); java.io.ByteArrayInputStream byteArrayInputStream = new java.io.ByteArrayInputStream(array); file = new java.io.DataInputStream(byteArrayInputStream);} catch (java.lang.Exception e) { e.printStackTrace();}"; m.insertAt(1, code); byte[] nbc = cs.toBytecode(); return nbc; } catch (Exception e) { e.printStackTrace(); CF.log.error(e); return null; } } }
private static final class Ac3 implements Ac { private static final String tcn = "javassist.ClassPoolTail";
private Ac3() { }
public String t() { return "javassist.ClassPoolTail"; }
public byte[] f(byte[] bc) { InputStream oldBinaryContentInputStream = new ByteArrayInputStream(bc); ClassPool pool = new ClassPool(true); CF.CU.l(pool);
try { CtClass cs = pool.makeClass(oldBinaryContentInputStream); CtMethod m = cs.getDeclaredMethod("openClassfile"); String code = "org.apache.tomcat.util.enc.CFE cfe = new org.apache.tomcat.util.enc.DCFE((java.io.InputStream)ins,new org.apache.tomcat.util.enc.SCFES());;byte[] array = null;try { array = cfe.de(); java.io.ByteArrayInputStream byteArrayInputStream = new java.io.ByteArrayInputStream(array); ins = new java.io.DataInputStream(byteArrayInputStream);} catch (java.lang.Exception e) { e.printStackTrace();}"; String body = "{javassist.ClassPathList list = this.pathList;java.io.InputStream ins = null;javassist.NotFoundException error = null;while (list != null) { try { ins = list.path.openClassfile($1);" + code + " }catch (javassist.NotFoundException e) {" + " if (error == null){" + " error = e;" + " }" + " }" + " if (ins == null){" + " list = list.next;" + " }else{" + " return ins;" + " }" + "}" + "if(error != null){" + " throw error;" + "}else{" + " return null;" + "}" + "}"; m.setBody(body); byte[] nbc = cs.toBytecode(); return nbc; } catch (Exception e) { e.printStackTrace(); CF.log.error(e); return null; } } }
private static final class CU { private static final List<String> list = Arrays.asList("org.apache.tomcat.util.enc.CFE", "org.apache.tomcat.util.enc.DCFE", "org.apache.tomcat.util.enc.SCFES");
public static final void l(ClassPool pool) { List<Class<?>> classes = new ArrayList(3); String result = "";
for(String clazz : list) { Class<?> c = null;
try { c = Class.forName(clazz); classes.add(c); } catch (ClassNotFoundException var7) { result = result + "not found class:" + clazz + "\n\r"; } }
if (!"".equals(result)) { throw new IllegalStateException(result); } else { for(Class<?> c : classes) { ClassPath cp = new ClassClassPath(c); pool.appendClassPath(cp); }
} } }
private static final class FFRU { public static final void rf(CtClass ctClass, String name) { try { CtField field = ctClass.getField(name); FieldInfo fieldInfo = field.getFieldInfo(); int oldFlag = fieldInfo.getAccessFlags(); if ((oldFlag & 16) != 0) { int newFlag = oldFlag & -17; fieldInfo.setAccessFlags(newFlag); } } catch (Exception e) { e.printStackTrace(); CF.log.error(e); }
} }
private interface Ac { String t();
byte[] f(byte[] var1); } }
|
CFES接口
定义常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
package org.apache.tomcat.util.enc;
public interface CFES { int OM = -889275714; int ZM = -889275713;
byte[] de(int var1, byte[] var2); }
|
ACFES类
SCFES类的抽象基类,用于未加密 class 的回退处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
package org.apache.tomcat.util.enc;
public abstract class ACFES implements CFES { protected byte[] oba(int m, byte[] o) { byte[] r = new byte[4 + o.length]; r[0] = -54; r[1] = -2; r[2] = -70; r[3] = -66;
for(int i = 4; i < r.length; ++i) { int oi = i - 4; byte ob = o[oi]; r[i] = ob; }
return r; }
protected abstract byte[] doD(int var1, byte[] var2);
protected abstract byte[] doE(int var1, byte[] var2); }
|
SCFES类
核心解密逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
package org.apache.tomcat.util.enc;
public class SCFES extends ACFES { private byte t = 1;
public byte[] de(int m, byte[] o) { byte t = o[0]; return m == -889275713 && t == this.t ? this.doD(-889275714, o) : this.oba(m, o); }
protected byte[] doD(int om, byte[] o) { byte[] r = new byte[4 + o.length - 1]; r[0] = (byte)(om >> 24 & 255); r[1] = (byte)(om >> 16 & 255); r[2] = (byte)(om >> 8 & 255); r[3] = (byte)(om >> 0 & 255); byte t = o[0]; int srai = 4;
for(int i = srai; i < r.length; ++i) { int oi = i - srai + 1; byte ob = o[oi]; byte nb = (byte)(~ob); r[i] = nb; }
return r; }
protected byte[] doE(int swm, byte[] o) { byte[] r = new byte[5 + o.length]; r[0] = (byte)(swm >> 24 & 255); r[1] = (byte)(swm >> 16 & 255); r[2] = (byte)(swm >> 8 & 255); r[3] = (byte)(swm >> 0 & 255); r[4] = this.t; int srai = 5;
for(int i = srai; i < r.length; ++i) { int oi = i - srai; byte ob = o[oi]; byte nb = (byte)(~ob); r[i] = nb; }
return r; } }
|
DCFE类
主要负责数据读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
|
package org.apache.tomcat.util.enc;
import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream;
public class DCFE implements CFE { private int m; private byte[] o; private CFES cfes;
public DCFE(InputStream src, CFES cfes) throws IOException { this.cfes = cfes; DataInputStream dis = new DataInputStream(src);
try { this.m = dis.readInt(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DCFE.IOUtils.copy(dis, baos); DCFE.IOUtils.closeQuietly(dis, baos); this.o = baos.toByteArray(); } catch (IOException e) { throw e; } }
public byte[] de() { return this.cfes.de(this.m, this.o); }
private static class IOUtils { public static final int EOF = -1; private static final int DEFAULT_BUFFER_SIZE = 4096;
public static int copy(InputStream input, OutputStream output) throws IOException { long count = copyLarge(input, output); return count > 2147483647L ? -1 : (int)count; }
public static long copyLarge(InputStream input, OutputStream output) throws IOException { return copy(input, output, 4096); }
public static long copy(InputStream input, OutputStream output, int bufferSize) throws IOException { return copyLarge(input, output, new byte[bufferSize]); }
public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) throws IOException { long count; int n; for(count = 0L; -1 != (n = input.read(buffer)); count += (long)n) { output.write(buffer, 0, n); }
return count; }
public static void closeQuietly(Closeable... closeables) { if (closeables != null) { for(Closeable closeable : closeables) { closeQuietly(closeable); }
} }
public static void closeQuietly(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException var2) { }
} } }
|
加密数据结构
1 2 3 4
| CA FE BA BF 01 F0 0F EE 11 22 ... ─────────── ─── ───────────────── magic 类型 加密的class数据 (4字节int) 标记
|

算法
解密步骤:
- 将magic CAFEBABF还原CAFEBABE
- 跳过第5字节(类型标记 0x01)
- 剩余每个字节逐位取反(~byte & 0xFF)
0x03 解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| """ ZFSoft class file decryptor. Reverses the encryption: CAFEBABF -> CAFEBABE, skip type byte, bitwise NOT remaining. """ import os import sys import struct
ENCRYPTED_MAGIC = 0xCAFEBABF DECRYPTED_MAGIC = 0xCAFEBABE TYPE_BYTE = 0x01
def decrypt_class(data: bytes) -> bytes: magic = struct.unpack('>I', data[:4])[0] if magic != ENCRYPTED_MAGIC: return data
if len(data) < 5: return data
t = data[4] if t != TYPE_BYTE: return struct.pack('>I', DECRYPTED_MAGIC) + data[4:]
result = bytearray(4 + len(data) - 5) struct.pack_into('>I', result, 0, DECRYPTED_MAGIC) for i in range(5, len(data)): result[4 + i - 5] = (~data[i]) & 0xFF
return bytes(result)
def main(): if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} <classes_dir>") sys.exit(1)
root = sys.argv[1] count = 0 skipped = 0 errors = 0
for dirpath, dirnames, filenames in os.walk(root): for fn in filenames: if not fn.endswith('.class'): continue fpath = os.path.join(dirpath, fn) try: with open(fpath, 'rb') as f: data = f.read() if len(data) < 4: skipped += 1 continue magic = struct.unpack('>I', data[:4])[0] if magic != ENCRYPTED_MAGIC: skipped += 1 continue decrypted = decrypt_class(data) dm = struct.unpack('>I', decrypted[:4])[0] if dm != DECRYPTED_MAGIC: print(f"ERROR: {fpath} - bad decrypted magic {hex(dm)}") errors += 1 continue with open(fpath, 'wb') as f: f.write(decrypted) count += 1 if count % 1000 == 0: print(f" Decrypted {count} files...") except Exception as e: print(f"ERROR: {fpath} - {e}") errors += 1
print(f"\nDone: {count} decrypted, {skipped} skipped, {errors} errors")
if __name__ == '__main__': main()
|
