Smali - Decompilare/[Modificare]/Compilare

Tip

Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Supporta HackTricks

A volte è interessante modificare il codice dell’applicazione per accedere a informazioni nascoste per te (magari password ben offuscate o flags). In questi casi, può essere utile decompilare l’apk, modificare il codice e ricompilarlo.

Riferimento opcodes: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

Metodo rapido

Usando Visual Studio Code e l’estensione APKLab, puoi decompilare automaticamente, modificare, ricompilare, firmare e installare l’applicazione senza eseguire alcun comando.

Un altro script che facilita molto questo compito è https://github.com/ax/apk.sh

Split APKs / App Bundles

I target moderni sono comunemente distribuiti come split APKs (base.apk + split_config.*.apk) invece di un singolo APK monolitico. Se modifichi solo base.apk, le risorse o le librerie native possono andare fuori sincronizzazione e l’installazione potrebbe fallire.

Verifica rapida da un dispositivo:

adb shell pm path com.example.app
adb pull /data/app/.../base.apk
adb pull /data/app/.../split_config.arm64_v8a.apk
adb pull /data/app/.../split_config.en.apk

Se il target è un split package, ricompila l’intero set oppure usa strumenti che uniscano prima gli APK. apk.sh è utile qui perché può combinare split APK in un singolo APK patchabile e correggere gli identificatori delle risorse pubbliche.
Per workflow di repacking orientati a Frida/Objection, vedi anche Android Anti-Instrumentation & SSL Pinning Bypass.

Decompila l’APK

Usando APKTool puoi accedere al codice smali e alle risorse:

apktool d APP.apk

Se apktool ti dà un errore, prova installing the latest version

