SuperAPI AI Directory

re-java

Security & auditing

View repository

Updated 10-15 21:34

About this skill

Java bytecode reverse engineering (CFR / JD-GUI / javap)

When to use / when not to use

  • Use: restore Java logic from jar/war/class samples; locate registration codes/keys/network logic; handle ProGuard/Allatori obfuscation and Java hardening
  • Use: static restoration of malicious Java samples ([[re-malware]] → [[re-managed]] path)
  • Do not use: direct analysis of Android DEX ([[re-apk]]; after converting to jar you can return to this skill)
  • Do not use: non-Java (.NET goes to [[re-dotnet]], scripts go to [[re-script-deob]], native goes to [[re-binary-core]])
  • Note: dynamic steps default to sandbox ([[re-analyze/platform-tips]] highest principle); back up unpacking artifacts first

Tool preparation

See [[re-analyze/platform-tips]]—decompilation/unpacking are static steps, no sandbox needed; dynamic verification goes into the sandbox per the highest principle.

JDK (javap / jar / java runtime)

  • Debian/Ubuntu: apt install openjdk-17-jdk
  • Fedora: dnf install java-17-openjdk;Arch: pacman -S jdk17-openjdk
  • macOS: brew install openjdk(or brew install --cask temurin)
  • Windows: choco install temurin or the official installer
  • Verify: java -version && javap -version

unzip (jar = zip container)

  • Linux: apt install unzip / dnf install unzip / pacman -S unzip;macOS: brew install unzip
  • Verify: unzip -v

CFR (jar CLI decompiler, zero dependencies)

  • Download (no dependencies, only needs JRE): curl -L -o cfr.jar https://www.benf.org/other/cfr/cfr-0.152.jar
  • Verify: java -jar cfr.jar --help

JD-GUI (GUI decompiler)

  • GitHub java-decompiler/jd-gui release zip → unzip, Linux/macOS: java -jar jd-gui-1.6.6.jar(the zip contains executables for each platform)
  • Verify: the GUI starts and can File > Open open the jar

procyon (alternative decompiler)

  • Maven Central: curl -L -o procyon-decompiler.jar https://repo1.maven.org/maven2/org/bitbucket/mstrobel/procyon-decompiler/0.6.0/procyon-decompiler-0.6.0.jar
  • Verify: java -jar procyon-decompiler.jar --help

Procedure

