Smali - Dekomplovanje/[Modifikovanje]/Kompajliranje

Tip

Nauči i vežbaj AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Nauči i vežbaj GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Nauči i vežbaj Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Pregledaj kompletan HackTricks Training katalog za assessment tracks (ARTA/GRTA/AzRTA) i Linux Hacking Expert (LHE).

Podrži HackTricks

Ponekad je interesantno izmeniti kod aplikacije da biste pristupili skrivenim informacijama (možda dobro obfuskisanim lozinkama ili flagovima). Zbog toga može biti korisno dekompilovati apk, izmeniti kod i ponovo ga kompajlirati.

Referenca opkodova: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

Brzi način

Koristeći Visual Studio Code i ekstenziju APKLab, možete automatski dekompilovati, izmeniti, ponovo kompilovati, potpisati & instalirati aplikaciju bez izvršavanja bilo koje komande.

Još jedna skripta koja mnogo olakšava ovaj zadatak je https://github.com/ax/apk.sh

Split APKs / App Bundles

Savremeni ciljevi se obično isporučuju kao split APKs (base.apk + split_config.*.apk) umesto jednog monolitnog APK-a. Ako izmenite samo base.apk, resursi ili native biblioteke mogu postati neusklađeni i instalacija može da ne uspe.

Brza trijaža sa uređaja:

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

Ako je cilj split package, ili ponovo izgradite ceo skup ili koristite alate koji prvo spajaju APK-ove. apk.sh je koristan ovde jer može da kombinuje split APK-ove u jedan APK koji se može patchovati i ispravi javne identifikatore resursa.
Za repacking tokove rada orijentisane na Frida/Objection, pogledajte i Android Anti-Instrumentation & SSL Pinning Bypass.

Dekompajlirajte APK

Koristeći APKTool možete pristupiti smali kodu i resursima:

apktool d APP.apk

Ako apktool prijavi bilo koju grešku, pokušajte installing the latest version

