Skip to main content
Sometimes you need to modify an application’s code to access hidden information, bypass checks, or understand obfuscated logic. This page covers the complete workflow: decompile → modify → recompile → sign.

APK Decompilers

jadx

The recommended choice. Decompiles DEX to readable Java.
jadx app.apk                              # CLI decompilation
jadx app.apk -d ./output --no-res         # No resources
jadx-gui                                  # Launch GUI

JD-Gui

Pioneering GUI Java decompiler. Open the APK directly in JD-Gui to inspect code.

Bytecode-Viewer

Analyze using multiple decompilers simultaneously for cross-verification.

GDA

Windows-only with extensive Android reverse engineering features.

CFR

Handles modern Java features well.
java -jar ./cfr.jar "app.jar" --outputdir "output/"
java -Xmx4G -jar ./cfr.jar "app.jar" --outputdir "output/"

frida-DEXdump

Dumps the DEX of a running APK from memory — bypasses static obfuscation removed at runtime.
Fastest workflow: Use Visual Studio Code with the APKLab extension to automatically decompile, modify, recompile, sign, and install without running any commands. Also useful: apk.sh.

The Smali Workflow

Step 1 — Decompile with apktool

apktool d APP.apk
This gives you Smali code and resources. Key files to inspect:
  • res/values/strings.xml (and all XMLs under res/values/*)
  • AndroidManifest.xml
  • Any .sqlite or .db files
If apktool has trouble decoding, try apktool d APP.apk -r (skip resource decoding).

Step 2 — Modify Smali Code

Smali is the human-readable representation of Dalvik bytecode. Reference for opcodes: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

Hello World Example

Java source:
public static void printHelloWorld() {
    System.out.println("Hello World")
}
Equivalent Smali:
.method public static printHelloWorld()V
    .registers 2
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "Hello World"
    invoke-virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    return-void
.end method

Common Light Modifications

# Modify constant values
const v9, 0xf4240
const/4 v8, 0x1
const-string v5, "wins"

# Math operations
add-int/lit8 v0, v2, 0x1   # v2 + 1, store in v0
mul-int v0,v2,0x2           # v2 * 2, store in v0

# Move values
move v1,v2

# Conditionals
if-ge  # Greater or equals
if-le  # Less or equals
if-eq  # Equals

# Get/set object attributes
iget v0, p0, Lcom/example/GameActivity;->score:I  # this.score → v0
iput v0, p0, Lcom/example/GameActivity;->score:I  # v0 → this.score

# Jumps
:goto_6
if-ne v0, v9, :goto_6
goto :goto_6

Adding Log Output

# Log "wins: <number>"
iget v5, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I
invoke-static {v5}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v1
const-string v5, "wins"
invoke-static {v5, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

Injecting System.loadLibrary() Early

To preload a native library in a static initializer:
.class public Lcom/example/App;
.super Landroid/app/Application;

.method static constructor <clinit>()V
    .registers 1
    const-string v0, "sotap"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    return-void
.end method
Alternatively in Application.onCreate():
.method public onCreate()V
    .locals 1
    const-string v0, "sotap"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    invoke-super {p0}, Landroid/app/Application;->onCreate()V
    return-void
.end method

Step 3 — Recompile

# Run from the folder generated by apktool d
apktool b .
# Output appears in ./dist/

Step 4 — Sign the APK

# Generate a signing key
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 \
        -validity 10000 -alias myalias

# Sign with jarsigner
jarsigner -keystore key.jks path/to/dist/app.apk myalias

# Align with zipalign
zipalign -v 4 infile.apk outfile.apk

# Or sign with apksigner (after zipalign)
apksigner sign --ks key.jks ./dist/mycompiled.apk
Sign either with jarsigner (before zipalign) or with apksigner (after zipalign) — not both. Using both will break the signature.

Understanding Dalvik / ART

  • Android apps are written in Java or Kotlin and compiled to Dalvik Executable (DEX) bytecode
  • The Android Runtime (ART) uses Ahead-of-Time (AOT) compilation of DEX to native code
  • Smali is the assembly language for DEX — the human-readable form
  • baksmali disassembles DEX to Smali; smali assembles Smali back to DEX

References

Build docs developers (and LLMs) love