Execute in order, recording evidence for each step (path + sha256, see [[re-triage]]).

  1. jar/war unpacking:

    jar tf app.jar                 # 先看清单(JDK 自带 jar 工具)
    unzip -o app.jar -d unpacked/
    unzip -p app.jar META-INF/MANIFEST.MF   # Main-Class / 加固标记
    
    • war: classes are in WEB-INF/classes/, dependencies are in WEB-INF/lib/
    • fat jar (Spring Boot): classes are in BOOT-INF/classes/, nested dependencies BOOT-INF/lib/*.jar need to be unpacked one by one
    • aar (Android): contains classes.jar, after unpacking process it according to this skill
    • Multi-Release jar: multiple versions of classes at the same path are under META-INF/versions/N/(N=9/11/17…) override—at runtime the version is selected and loaded according to the JDK, for reverse engineering look at the corresponding layer for the target JDK, do not just look at the top level
  2. Class structure identification:

    javap -c -p unpacked/com/example/Main.class    # 字节码(-c)+ 私有成员(-p)
    javap -v unpacked/com/example/Main.class       # 常量池/元数据
    
    • Find the entry point: MANIFEST.MF's Main-Class → javap -c -p <入口类> to see the main logic
    • For obfuscated assemblies, first check whether class names are readable (a/b/c → step 4)
    • class file header quick reference:xxd -l 8 <类>.class → CA FE BA BE + minor(2) + major(2); Java version = major − 44 (52=Java 8, 55=11, 61=17, 65=21, measured JDK 21 artifact major=65);javap -v the first line directly prints minor/major
    • Constant pool: the count is after major (u16, index 0 is a placeholder),javap -v lists entry types (String/Class/NameAndType/Methodref/Utf8 etc.)—string literals, class names, and signatures are all in the constant pool, for obfuscated samples look for the plaintext strings here first
  3. Logic restoration (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/   # 备选
    
    • JD-GUI: File > Open → browse the tree on the left → File > Save All Sources export
    • Cross-validate key classes/methods with two decompilers (CFR restores lambda/modern bytecode better, JD-GUI is smoother for old code)
  4. Obfuscation identification and string decryption:

    • ProGuard: class/method names all become a/b/c、javap -l No LineNumberTable/LocalVariableTable (debug info stripped)
    • Allatori: decompiled output shows com.allatori.* watermark classes,StringEncryptor calls (string encryption)
    • ZKM(Zelix KlassMaster): com.zelix.* class signature
    • Detection: grep -rn 'StringEncryptor\|decrypt(' cfr_out/ | head
    • String encryption handling: locate the decryption class and algorithm (key/transformation method) → replicate in python3 for batch recovery; or dynamically obtain plaintext (step 5, inside sandbox) — static recovery first, fall back to dynamic if static stalls
  5. Dynamic (optional; execute in sandbox [[re-sandbox]]):

    • JDB (desktop JVM):
      jdb -classpath app.jar com.example.Main
      > stop in com.example.Main.checkKey     # 下断点
      > print key                             # 取变量
      > eval new com.example.Util().decrypt("密文")   # 直接调用解密方法取明文
      
    • Frida (Android Java apps, switch to [[re-mobile]]/[[re-apk]] domain):
      Java.perform(function () {
        var c = Java.use("com.example.Main");
        c.checkKey.implementation = function (k) {
          console.log("key = " + k);
          return this.checkKey(k);
        };
      });
      
    • Desktop JVM has no Frida Java API → use JDB / a self-written Java agent

Cross-domain combination

  • [[re-managed]]: gateway workflow step ② (decompile) ③ (deobfuscate) always invokes this skill
  • [[re-malware]]: Java malware sample path (re-malware → re-managed → this skill's static recovery)
  • Android side: [[re-apk]] / [[re-mobile]] (after DEX-to-jar conversion, can return to this skill); dynamic [[re-frida]] (Android)
  • Foundation [[re-binary-core]]: initial survey ([[re-triage]]); native/JNI parts

Common pitfalls and traps

  • After ProGuard renaming, rely on string cross-references: symptom — decompiled output is all a/b/c classes,a(...) methods, target logic cannot be located; cause — ProGuard shrink+obfuscate renaming erases semantics; countermeasure — start from strings: grep plaintext URLs/prompts → in the decompiled output find the class that references it (grep -rn "提示语" cfr_out/) → recover semantics along the call chain
  • Allatori/string encryption must be decrypted first: symptom — decompiled output only shows StringEncryptor.decrypt("...") calls, no plaintext visible at all; cause — strings are only decrypted at runtime; countermeasure — statically locate the decryption algorithm and key → replicate in python3 for batch recovery; or dynamically obtain plaintext after the decryption call (JDB eval / Frida), execute inside sandbox
  • Decompilation is not entirely correct: symptom — CFR/JD-GUI output has syntax errors, chaotic goto/label, bizarre try-catch structures, lambda recovery failures; cause — lossless recovery from bytecode to Java does not exist; countermeasure — cross-check against javap -c -p bytecode and fix by hand, cross-validate with multiple decompilers
  • Java hardening (e.g. Virbox) is like a packer and must be unpacked first: symptom — JD-GUI opens with errors/blank,javap output corrupted, non-standard file header; cause — the hardener encrypts class bytecode, decrypted only at runtime (essentially a packer); countermeasure — unpack the hardening first: dump classes at runtime (attach agent / dedicated unpacking tools preferred;-Xbootclasspath only available on JDK 8, removed in JDK 9+) → then decompile the dumped standard classes; the approach is the same as [[re-anti-analysis]]'s "unpack before analysis"
  • javap reports unsupported class file version: symptom —javap reports major version 65 or similar unsupported; cause — the class file version is higher than the local JDK tool version; countermeasure — convert via major−44 to the target Java version, install the corresponding or newer JDK; to just read the version and constant pool you can first use xxd to manually inspect the header fields, no need to wait for tooling
  • Multi-Release jar analyzed at the wrong layer: symptom —unzip -o After decompiling the resolved top-level class, the logic does not match the runtime behavior (the version branch disappears); reason — multi-version classes are in META-INF/versions/N/, and different JDKs load different layers; countermeasure — after unpacking, check META-INF/versions/, and select the layer for analysis according to the target JDK; the difference between the top level and the versions layer is the version-conditional logic
  • Lambda decompilation produces synthetic methods: symptom — CFR/JD-GUI outputs lambda$xxx$n methods or invokedynamic calls; reason — the lambda body is compiled into a synthetic private method + invokedynamic bootstrap; countermeasure — use the BootstrapMethods table ofjavap -v -p to locate the lambda body method (captured variables are in the synthetic method parameters), and analyze it as an ordinary method