计划执行与检查节点
executing-plans
按既定实施计划完成工作,保持过程检查和反馈。
re-java【效率与协作】Java 字节码逆向:CFR/JD-GUI、jar 解包、Java 加固。触发词:Java、jar、字节码、JD-GUI、CFR、class文件
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
参考 [[re-analyze/platform-tips]]——反编译/解包为静态步骤,免沙箱;动态验证按最高原则进沙箱。
apt install openjdk-17-jdkdnf install java-17-openjdk;Arch: pacman -S jdk17-openjdkbrew install openjdk(或 brew install --cask temurin)choco install temurin 或官方安装器java -version && javap -versionapt install unzip / dnf install unzip / pacman -S unzip;macOS: brew install unzipunzip -vcurl -L -o cfr.jar https://www.benf.org/other/cfr/cfr-0.152.jarjava -jar cfr.jar --helpjava-decompiler/jd-gui release zip → 解压,Linux/macOS: java -jar jd-gui-1.6.6.jar(zip 内含各平台可执行)File > Open 打开 jarcurl -L -o procyon-decompiler.jar https://repo1.maven.org/maven2/org/bitbucket/mstrobel/procyon-decompiler/0.6.0/procyon-decompiler-0.6.0.jarjava -jar procyon-decompiler.jar --help按顺序执行,每步记录证据(路径 + sha256,见 [[re-triage]])。
jar/war 解包:
jar tf app.jar # 先看清单(JDK 自带 jar 工具)
unzip -o app.jar -d unpacked/
unzip -p app.jar META-INF/MANIFEST.MF # Main-Class / 加固标记
WEB-INF/classes/,依赖在 WEB-INF/lib/BOOT-INF/classes/,嵌套依赖 BOOT-INF/lib/*.jar 需逐个解classes.jar,解出后再按本技能处理META-INF/versions/N/(N=9/11/17…)下覆盖——运行时按 JDK 选版加载,逆向按目标 JDK 看对应层,别只看顶层类结构识别:
javap -c -p unpacked/com/example/Main.class # 字节码(-c)+ 私有成员(-p)
javap -v unpacked/com/example/Main.class # 常量池/元数据
Main-Class → javap -c -p <入口类> 看 main 逻辑xxd -l 8 <类>.class → CA FE BA BE + minor(2) + major(2);Java 版本 = major − 44(52=Java 8、55=11、61=17、65=21,实测 JDK 21 产物 major=65);javap -v 首行直接打印 minor/majorjavap -v 列条目类型(String/Class/NameAndType/Methodref/Utf8 等)——字符串字面量、类名、签名全在常量池,混淆样本的明文串先在这里找逻辑还原(CFR / JD-GUI):
java -jar cfr.jar app.jar --outputdir cfr_out/ # 整包还原为 Java 源码
java -jar cfr.jar unpacked/com/example/Main.class # 单类还原
java -jar procyon-decompiler.jar -jar app.jar -o procyon_out/ # 备选
File > Open → 左侧树浏览 → File > Save All Sources 导出混淆识别与字符串解密:
a/b/c、javap -l 无 LineNumberTable/LocalVariableTable(调试信息被剥)com.allatori.* 水印类、StringEncryptor 调用(字符串加密)com.zelix.* 类特征grep -rn 'StringEncryptor\|decrypt(' cfr_out/ | head动态(可选;沙箱内执行 [[re-sandbox]]):
jdb -classpath app.jar com.example.Main
> stop in com.example.Main.checkKey # 下断点
> print key # 取变量
> eval new com.example.Util().decrypt("密文") # 直接调用解密方法取明文
Java.perform(function () {
var c = Java.use("com.example.Main");
c.checkKey.implementation = function (k) {
console.log("key = " + k);
return this.checkKey(k);
};
});
a/b/c 类、a(...) 方法,无法定位目标逻辑;原因——ProGuard shrink+obfuscate 重命名抹掉语义;对策——从字符串入手:grep 明文 URL/提示语 → 在反编译产物里找引用它的类(grep -rn "提示语" cfr_out/)→ 沿调用链恢复语义StringEncryptor.decrypt("...") 调用,看不到任何明文;原因——字符串运行时才解密;对策——静态定位解密算法与 key → python3 复刻批量还原;或动态在解密调用后取明文(JDB eval / Frida),沙箱内执行javap -c -p 字节码手工修正,多反编译器交叉验证javap 输出损坏、文件头非标准;原因——加固器加密 class 字节码、运行时才解密(本质是壳);对策——先脱加固:运行时 dump class(attach agent / 专用脱壳工具为主;-Xbootclasspath 仅 JDK 8 可用,JDK 9+ 已移除该选项)→ 对脱出的标准 class 再反编译;思路同 [[re-anti-analysis]] 的"先脱壳后分析"javap 报 major version 65 之类不支持;原因——class 文件版本高于本机 JDK 工具版本;对策——按 major−44 换算目标 Java 版本,装对应或更新的 JDK;只读版本与常量池可先用 xxd 手工看头字段,不必等工具unzip -o 解出顶层类后反编译,逻辑与运行时行为不符(版本分支消失);原因——多版本类在 META-INF/versions/N/,不同 JDK 加载不同层;对策——解包后检查 META-INF/versions/,按目标 JDK 选层分析;顶层与 versions 层的差异就是版本条件逻辑lambda$xxx$n 方法或 invokedynamic 调用;原因——lambda 体编译为合成私有方法 + invokedynamic 引导;对策——javap -v -p 的 BootstrapMethods 表定位 lambda 体方法(捕获变量在合成方法参数里),按普通方法分析即可