Alcuni file interessanti da controllare sono:

  • res/values/strings.xml (e tutti gli xml all’interno di res/values/*)
  • AndroidManifest.xml
  • Any file with extension .sqlite or .db

Se apktool ha problemi nel decodificare l’applicazione dai un’occhiata a https://ibotpeaches.github.io/Apktool/documentation/#framework-files oppure prova a usare l’argomento -r (Non decodificare le risorse). Poi, se il problema era in una risorsa e non nel codice sorgente, non avrai il problema (non decompilerai nemmeno le risorse).

Cambia smali code

Puoi modificare le istruzioni, cambiare il valore di alcune variabili o aggiungere nuove istruzioni. Io modifico il codice Smali usando VS Code, poi installi la smalise extension e l’editor ti dirà se qualche istruzione è errata.
Some examples can be found here:

Oppure puoi check below some Smali changes explained.

Ricompilare l’APK

Dopo aver modificato il codice puoi ricompilare il codice usando:

apktool b . #In the folder generated when you decompiled the application

Verrà compilato il nuovo APK all’interno della cartella dist.

Se apktool restituisce un errore, prova a installare l’ultima versione

Firma il nuovo APK

Poi, devi generare una chiave (ti verrà chiesta una password e alcune informazioni che puoi compilare a caso):

keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <your-alias>

Infine, firma il nuovo APK:

jarsigner -keystore key.jks path/to/dist/* <your-alias>

jarsigner funziona ancora per alcuni test rapidi, ma per le build Android moderne apksigner è preferito perché gestisce i più recenti schemi di firma APK.

Ottimizza la nuova applicazione

zipalign è uno strumento di allineamento degli archivi che fornisce importanti ottimizzazioni ai file delle applicazioni Android (APK). More information here.

zipalign [-f] [-v] <alignment> infile.apk outfile.apk
zipalign -v 4 infile.apk

Se l’APK contiene librerie native incluse (lib/*.so), Android ora raccomanda di usare -P 16 in modo che i file .so siano allineati sia per dispositivi con pagina da 16 KiB sia per quelli con pagina da 4 KiB:

zipalign -P 16 -f -v 4 infile.apk outfile.apk

Firma il nuovo APK (di nuovo?)

Se preferisci usare apksigner invece di jarsigner, dovresti firmare l’APK dopo aver applicato l’ottimizzazione con zipalign. MA ATTENZIONE: DEVI FIRMARE L’APPLICAZIONE UNA SOLA VOLTA CON jarsigner (prima di zipalign) O CON apksigner (dopo zipalign).

apksigner sign --ks key.jks ./dist/mycompiled.apk

Un flusso moderno più pratico è:

apktool b . -o dist/app-unsigned.apk
zipalign -P 16 -f -v 4 dist/app-unsigned.apk dist/app-aligned.apk
apksigner sign --ks key.jks --out dist/app-signed.apk dist/app-aligned.apk
apksigner verify --verbose --print-certs dist/app-signed.apk

Note importanti:

  • Se modifichi un APK dopo averlo firmato con apksigner, la firma viene invalidata e devi firmarlo di nuovo.
  • apksigner verify --print-certs è utile per confermare che l’APK ricostruito sia installabile e per ispezionare il certificato che il target esporrà durante l’esecuzione.

Modificare Smali

Per il seguente codice Java Hello World:

public static void printHelloWorld() {
System.out.println("Hello World")
}

Il codice Smali sarebbe:

.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

L’insieme di istruzioni Smali è disponibile here.

Modifiche leggere

Modificare i valori iniziali di una variabile all’interno di una funzione

Alcune variabili sono definite all’inizio della funzione usando l’opcode const, puoi modificare i loro valori, oppure puoi definirne di nuove:

#Number
const v9, 0xf4240
const/4 v8, 0x1
#Strings
const-string v5, "wins"

Operazioni di base

#Math
add-int/lit8 v0, v2, 0x1 #v2 + 0x1 and save it in v0
mul-int v0,v2,0x2 #v2*0x2 and save in v0

#Move the value of one object into another
move v1,v2

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

#Get/Save attributes of an object
iget v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save this.o inside v0
iput v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save v0 inside this.o

#goto
:goto_6 #Declare this where you want to start a loop
if-ne v0, v9, :goto_6 #If not equals, go to: :goto_6
goto :goto_6 #Always go to: :goto_6

Modifiche maggiori

Insidie di Smali che di solito rompono le ricompilazioni

  • Preferisci aumentare .locals quando hai bisogno solo di registri temporanei nel corpo di un metodo esistente. I registri dei parametri (p0, p1…) sono mappati sui registri più alti del metodo, quindi passare ciecamente a .registers spesso rompe il layout degli argomenti.
  • move-result, move-result-wide, and move-result-object must appear immediately after l’invoke-* corrispondente. Inserire logging o qualsiasi altro opcode tra di essi rende il metodo non valido.
  • I valori long e double sono valori wide e occupano una coppia di registri. Se riutilizzi quei registri più tardi, ricorda che v10 occupa anche v11.
  • Se devi passare molti registri, o registri con numeri molto elevati, usa le varianti /range come invoke-virtual/range.

Logging

#Log win: <number>
iget v5, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Get this.o inside v5
invoke-static {v5}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; #Transform number to String
move-result-object v1 #Move to v1
const-string v5, "wins" #Save "win" inside v5
invoke-static {v5, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I #Logging "Wins: <num>"

Raccomandazioni:

  • Se intendi usare variabili dichiarate all’interno della funzione (declared v0,v1,v2…) metti queste righe tra .local e le dichiarazioni delle variabili (const v0, 0x1)
  • Se vuoi inserire il codice di logging nel mezzo del codice di una funzione:
  • Aggiungi 2 al numero di variabili dichiarate: Es: da .locals 10 a .locals 12
  • Le nuove variabili devono essere i numeri successivi rispetto a quelle già dichiarate (in questo esempio dovrebbero essere v10 e v11, ricorda che si parte da v0).
  • Modifica il codice della funzione di logging e usa v10 e v11 al posto di v5 e v1.

Patchare i controlli anti-tamper comuni

Quando un’app viene repacked, una delle prime cose che può rompersi è un controllo in-app di signature / installer / integrity. Buone stringhe da cercare in JADX o nell’albero smali sono:

  • GET_SIGNATURES
  • GET_SIGNING_CERTIFICATES
  • apkContentsSigners
  • MessageDigest
  • SHA-256
  • Base64
  • getInstallerPackageName
  • com.android.vending

Le app moderne spesso chiamano PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES), hashano i byte del signer con MessageDigest, e confrontano il risultato con una costante hardcoded. In pratica, di solito è più semplice patchare il final boolean / branch che riscrivere tutto il codice di gestione delle signature.

Esempi di pattern:

# Force a boolean result to "valid"
const/4 v0, 0x1

# Or invert the branch that sends execution to the tamper handler
if-eqz v0, :tamper_detected   # original
if-nez v0, :tamper_detected   # patched

Se il codice di verifica è rumoroso, cerca l’ultima comparazione prima della dialog di errore / finish() / System.exit() / chiamata di telemetry e applica la patch lì invece di toccare l’intera routine.

Mostrare un toast

Ricorda di aggiungere 3 al numero di .locals all’inizio della funzione.

Questo codice è pronto per essere inserito nel mezzo di una funzione (modifica il numero delle variabili se necessario). Prenderà il valore di this.o, lo trasformerà in String e poi mostrerà un toast con il suo valore.

const/4 v10, 0x1
const/4 v11, 0x1
const/4 v12, 0x1
iget v10, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I
invoke-static {v10}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v11
invoke-static {p0, v11, v12}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v12
invoke-virtual {v12}, Landroid/widget/Toast;->show()V

Caricare una libreria nativa all’avvio (System.loadLibrary)

A volte è necessario caricare in anticipo una libreria nativa in modo che si inizializzi prima di altre librerie JNI (ad es., per abilitare telemetria/registrazione a livello di processo). Puoi iniettare una chiamata a System.loadLibrary() in un initializer statico o all’inizio di Application.onCreate(). Esempio smali per un inizializzatore statico di classe ():

.class public Lcom/example/App;
.super Landroid/app/Application;

.method static constructor <clinit>()V
.registers 1
const-string v0, "sotap"         # library name without lib...so prefix
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
return-void
.end method

In alternativa, inserisci le stesse due istruzioni all’inizio del tuo Application.onCreate() per garantire che la libreria venga caricata il prima possibile:

.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

Note:

  • Assicurati che la variante ABI corretta della libreria esista sotto lib// (es., arm64-v8a/armeabi-v7a) per evitare UnsatisfiedLinkError.
  • Il caricamento molto precoce (class static initializer) garantisce che il native logger possa osservare l’attività JNI successiva.

Smali Static Analysis / Rule-Based Hunting

Dopo il decompilamento con apktool, puoi scansionare Smali riga per riga con regole regex per individuare rapidamente la logica anti-analysis (root/emulator checks) e probabili hardcoded secrets. Questa è una tecnica di fast triage: tratta gli hits come piste che devi verificare nello Smali circostante o nel codice Java/Kotlin ricostruito.

Concetti chiave:

  • Library filtering: sopprimere o taggare i riscontri sotto namespace di terze parti comuni in modo da concentrarti sui code path di proprietà dell’app.
  • Context hints: richiedere che le stringhe sospette compaiano vicino alle API che le consumano (nello stesso metodo, entro N righe).
  • Confidence: usare livelli semplici (high/medium) per classificare gli indizi e ridurre i falsi positivi.

Esempi di prefissi di libreria da sopprimere per impostazione predefinita:

Landroidx/
Lkotlin/
Lkotlinx/
Lcom/google/
Lcom/squareup/
Lokhttp3/
Lokio/
Lretrofit2/

Esempi di regole di rilevamento (regex + euristiche di contesto):

{
"category": "root_check",
"regex_patterns": [
"(?i)invoke-static .*Runtime;->getRuntime\\(\\).*->exec\\(.*\\"(su|magisk|busybox)\\"",
"(?i)const-string [vp0-9, ]+\\"(/system/xbin/su|/system/bin/su|/sbin/su)\\""
],
"context_hint": "Only report when the same method also calls File;->exists/canExecute or Runtime;->exec."
}

Altre euristiche che funzionano bene nella pratica:

  • Root package/path checks: richiedere chiamate vicine a PackageManager;->getPackageInfo o File;->exists per stringhe come com.topjohnwu.magisk o /data/local/tmp.
  • Emulator checks: associare letterali sospetti (es., ro.kernel.qemu, generic, goldfish) con getter Build.* vicini e confronti di stringhe (->equals, ->contains, ->startsWith).
  • Hardcoded secrets: segnalare const-string solo quando un identificatore .field o move-result vicino include parole chiave come password, token, api_key. Ignorare esplicitamente i marker solo per UI come AutofillType, InputType, EditorInfo.

Scanner basati su regole come PulseAPK Core implementano questo modello per far emergere rapidamente logiche anti-analisi e potenziali segreti in Smali.

Riferimenti

Tip

Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Supporta HackTricks