Java 類文件結構公開、反編譯容易,傳統混淆 (ProGuard) 僅提高閱讀難度。本文探討三級防護體系:混淆 → 類加載器加殼 → Javassist 運行時字節碼加密,並分析各層防禦深度與破解成本。
-keepattributes Signature,SourceFile,LineNumberTable 保留除錯資訊 (可選)。原理:將 .class 文件加密 (AES/XOR/自定義) 存放,實現 ClassLoader.defineClass 解密後定義類。
public class EncryptedClassLoader extends ClassLoader {
private final SecretKey key;
private final String encryptedPath;
public EncryptedClassLoader(SecretKey key, String path) {
this.key = key; this.encryptedPath = path;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] encrypted = readEncryptedBytes(name); // 從 jar/檔案/網路讀取
byte[] decrypted = decrypt(encrypted, key);
return defineClass(name, decrypted, 0, decrypted.length);
}
}
弱點:解密邏輯與金鑰在 JVM 內存中,javaagent / JVMTI / hsdb / arthas 可在 defineClass 時攔截原始字節碼。需結合 JVM 參數 -XX:+UnlockDiagnosticVMOptions -XX:+LogClassLoading 等對抗,但屬攻防賽跑。
Javassist (Java Programming Assistant) 允許載入時/運行時修改字節碼:插入解密桩、替換方法體、動態生成代理類。
// 啟動時掛載 Java Agent
public class CryptoAgent {
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class> classBeingRedefined, ProtectionDomain pd, byte[] classfileBuffer) {
if (shouldEncrypt(className)) {
CtClass ct = ClassPool.getDefault().makeClass(new ByteArrayInputStream(classfileBuffer));
// 為敏感方法插入解密/校驗邏輯
for (CtMethod m : ct.getDeclaredMethods()) {
if (m.hasAnnotation(Sensitive.class)) {
m.insertBefore("com.mysec.RuntimeDecryptor.decryptAndVerify();");
}
}
return ct.toBytecode();
}
return null;
}
});
}
}
優勢:邏輯分散在運行時,靜態分析極難;可結合硬體綁定 (CPU ID/TPM)、授權服務器心跳動態下發解密金鑰。
| 攻擊者等級 | 可突破層級 | 建議對策 |
|---|---|---|
| 腳本小子 | 混淆 | ProGuard 即可阻擋 90% |
| 熟練逆向工程師 | ClassLoader 加殼 | Javassist + 關鍵邏輯 Server 端化 |
| 國家級/APT | 所有客戶端防護 | 核心 IP 不下發、硬體信任錨 (TEE/SGX)、法律保護 |
--add-opens 僅對信任模組開放。代碼保護沒有銀彈,目標是提高破解成本超過攻擊收益。架構層面「核心不下發」永遠優於技術層面「加殼對抗」。