Smali - Decompiling/[Modifying]/Compiling

Tip

Lerne & übe AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lerne & übe GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lerne & übe Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Durchsuche den vollständigen HackTricks Training-Katalog nach den Assessment-Tracks (ARTA/GRTA/AzRTA) und Linux Hacking Expert (LHE).

Support HackTricks

Manchmal ist es interessant, den Anwendungscode zu verändern, um auf versteckte Informationen zuzugreifen (z. B. stark obfuskierte Passwörter oder Flags). Dann kann es sinnvoll sein, die apk zu dekompilieren, den Code zu ändern und sie wieder zu kompilieren.

Opcodes reference: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

Schneller Weg

Mit Visual Studio Code und der APKLab Extension kannst du die Anwendung automatisch dekompilieren, ändern, rekompilieren, signieren & installieren, ohne einen Befehl auszuführen.

Ein weiteres Script, das diese Aufgabe sehr erleichtert, ist https://github.com/ax/apk.sh

Split APKs / App Bundles

Moderne Targets werden häufig als split APKs (base.apk + split_config.*.apk) ausgeliefert, statt als ein einzelnes monolithisches APK. Wenn du nur base.apk patchst, können Ressourcen oder native Bibliotheken inkonsistent werden und die Installation fehlschlagen.

Schnelle Triage von einem Gerät:

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

Wenn das Ziel ein Split-Paket ist, baue entweder das gesamte Set neu oder verwende Tools, die zuerst die APKs zusammenführen. apk.sh ist hierfür praktisch, da es split APKs zu einer einzigen patchbaren APK kombinieren und öffentliche Ressourcen-IDs korrigieren kann.
Für Frida/Objection-orientierte Repacking-Workflows siehe auch Android Anti-Instrumentation & SSL Pinning Bypass.

APK dekompilieren

Mit APKTool kannst du auf den smali code and resources zugreifen:

apktool d APP.apk

Wenn apktool dir einen Fehler meldet, versuche installing the latest version

