marginaldeer

char nick[5] = "marg";

char full_nick[13] = "marginaldeer";

printf("https://github.com/%s\n",full_nick);

printf("%s\x40marginaldeer\x2ecom\n",nick);

puts("63DD 76E6 3428 1285 CD8B E3F5 7509 1985 DF70 E945");

Decompiling Java Applications with CFR

Oct 18, 2025 • java,reverse-engineering,decompilation,cfr,pentest

Starting a new series on Java application security. I do a lot of web app assessments and many of them involve Java backends — Spring apps, Tomcat deployments, fat JARs, the works. Being able to decompile and read the source is invaluable for finding vulnerabilities that black-box testing misses.

My go-to decompiler is CFR. It handles modern Java well, produces readable output, and runs from the command line which makes it easy to script. Let’s get into it.

Why Decompile?

When you’re pentesting a Java web app you often have access to:

  • WAR/JAR files from the deployment
  • Class files extracted from a running server
  • Client-side Java applets (increasingly rare but still around)
  • Android APKs (Dalvik bytecode converts back to Java)

Decompilation lets you see the actual business logic, find hardcoded secrets, trace authentication flows, and identify vulnerable code patterns. It’s the difference between guessing and knowing.

Getting CFR

CFR is a single JAR file with no dependencies. Grab it from the official site or use the Maven artifact:

wget https://www.benf.org/other/cfr/cfr-0.152.jar

That’s it. No installation, no setup.

Basic Usage

Decompile a single class file:

java -jar cfr-0.152.jar MyClass.class

Output goes to stdout by default. For a whole JAR:

java -jar cfr-0.152.jar application.jar --outputdir ./decompiled

This extracts and decompiles everything into a nice directory structure matching the package hierarchy.

Dealing with WAR Files

Web applications come packaged as WAR files. These are just ZIP archives with a specific structure:

myapp.war
├── META-INF/
├── WEB-INF/
│   ├── classes/      <- Compiled application code
│   ├── lib/          <- Dependency JARs
│   └── web.xml       <- Deployment descriptor
└── (static files)

The interesting stuff is in WEB-INF/classes and sometimes WEB-INF/lib if the app bundles vulnerable dependencies.

Here’s my workflow:

# Extract the WAR
unzip -q myapp.war -d myapp_extracted

# Decompile the application classes
java -jar cfr-0.152.jar myapp_extracted/WEB-INF/classes --outputdir ./decompiled

# If you need to dig into a dependency
java -jar cfr-0.152.jar myapp_extracted/WEB-INF/lib/some-lib.jar --outputdir ./decompiled_lib

Useful CFR Options

CFR has a ton of options for handling edge cases. These are the ones I use most:

  • --outputdir <path> — Write output to files instead of stdout
  • --caseinsensitivefs true — Helps on Windows where filenames are case-insensitive
  • --silent true — Suppress progress messages
  • --comments false — Skip the CFR comment header in output files
  • --decodestringswitch false — Sometimes helps with obfuscated code
  • --sugarasserts false — Can help with weird assertion patterns

For obfuscated code:

java -jar cfr-0.152.jar obfuscated.jar \
    --outputdir ./decompiled \
    --renameillegalidents true \
    --renamesmallmembers 4

The renameillegalidents flag handles classes with names that aren’t valid Java identifiers (common in obfuscated code).

Reading the Output

CFR produces pretty clean Java. It handles:

  • Lambda expressions
  • Try-with-resources
  • Switch expressions (newer Java)
  • Generics (mostly)
  • Inner classes

Sometimes you’ll see comments like /* synthetic */ or /* bridge */ — these are compiler-generated methods you can usually ignore.

If you see variable names like var1, var2, the original names were stripped. You’ll need to figure out what they represent from context.

A Quick Automation Script

I wrote a simple wrapper for bulk decompilation:

#!/bin/bash
# decompile.sh - Bulk decompile Java artifacts

CFR_JAR="$HOME/tools/cfr-0.152.jar"
OUTPUT_BASE="./decompiled"

for artifact in "$@"; do
    name=$(basename "$artifact" | sed 's/\.[^.]*$//')
    outdir="$OUTPUT_BASE/$name"
    
    echo "[*] Decompiling $artifact -> $outdir"
    
    if [[ "$artifact" == *.war ]]; then
        tmpdir=$(mktemp -d)
        unzip -q "$artifact" -d "$tmpdir"
        java -jar "$CFR_JAR" "$tmpdir/WEB-INF/classes" \
            --outputdir "$outdir" --silent true
        rm -rf "$tmpdir"
    else
        java -jar "$CFR_JAR" "$artifact" \
            --outputdir "$outdir" --silent true
    fi
done

echo "[+] Done"

Usage:

./decompile.sh app1.war app2.jar lib.jar

What to Look For

Once you have decompiled source, you’re looking for security issues. In future posts I’ll cover specific vulnerability patterns, but here’s a quick hit list:

  • Hardcoded credentials — Database passwords, API keys, encryption keys
  • SQL queries — String concatenation instead of prepared statements
  • Deserialization — ObjectInputStream.readObject() calls
  • Path traversal — File operations with user input
  • Command injection — Runtime.exec() or ProcessBuilder with user data
  • XXE — XML parsers without secure configuration
  • SSRF — HTTP clients making requests to user-controlled URLs
  • Weak crypto — MD5, SHA1 for passwords, ECB mode, hardcoded IVs

Grep is your friend:

# Find potential SQL injection
grep -rn "createStatement\|executeQuery\|executeUpdate" ./decompiled

# Find hardcoded passwords
grep -rn -i "password\s*=\s*\"" ./decompiled

# Find deserialization
grep -rn "ObjectInputStream\|readObject" ./decompiled

# Find command execution
grep -rn "Runtime.getRuntime().exec\|ProcessBuilder" ./decompiled

CFR vs Other Decompilers

I’ve tried most of the popular Java decompilers:

  • JD-GUI — GUI-based, good for quick looks but the decompiler is dated
  • Procyon — Solid alternative to CFR, sometimes handles edge cases differently
  • Fernflower — Built into IntelliJ, good quality but harder to use standalone
  • JADX — My go-to for Android APKs, includes dex2jar conversion
  • Krakatau — Handles weird bytecode that breaks other decompilers

I keep coming back to CFR because it’s reliable, actively maintained, and the CLI makes it easy to integrate into workflows. When CFR fails on something I’ll try Procyon or Krakatau.

Tips I’ve Learned

A few things that save time:

  • Keep the original structure — Don’t flatten the package hierarchy. You’ll need it to understand the architecture.
  • Decompile dependencies selectively — Don’t decompile all of WEB-INF/lib unless you need to. Focus on app code first.
  • Compare versions — If you can get multiple versions of an app, diff them to find recent changes (often security fixes).
  • Check web.xml — The deployment descriptor shows URL mappings, filters, and servlets. Good roadmap for the codebase.
  • Look at pom.xml or build.gradle — If included, these show dependencies and versions. Check for known CVEs.

What’s Next

This post covered the basics of getting Java source from compiled artifacts. In the next post we’ll dig into finding SQL injection vulnerabilities through code analysis — building patterns to identify injectable queries even in large codebases.

More to come!

If you enjoyed this post please consider subscribing to the feed!