0x01

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

image-20260526151949524

image-20260526152123211

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

image-20260526152407559

同时生产环境的启动命令发现没有 -javaagent 参数,说明解密不是通过 Java Agent 实现的,解密只能在 ClassLoader 层面完成

catalina.jar > org.apache.catalina.loader.WebappClassLoaderBase#findClassInternal

image-20260526153228544

第一阶段:对特定框架类(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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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;
}

// 在构造函数中注入解密,确保 Spring 扫描注解时能读取加密 class
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;
}
}
}

// 在 parse() 方法中注入解密,确保 AspectJ 能解析加密 class
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;
}
}
}

// 在 openClassfile() 中注入解密,确保 javassist 运行时能处理加密 class
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.tomcat.util.enc;

public interface CFES {
int OM = -889275714; // 0xCAFEBABE(标准 magic)
int ZM = -889275713; // 0xCAFEBABF(加密 magic)

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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; // 0xCA
r[1] = -2; // 0xFE
r[2] = -70; // 0xBA
r[3] = -66; // 0xBE → CAFEBABE

// 原样复制
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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];
// 如果是加密magic+类型标记就进入解密流程doD,否则oba直接还原magic
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); // 写入标准 magic(0xCAFEBABE)
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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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; // 前4字节:magic number
private byte[] o; // 剩余字节:[类型标记 + 加密数据]
private CFES cfes; // 解密策略(SCFES)

public DCFE(InputStream src, CFES cfes) throws IOException {
this.cfes = cfes;
DataInputStream dis = new DataInputStream(src);

try {
this.m = dis.readInt(); // 读取前4字节作为 magic
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DCFE.IOUtils.copy(dis, baos); // 剩余全部读入
DCFE.IOUtils.closeQuietly(dis, baos);
this.o = baos.toByteArray(); // [0x01, ~byte0, ~byte1, ...]
} catch (IOException e) {
throw e;
}
}

public byte[] de() {
return this.cfes.de(this.m, this.o); // 委托给 SCFES 执行解密
}

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) 标记

image-20260526161917852

算法

解密步骤:

  1. 将magic CAFEBABF还原CAFEBABE
  2. 跳过第5字节(类型标记 0x01)
  3. 剩余每个字节逐位取反(~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
#!/usr/bin/env python3
"""
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] # Unsigned
if magic != ENCRYPTED_MAGIC:
return data # Not encrypted

if len(data) < 5:
return data

t = data[4]
if t != TYPE_BYTE:
return struct.pack('>I', DECRYPTED_MAGIC) + data[4:]

# Result: CAFEBABE + bitwise NOT of data[5:]
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] # Unsigned
if magic != ENCRYPTED_MAGIC:
skipped += 1
continue
decrypted = decrypt_class(data)
# Verify decrypted magic
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()

image-20260526162929161