Smali - Dekompilacja/[Modyfikacja]/Kompilacja
Tip
Ucz się i ćwicz AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Przeglądaj pełny katalog HackTricks Training dla ścieżek assessment (ARTA/GRTA/AzRTA) oraz Linux Hacking Expert (LHE).
Wsparcie HackTricks
- Sprawdź plany subskrypcji!
- Dołącz do 💬 grupy Discord, grupy telegram, obserwuj @hacktricks_live na X/Twitter, albo sprawdź stronę LinkedIn i kanał YouTube.
- Dziel się hacking tricks, wysyłając PR do repozytoriów github HackTricks i HackTricks Cloud.
Czasami warto zmodyfikować kod aplikacji, aby uzyskać dostęp do ukrytych informacji (może dobrze obfuskowane hasła lub flags). Wtedy opłaca się zdekompilować apk, zmienić kod i ponownie go skompilować.
Referencja opcode’ów: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
Szybki sposób
Używając Visual Studio Code i rozszerzenia APKLab, możesz automatycznie zdekompilować, zmodyfikować, ponownie skompilować, podpisać i zainstalować aplikację bez wykonywania żadnego polecenia.
Inny skrypt, który bardzo ułatwia to zadanie, to https://github.com/ax/apk.sh
Split APKs / App Bundles
Nowoczesne cele są zwykle dostarczane jako split APKs (base.apk + split_config.*.apk) zamiast jednego monolitycznego APK. Jeśli załatasz tylko base.apk, zasoby lub biblioteki natywne mogą się rozjechać i instalacja może się nie powieść.
Szybkie sprawdzenie z urządzenia:
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
Jeśli docelowy pakiet jest split package, przebuduj cały zestaw albo użyj narzędzia, które najpierw łączy APKs. apk.sh jest tu przydatne, ponieważ potrafi połączyć split APKs w pojedynczy APK możliwy do patchowania i naprawić public resource identifiers.
Dla workflowów repackingu ukierunkowanych na Frida/Objection sprawdź także Android Anti-Instrumentation & SSL Pinning Bypass.
Dekompilacja APK
Używając APKTool możesz uzyskać dostęp do kodu smali i zasobów:
apktool d APP.apk
Jeśli apktool zwróci jakiś błąd, spróbuj installing the latest version
Some interesting files you should look are:
- res/values/strings.xml (i wszystkie pliki xml w res/values/*)
- AndroidManifest.xml
- Każdy plik z rozszerzeniem .sqlite lub .db
If apktool has problems decoding the application zajrzyj na https://ibotpeaches.github.io/Apktool/documentation/#framework-files lub spróbuj użyć argumentu -r (Nie dekoduj zasobów). Wtedy, jeśli problem był w zasobie, a nie w kodzie źródłowym, nie będziesz mieć problemu (nie zdekompilujesz też zasobów).
Zmień kod smali
Możesz zmieniać instrukcje, zmieniać wartość niektórych zmiennych lub dodawać nowe instrukcje. Zmieniam kod Smali używając VS Code, następnie instalujesz smalise extension i edytor powie ci, jeśli jakaś instrukcja jest niepoprawna.
Niektóre przykłady można znaleźć tutaj:
Lub możesz check below some Smali changes explained.
Przekompiluj APK
Po zmodyfikowaniu kodu możesz przekompilować kod używając:
apktool b . #In the folder generated when you decompiled the application
Skompiluje nowy APK wewnątrz folderu dist.
Jeśli apktool zgłasza błąd, spróbuj zainstalować najnowszą wersję
Podpisz nowy APK
Następnie musisz wygenerować klucz (zostaniesz poproszony o hasło oraz o pewne informacje, które możesz wypełnić losowo):
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <your-alias>
Na koniec, podpisz nowy APK:
jarsigner -keystore key.jks path/to/dist/* <your-alias>
jarsigner wciąż działa dla szybkich testów, ale dla nowoczesnych buildów Androida apksigner jest preferowany, ponieważ obsługuje nowsze schematy podpisywania APK.
Optymalizuj nową aplikację
zipalign to narzędzie do wyrównywania archiwów, które zapewnia istotną optymalizację plików aplikacji Android (APK). Więcej informacji tutaj.
zipalign [-f] [-v] <alignment> infile.apk outfile.apk
zipalign -v 4 infile.apk
Jeśli APK zawiera dołączone biblioteki natywne (lib/*.so), Android teraz zaleca użycie -P 16, aby pliki .so były wyrównane zarówno dla urządzeń z rozmiarem strony 16 KiB, jak i 4 KiB:
zipalign -P 16 -f -v 4 infile.apk outfile.apk
Podpisz nowe APK (znowu?)
Jeśli wolisz użyć apksigner zamiast jarsigner, powinieneś podpisać APK po zastosowaniu optymalizacji za pomocą zipaling. ALE ZWRÓĆ UWAGĘ, ŻE MUSISZ PODPISAĆ APLIKACJĘ TYLKO RAZ przy użyciu jarsigner (przed zipalign) LUB przy użyciu aspsigner (po zipaling).
apksigner sign --ks key.jks ./dist/mycompiled.apk
Bardziej praktyczny, nowoczesny przebieg wygląda następująco:
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
Ważne uwagi:
- Jeśli zmodyfikujesz APK po podpisaniu go za pomocą
apksigner, podpis zostanie unieważniony i musisz podpisać go ponownie. apksigner verify --print-certsjest przydatne do potwierdzenia, że przebudowane APK jest możliwe do zainstalowania oraz do sprawdzenia certyfikatu, który aplikacja docelowa ujawni w czasie działania.
Modyfikowanie Smali
Dla następującego kodu Java ‘Hello World’:
public static void printHelloWorld() {
System.out.println("Hello World")
}
Kod Smali wyglądałby następująco:
.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
Zestaw instrukcji Smali jest dostępny tutaj.
Niewielkie zmiany
Modyfikacja wartości początkowych zmiennej wewnątrz funkcji
Niektóre zmienne są zdefiniowane na początku funkcji przy użyciu opcode const; możesz zmodyfikować ich wartości lub zdefiniować nowe:
#Number
const v9, 0xf4240
const/4 v8, 0x1
#Strings
const-string v5, "wins"
Podstawowe operacje
#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
Większe zmiany
Pułapki Smali, które zwykle psują przebudowy
- Prefer increasing
.localsgdy potrzebujesz tylko tymczasowych rejestrów w ciele istniejącej metody. Rejestry parametrów (p0,p1…) są mapowane na najwyższe rejestry metody, więc bezmyślne przejście na.registersczęsto psuje układ argumentów. move-result,move-result-wide, imove-result-objectmuszą występować bezpośrednio po odpowiadającyminvoke-*. Wstawienie logowania lub dowolnego innego opcode pomiędzy nimi sprawia, że metoda jest nieprawidłowa.- Wartości
longidoublesą wartościami wide i zajmują parę rejestrów. Jeśli ponownie używasz tych rejestrów później, pamiętaj, żev10zajmuje teżv11. - Jeśli musisz przekazać wiele rejestrów lub bardzo wysoko ponumerowanych, użyj wariantów
/range, takich jakinvoke-virtual/range.
Logowanie
#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>"
Recommendations:
- Jeśli zamierzasz użyć zadeklarowanych zmiennych wewnątrz funkcji (declared v0,v1,v2…) umieść te linie pomiędzy .local
i deklaracjami zmiennych (const v0, 0x1) - Jeśli chcesz umieścić kod logowania w środku ciała funkcji:
- Dodaj 2 do liczby zadeklarowanych zmiennych: Ex: from .locals 10 to .locals 12
- Nowe zmienne powinny być kolejnymi numerami po już zadeklarowanych zmiennych (w tym przykładzie powinny to być v10 i v11, pamiętaj, że numeracja zaczyna się od v0).
- Zmień kod funkcji logującej i użyj v10 i v11 zamiast v5 i v1.
Modyfikowanie typowych kontroli antymanipulacyjnych
Gdy aplikacja jest repakowana, jedną z pierwszych rzeczy, które mogą przestać działać, jest wbudowana kontrola podpisu / instalatora / integralności. Dobre ciągi do wyszukania w JADX lub w drzewie smali to:
GET_SIGNATURESGET_SIGNING_CERTIFICATESapkContentsSignersMessageDigestSHA-256Base64getInstallerPackageNamecom.android.vending
Nowoczesne aplikacje często wywołują PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES), haszują bajty podpisującego przy użyciu MessageDigest i porównują wynik ze stałą zakodowaną w kodzie. W praktyce zwykle łatwiej jest załatać final boolean / branch niż przepisać cały kod obsługi podpisów.
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
Jeśli kod weryfikacji jest noisy (generuje dużo sprawdzeń), poszukaj ostatniego porównania przed oknem błędu / finish() / System.exit() / wywołaniem telemetry i zapatchuj tam zamiast modyfikować całą rutynę.
Toasting
Pamiętaj, aby dodać 3 do liczby .locals na początku funkcji.
Ten kod jest przygotowany do wstawienia w środek funkcji (zmień liczbę zmiennych w razie potrzeby). Pobierze wartość this.o, konwertuje ją do String i następnie wyświetli toast z jej wartością.
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
Ładowanie natywnej biblioteki przy uruchamianiu (System.loadLibrary)
Czasami trzeba wstępnie załadować natywną bibliotekę, aby została zainicjalizowana przed innymi bibliotekami JNI (np. aby włączyć telemetryę/logowanie lokalne dla procesu). Możesz wstrzyknąć wywołanie System.loadLibrary() w statycznym inicjalizatorze lub wcześnie w Application.onCreate(). Przykładowy smali dla statycznego inicjalizatora klasy (
.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
Alternatywnie, umieść te same dwie instrukcje na początku Application.onCreate(), aby zapewnić, że biblioteka załaduje się jak najwcześniej:
.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
Uwagi:
- Upewnij się, że poprawny wariant ABI biblioteki znajduje się w lib/
/ (np. arm64-v8a/armeabi-v7a), aby uniknąć UnsatisfiedLinkError. - Ładowanie bardzo wcześnie (class static initializer) gwarantuje, że native logger może obserwować następującą aktywność JNI.
Analiza statyczna Smali / wykrywanie oparte na regułach
Po dekompilacji za pomocą apktool, możesz skanować Smali linia po linii przy użyciu reguł regex, aby szybko wyłapać logikę anty-analizy (sprawdzenia root/emulator) i potencjalnie hardcoded secrets. To technika szybkiej triage: traktuj trafienia jako wskazówki, które musisz zweryfikować w otaczającym Smali lub zrekonstruowanym Java/Kotlin.
Kluczowe pomysły:
- Library filtering: tłumić lub oznaczać wyniki w obrębie powszechnych third-party namespace’ów, żeby skupić się na ścieżkach kodu należących do aplikacji.
- Context hints: wymagać, aby podejrzane ciągi pojawiały się blisko API, które ich konsumuje (w tej samej metodzie, w obrębie N linii).
- Confidence: stosować proste poziomy (wysoki/średni) do priorytetyzacji wskazówek i zmniejszenia fałszywych pozytywów.
Przykładowe prefiksy bibliotek do domyślnego tłumienia:
Landroidx/
Lkotlin/
Lkotlinx/
Lcom/google/
Lcom/squareup/
Lokhttp3/
Lokio/
Lretrofit2/
Przykładowe reguły wykrywania (regex + heurystyka kontekstowa):
{
"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."
}
Dodatkowe heurystyki, które dobrze działają w praktyce:
- Root package/path checks: wymagaj pobliskich wywołań
PackageManager;->getPackageInfolubFile;->existsdla stringów takich jakcom.topjohnwu.magisklub/data/local/tmp. - Emulator checks: paruj podejrzane literały (np.
ro.kernel.qemu,generic,goldfish) z pobliskimi getteramiBuild.*i porównaniami stringów (->equals,->contains,->startsWith). - Hardcoded secrets: oznacz
const-stringtylko gdy pobliski.fieldlub identyfikatormove-resultzawiera słowa kluczowe takie jakpassword,token,api_key. Jawnie ignoruj markery tylko UI, takie jakAutofillType,InputType,EditorInfo.
Skannery sterowane regułami, takie jak PulseAPK Core, implementują ten model, aby szybko uwidocznić anti-analysis logic i potencjalne secrets w Smali.
Referencje
- PulseAPK Core
- PulseAPK Smali Detection Rules
- SoTap: Lekki logger zachowania JNI (.so) w aplikacji – github.com/RezaArbabBot/SoTap
- Android Developers: apksigner i zipalign
- apk.sh: github.com/ax/apk.sh
Tip
Ucz się i ćwicz AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Przeglądaj pełny katalog HackTricks Training dla ścieżek assessment (ARTA/GRTA/AzRTA) oraz Linux Hacking Expert (LHE).
Wsparcie HackTricks
- Sprawdź plany subskrypcji!
- Dołącz do 💬 grupy Discord, grupy telegram, obserwuj @hacktricks_live na X/Twitter, albo sprawdź stronę LinkedIn i kanał YouTube.
- Dziel się hacking tricks, wysyłając PR do repozytoriów github HackTricks i HackTricks Cloud.