Einige interessante Dateien, die du dir ansehen solltest, sind:

  • res/values/strings.xml (und alle XMLs in res/values/*)
  • AndroidManifest.xml
  • Jede Datei mit der Endung .sqlite oder .db

Wenn apktool Probleme beim Decodieren der Anwendung hat, schau dir https://ibotpeaches.github.io/Apktool/documentation/#framework-files an oder versuche das Argument -r zu verwenden (Ressourcen nicht dekodieren). Wenn das Problem in einer Ressource und nicht im Quellcode lag, tritt das Problem dann nicht auf (du wirst außerdem die Ressourcen nicht dekompilieren).

Smali-Code ändern

Du kannst Anweisungen ändern, den Wert einiger Variablen ändern oder neue Anweisungen hinzufügen. Ich bearbeite den Smali-Code mit VS Code, installiere dann die smalise extension und der Editor zeigt dir, wenn eine Anweisung inkorrekt ist.
Einige Beispiele findest du hier:

Oder du kannst check below some Smali changes explained.

APK neu kompilieren

Nachdem du den Code geändert hast, kannst du den Code mit folgendem Befehl neu kompilieren:

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

Die neue APK wird kompiliert im dist-Ordner.

Wenn apktool einen Fehler wirft, versuche die neueste Version zu installieren

Die neue APK signieren

Anschließend musst du einen Key generieren (du wirst nach einem Passwort und nach einigen Informationen gefragt, die du zufällig ausfüllen kannst):

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

Zum Schluss, sign die neue APK:

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

jarsigner funktioniert noch für einige schnelle Tests, aber für moderne Android-Builds wird apksigner bevorzugt, da es die neueren APK-Signatur-Schemata unterstützt.

Neue Anwendung optimieren

zipalign ist ein Tool zur Archiv-Ausrichtung, das wichtige Optimierungen für Android-Anwendungsdateien (APK) vornimmt. Weitere Informationen.

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

Wenn das APK gebündelte native Bibliotheken (lib/*.so) enthält, empfiehlt Android jetzt die Verwendung von -P 16, damit die .so-Dateien sowohl für Geräte mit einer Seitengröße von 16 KiB als auch für solche mit 4 KiB ausgerichtet sind:

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

Signiere die neue APK (nochmal?)

Wenn du es vorziehst, apksigner anstelle von jarsigner zu verwenden, solltest du die apk signieren, nachdem du die Optimierung mit zipaling angewendet hast. ABER BEACHTE, DASS DU DIE ANWENDUNG NUR EINMAL SIGNIEREN MUSST MIT jarsigner (vor zipalign) ODER MIT aspsigner (nach zipaling).

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

Ein praktischeres, moderneres Vorgehen ist:

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

Wichtige Hinweise:

  • Wenn Sie ein APK nach dem Signieren mit apksigner modifizieren, wird die Signatur ungültig und Sie müssen es erneut signieren.
  • apksigner verify --print-certs ist nützlich, um zu bestätigen, dass das neu aufgebaute APK installierbar ist, und um das Zertifikat zu inspizieren, das das Ziel zur Laufzeit offenlegen wird.

Ändern von Smali

Für den folgenden Hello World Java-Code:

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

Der Smali-Code würde wie folgt aussehen:

.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

Der Smali-Instruktionssatz ist hier verfügbar.

Leichte Änderungen

Initialwerte einer Variablen innerhalb einer Funktion ändern

Einige Variablen werden zu Beginn der Funktion mit dem Opcode const definiert; du kannst deren Werte ändern oder neue definieren:

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

Grundlegende Operationen

#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

Größere Änderungen

Smali-Fallstricke, die meist Builds zum Scheitern bringen

  • Erhöhe vorzugsweise .locals, wenn du nur temporäre Register im Körper einer bestehenden Methode benötigst. Parameter-Register (p0, p1…) sind den höheren Registern der Methode zugeordnet, deshalb bricht ein blindes Wechseln zu .registers oft das Argumentlayout.
  • move-result, move-result-wide und move-result-object müssen unmittelbar nach dem passenden invoke-* erscheinen. Das Einfügen von logging oder einem anderen Opcode dazwischen macht die Methode ungültig.
  • long und double Werte sind wide values und belegen ein Registerpaar. Wenn du diese Register später wiederverwendest, beachte, dass v10 auch v11 belegt.
  • Wenn du viele Register oder sehr hoch nummerierte übergeben musst, verwende die /range-Varianten, z. B. 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>"

Empfehlungen:

  • Wenn du deklarierte Variablen innerhalb der Funktion verwenden willst (deklariert v0,v1,v2…) setze diese Zeilen zwischen der .local und der Deklaration der Variablen (const v0, 0x1)
  • Wenn du den logging code mitten im Code einer Funktion einfügen möchtest:
  • Erhöhe die Anzahl deklarierter Variablen um 2: Ex: von .locals 10 zu .locals 12
  • Die neuen Variablen sollten die nächsten Nummern nach den bereits deklarierten Variablen sein (in diesem Beispiel sollten das v10 und v11 sein, denk daran, dass es bei v0 beginnt).
  • Ändere den Code der logging-Funktion und verwende v10 und v11 anstelle von v5 und v1.

Häufige Anti-Tamper-Checks patchen

Wenn eine App neu verpackt wird, gehört eine der ersten Sachen, die möglicherweise kaputtgehen, zu einer In-App-Signatur-/Installer-/Integritätsprüfung. Gute Strings, nach denen man in JADX oder im smali-Tree suchen kann, sind:

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

Moderne Apps rufen oft PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES) auf, hash’en die Signer-Bytes mit MessageDigest und vergleichen das Ergebnis mit einer hardcodierten Konstanten. In der Praxis ist es meist einfacher, das finale boolean / branch zu patchen, als den gesamten Signatur-Handling-Code umzuschreiben.

Example patterns:

# 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

Wenn der Verifikationscode unübersichtlich ist, suche nach dem letzten Vergleich vor dem Fehlerdialog / finish() / System.exit() / Telemetrie-Aufruf und patch dort, anstatt die gesamte Routine zu verändern.

Toast anzeigen

Erhöhe die Anzahl der .locals am Anfang der Funktion um 3.

Dieser Code ist dafür vorbereitet, in die Mitte einer Funktion eingefügt zu werden (ändere die Anzahl der Variablen nach Bedarf). Er nimmt den Wert von this.o, wandelt ihn in ein String um und erstellt dann einen toast mit dessen Wert.

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

Laden einer nativen Library beim Start (System.loadLibrary)

Manchmal muss man eine native Library vorladen, damit sie sich vor anderen JNI-Libs initialisiert (z. B. um prozesslokale Telemetrie/Logging zu ermöglichen). Du kannst einen Aufruf von System.loadLibrary() in einen statischen Initializer injizieren oder früh in Application.onCreate(). Beispiel-smali für einen statischen Klasseninitializer ():

.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

Alternativ platziere dieselben zwei Anweisungen am Beginn deiner Application.onCreate(), um sicherzustellen, dass die Bibliothek so früh wie möglich geladen wird:

.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

Hinweise:

  • Stellen Sie sicher, dass die korrekte ABI-Variante der Bibliothek unter lib// (z. B. arm64-v8a/armeabi-v7a) vorhanden ist, um UnsatisfiedLinkError zu vermeiden.
  • Frühes Laden (class static initializer) gewährleistet, dass der native logger nachfolgende JNI-Aktivität beobachten kann.

Smali Static Analysis / Rule-Based Hunting

Nach dem Decompilieren mit apktool können Sie Smali Zeile für Zeile scannen mit regex rules, um schnell anti-analysis logic (root/emulator checks) und wahrscheinlich hardcoded secrets zu entdecken. Dies ist eine fast triage-Technik: Behandeln Sie Treffer als Hinweise, die Sie im umgebenden Smali oder im rekonstruierten Java/Kotlin verifizieren müssen.

Key ideas:

  • Library filtering: Unterdrücken oder Kennzeichnen von Funden in gängigen Third-Party-Namespaces, damit Sie sich auf app-eigene Codepfade konzentrieren.
  • Context hints: verlangen, dass verdächtige Strings in der Nähe der APIs erscheinen, die sie verwenden (innerhalb derselben Methode, innerhalb von N Zeilen).
  • Confidence: verwenden Sie einfache Stufen (high/medium), um Leads zu priorisieren und False Positives zu reduzieren.

Example library prefixes to suppress by default:

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

Beispiel-Erkennungsregeln (regex + Kontextheuristiken):

{
"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."
}

Weitere Heuristiken, die sich in der Praxis bewährt haben:

  • Root-Paket-/Pfad-Prüfungen: erfordern nahegelegene Aufrufe von PackageManager;->getPackageInfo oder File;->exists für Strings wie com.topjohnwu.magisk oder /data/local/tmp.
  • Emulator-Prüfungen: kombiniere verdächtige Literale (z. B. ro.kernel.qemu, generic, goldfish) mit nahegelegenen Build.*-Getter-Aufrufen und String-Vergleichen (->equals, ->contains, ->startsWith).
  • Hartkodierte Geheimnisse: markiere const-string nur, wenn ein nahegelegenes .field- oder move-result-Identifier Schlüsselwörter wie password, token, api_key enthält. UI-only Marker wie AutofillType, InputType, EditorInfo explizit ignorieren.

Regelbasierte Scanner wie PulseAPK Core setzen dieses Modell ein, um Anti-Analyse-Logik und potenzielle Geheimnisse in Smali schnell aufzudecken.

Referenzen

Tip

Lerne & übe AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lerne & übe GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lerne & übe Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Durchsuche den vollständigen HackTricks Training-Katalog nach den Assessment-Tracks (ARTA/GRTA/AzRTA) und Linux Hacking Expert (LHE).

Support HackTricks