Some interesting files you should look are:

  • res/values/strings.xml (i sve xml fajlove unutar res/values/*)
  • AndroidManifest.xml
  • Bilo koja datoteka sa ekstenzijom .sqlite ili .db

Ako apktool ima probleme pri dekodiranju aplikacije, pogledajte https://ibotpeaches.github.io/Apktool/documentation/#framework-files ili pokušajte da koristite argument -r (Ne dekodiraj resurse). Zatim, ako je problem bio u resursu, a ne u source code-u, nećete više imati problem (takođe nećete dekompajlirati resurse).

Izmeni Smali code

Možete promeniti instrukcije, promeniti vrednost nekih promenljivih ili dodati nove instrukcije. Ja menjam Smali code koristeći VS Code, zatim instalirajte smalise extension i editor će vam reći ako je neka instrukcija netačna.
Neki primeri se mogu naći ovde:

Ili možete check below some Smali changes explained.

Ponovo kompajlirajte APK

Nakon izmene koda možete ponovo kompajlirati kod koristeći:

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

Ovo će compile novi APK inside dist folder.

Ako apktool izbaci grešku, pokušajte instalirati najnoviju verziju

Potpišite novi APK

Zatim, potrebno je generisati ključ (biće zatražena lozinka i neke informacije koje možete popuniti nasumično):

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

Na kraju, potpišite novi APK:

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

jarsigner još uvek radi za neke brze testove, ali za moderne Android buildove apksigner je poželjniji jer podržava novije sheme potpisivanja APK-a.

Optimizujte novu aplikaciju

zipalign je alat za poravnavanje arhiva koji pruža važnu optimizaciju Android APK datoteka. Više informacija ovde.

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

Ako APK sadrži ugrađene nativne biblioteke (lib/*.so), Android sada preporučuje korišćenje -P 16 kako bi .so datoteke bile poravnate za uređaje sa veličinom stranice 16 KiB i 4 KiB:

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

Potpišite novi APK (ponovo?)

Ako više volite da koristite apksigner umesto jarsigner, trebalo bi da potpišete apk nakon primene optimizacije sa zipaling. ALI OBRATITE PAŽNJU DA JE POTREBNO POTPISATI APLIKACIJU SAMO JEDNOM SA jarsigner (pre zipalign) ILI SA aspsigner (posle zipaling).

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

Praktičniji, moderniji tok je:

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

Važne napomene:

  • Ako izmenite APK nakon potpisivanja sa apksigner, potpis se poništava i morate ga ponovo potpisati.
  • apksigner verify --print-certs je koristan za potvrdu da je ponovo izgrađeni APK instalabilan i za pregled sertifikata koji će cilj prikazati tokom izvršavanja.

Izmena Smali

Za sledeći Hello World Java kod:

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

Smali kod bi bio:

.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

The Smali instruction set is available here.

Lagane izmene

Izmena početnih vrednosti promenljive unutar funkcije

Neke promenljive su definisane na početku funkcije koristeći opcode const, možete izmeniti njihove vrednosti ili definisati nove:

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

Osnovne operacije

#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

Veće promene

Smali zamke koje obično kvare ponovnu izgradnju

  • Preporučuje se povećati .locals kada su vam potrebni samo privremeni registri u telu postojeće metode. Parametarski registri (p0, p1…) mapirani su na najviše registre metode, pa besciljno prebacivanje na .registers često kvari raspored argumenata.
  • move-result, move-result-wide, i move-result-object moraju se pojaviti odmah nakon odgovarajućeg invoke-*. Umetanje logovanja ili bilo kog drugog opkoda između njih čini metodu nevažećom.
  • long i double vrednosti su wide vrednosti i zauzimaju par registara. Ako kasnije ponovo koristite te registre, zapamtite da v10 takođe zauzima v11.
  • Ako treba da prosledite mnogo registara, ili registre sa visokim brojevima, koristite /range varijante kao što su invoke-virtual/range.

Logovanje

#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:

  • Ako planirate da koristite deklarisane promenljive unutar funkcije (deklarisane v0,v1,v2…) stavite ove linije između .local i deklaracija promenljivih (const v0, 0x1)
  • Ako želite da ubacite logging kod u sredinu koda funkcije:
  • Dodajte 2 na broj deklarisanih promenljivih: Npr: od .locals 10 do .locals 12
  • Nove promenljive treba da budu sledeći brojevi nakon već deklarisanih (u ovom primeru to treba da budu v10 i v11, zapamtite da počinje u v0).
  • Izmenite kod logging funkcije i koristite v10 i v11 umesto v5 i v1.

Patching common anti-tamper checks

When an app is repacked, one of the first things that may break is an in-app signature / installer / integrity check. Good strings to search in JADX or in the smali tree are:

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

Modern apps often call PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES), hash the signer bytes with MessageDigest, and compare the result with a hardcoded constant. In practice, it is usually easier to patch the final boolean / branch than to rewrite all the signature-handling code.

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

Ako je verifikacioni kod bučan, potražite poslednje poređenje pre dijaloga o grešci / finish() / System.exit() / poziva telemetry i izmenite tamo umesto da dirate celu rutinu.

Toasting

Zapamtite da dodate 3 broju .locals na početku funkcije.

Ovaj kod je pripremljen da bude umetnut u sredinu funkcije (promenite broj varijabli po potrebi). On će uzeti vrednost this.o, pretvoriti je u String i zatim prikazati toast sa tom vrednošću.

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

Učitavanje nativne biblioteke pri pokretanju (System.loadLibrary)

Ponekad je potrebno unapred učitati nativnu biblioteku tako da se ona inicijalizuje pre ostalih JNI libova (npr. da bi se omogućila lokalna za proces telemetry/logging). Možete ubaciti poziv System.loadLibrary() u statički inicijalizator ili rano u Application.onCreate(). Primer smali za statički inicijalizator klase ():

.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

Kao alternativu, postavite iste dve instrukcije na početak vašeg Application.onCreate() da biste osigurali da se biblioteka učita što ranije:

.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

Napomene:

  • Uverite se da odgovarajuća ABI varijanta biblioteke postoji u lib// (npr. arm64-v8a/armeabi-v7a) kako biste izbegli UnsatisfiedLinkError.
  • Učitavanje veoma rano (class static initializer) garantuje da native logger može posmatrati naknadnu JNI aktivnost.

Smali statička analiza / Lov zasnovan na pravilima

Nakon dekompajliranja sa apktool, možete skenirati Smali liniju po liniju pomoću regex pravila da brzo otkrijete anti-analitičku logiku (provere root/emulator) i verovatno hardkodovane tajne. Ovo je brza trijaža tehnika: tretirajte pronađeno kao tragove koje morate verifikovati u okolnom Smali ili rekonstruisanom Java/Kotlin kodu.

Ključne ideje:

  • Filtriranje biblioteka: potiskujte ili označavajte nalaze pod uobičajenim third-party namespace-ovima kako biste se fokusirali na putanje koda koje pripadaju aplikaciji.
  • Naznake konteksta: zahtevajte da se sumnjivi stringovi pojavljuju blizu API-ja koji ih koriste (u istoj metodi, unutar N linija).
  • Stepen poverenja: koristite jednostavne stepene (visok/srednji) za rangiranje tragova i smanjenje lažnih pozitivnih.

Example library prefixes to suppress by default:

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

Primeri pravila detekcije (regex + heuristike konteksta):

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

Dodatne heuristike koje dobro funkcionišu u praksi:

  • Root package/path checks: zahtevaju prisustvo u blizini poziva PackageManager;->getPackageInfo ili File;->exists za stringove kao što su com.topjohnwu.magisk ili /data/local/tmp.
  • Emulator checks: sparuj sumnjive literale (npr., ro.kernel.qemu, generic, goldfish) sa bliskim Build.* getterima i poređenjima stringova (->equals, ->contains, ->startsWith).
  • Hardcoded secrets: označi const-string samo kada u blizini .field ili move-result identifikatora postoje ključne reči kao password, token, api_key. Izričito ignoriši markere koji su samo za UI kao AutofillType, InputType, EditorInfo.

Skeneri zasnovani na pravilima poput PulseAPK Core implementiraju ovaj model kako bi brzo identifikovali logiku protiv analize i potencijalne tajne u Smali.

Reference

Tip

Nauči i vežbaj AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Nauči i vežbaj GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Nauči i vežbaj Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Pregledaj kompletan HackTricks Training katalog za assessment tracks (ARTA/GRTA/AzRTA) i Linux Hacking Expert (LHE).

Podrži HackTricks