Smali - Descompilando/[Modificando]/Compilando

Tip

Aprenda e pratique AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Support HackTricks

Às vezes é interessante modificar o código da aplicação para acessar informações ocultas para você (talvez senhas bem ofuscadas ou flags). Então, pode ser interessante descompilar o apk, modificar o código e recompilá-lo.

Referência de opcodes: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

Método rápido

Usando o Visual Studio Code e a extensão APKLab, você pode descompilar automaticamente, modificar, recompilar, assinar e instalar o aplicativo sem executar qualquer comando.

Outro script que facilita muito essa tarefa é https://github.com/ax/apk.sh

Split APKs / App Bundles

Alvos modernos costumam ser distribuídos como split APKs (base.apk + split_config.*.apk) ao invés de um único APK monolítico. Se você modificar apenas o base.apk, recursos ou bibliotecas nativas podem ficar dessincronizados e a instalação pode falhar.

Triagem rápida a partir de um 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 o alvo for um pacote dividido, reconstrua todo o conjunto ou use ferramentas que unam os APKs primeiro. apk.sh é útil aqui porque pode combinar split APKs em um único APK patchável e corrigir identificadores de recursos públicos.
Para fluxos de trabalho de repackaging orientados a Frida/Objection, consulte também Android Anti-Instrumentation & SSL Pinning Bypass.

Descompile o APK

Usando o APKTool você pode acessar o código smali e os recursos:

apktool d APP.apk

Se o apktool lhe der qualquer erro, tente installing the latest version

Some interesting files you should look are:

  • res/values/strings.xml (e todos os xmls dentro de res/values/*)
  • AndroidManifest.xml
  • Qualquer arquivo com extensão .sqlite ou .db

Se o apktool tiver problemas ao decodificar a aplicação consulte https://ibotpeaches.github.io/Apktool/documentation/#framework-files ou tente usar o argumento -r (Não decodificar recursos). Então, se o problema estava em um recurso e não no código-fonte, você não terá esse problema (você também não decompilará os recursos).

Alterar o código smali

Você pode alterar instruções, mudar o valor de algumas variáveis ou adicionar novas instruções. Eu altero o código Smali usando VS Code, então instale a smalise extension e o editor lhe dirá se alguma instrução está incorreta.
Some examples can be found here:

Or you can check below some Smali changes explained.

Recompilar o APK

Após modificar o código você pode recompilar o código usando:

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

Ele vai compilar o novo APK dentro da pasta dist.

Se o apktool lançar um error, tente installing the latest version

Assinar o novo APK

Em seguida, você precisa gerar uma chave (será solicitado uma senha e algumas informações que você pode preencher aleatoriamente):

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

Finalmente, assine o novo APK:

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

jarsigner ainda funciona para alguns testes rápidos, mas para builds Android modernos apksigner é preferido porque ele lida com os esquemas de assinatura de APK mais recentes.

Otimizar novo aplicativo

zipalign é uma ferramenta de alinhamento de arquivos que fornece uma otimização importante para arquivos de aplicativo Android (APK). Mais informações aqui.

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

Se o APK contiver bibliotecas nativas empacotadas (lib/*.so), o Android agora recomenda usar -P 16 para que os arquivos .so sejam alinhados tanto para dispositivos com página de 16 KiB quanto de 4 KiB:

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

Assinar o novo APK (de novo?)

Se você prefere usar apksigner em vez do jarsigner, você deve assinar o apk após aplicar a otimização com zipalign. MAS ATENÇÃO: VOCÊ SÓ PRECISA ASSINAR A APLICAÇÃO UMA VEZ COM jarsigner (antes do zipalign) OU COM apksigner (depois do zipalign).

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

Um fluxo moderno mais prático é:

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

Notas importantes:

  • Se você modificar um APK depois de assiná-lo com apksigner, a assinatura é invalidada e você deve assiná-lo novamente.
  • apksigner verify --print-certs é útil para confirmar que o APK reconstruído é instalável e para inspecionar o certificado que o alvo irá expor em tempo de execução.

Modificando Smali

Para o seguinte código Java Hello World:

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

O código Smali seria:

.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

O conjunto de instruções Smali está disponível here.

Alterações leves

Modificar valores iniciais de uma variável dentro de uma função

Algumas variáveis são definidas no início da função usando o opcode const. Você pode modificar seus valores, ou pode definir novas:

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

Operações Básicas

#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

Alterações maiores

Armadilhas do Smali que normalmente quebram reconstruções

  • Prefira aumentar .locals quando você só precisa de registradores temporários no corpo de um método existente. Os registradores de parâmetros (p0, p1…) são mapeados para os registradores mais altos do método, então trocar cegamente para .registers frequentemente quebra o layout dos argumentos.
  • move-result, move-result-wide, and move-result-object devem aparecer imediatamente após o invoke-* correspondente. Inserir logging ou qualquer outro opcode entre eles torna o método inválido.
  • Valores long e double são valores wide e consomem um par de registradores. Se você reutilizar esses registradores depois, lembre-se de que v10 também ocupa v11.
  • Se você precisar passar muitos registradores, ou registradores com números muito altos, use as variantes /range, como 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>"

Recomendações:

  • Se você for usar variáveis declaradas dentro da função (declaradas v0,v1,v2…) coloque essas linhas entre o .local e as declarações das variáveis (const v0, 0x1)
  • Se você quiser colocar o código de logging no meio do código de uma função:
    • Adicione 2 ao número de variáveis declaradas: Ex: de .locals 10 para .locals 12
    • As novas variáveis devem ser os próximos números das variáveis já declaradas (neste exemplo devem ser v10 e v11, lembre que começa em v0).
    • Altere o código da função de logging e use v10 e v11 em vez de v5 e v1.

Corrigindo verificações anti-tamper comuns

Quando um app é reempacotado, uma das primeiras coisas que pode quebrar é uma verificação in-app de signature / installer / integrity. Boas strings para procurar no JADX ou na árvore smali são:

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

Aplicativos modernos frequentemente chamam PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES), hasheiam os bytes do signer com MessageDigest, e comparam o resultado com uma constante hardcoded. Na prática, geralmente é mais fácil patchar o final boolean / branch do que reescrever todo o código que lida com signatures.

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

Se o código de verificação for ruidoso, procure a última comparação antes do diálogo de erro / finish() / System.exit() / chamada de telemetria e faça o patch ali em vez de tocar em toda a rotina.

Toasting

Lembre-se de adicionar 3 ao número de .locals no início da função.

Este código está preparado para ser inserido no meio de uma função (altere o número das variáveis conforme necessário). Ele vai pegar o valor de this.o, transformá-lo em String e então fazer um toast com seu valor.

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

Carregar uma Biblioteca Nativa na Inicialização (System.loadLibrary)

Às vezes é necessário pré-carregar uma biblioteca nativa para que ela seja inicializada antes de outras libs JNI (por exemplo, para habilitar telemetria/registro local do processo). Você pode injetar uma chamada para System.loadLibrary() em um inicializador estático ou logo no início de Application.onCreate(). Exemplo smali para um inicializador de classe estático ():

.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

Alternativamente, coloque as mesmas duas instruções no início do seu Application.onCreate() para garantir que a biblioteca seja carregada o mais cedo possível:

.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

Notes:

  • Certifique-se de que a variante ABI correta da biblioteca exista em lib// (por exemplo, arm64-v8a/armeabi-v7a) para evitar UnsatisfiedLinkError.
  • Carregar muito cedo (class static initializer) garante que o native logger possa observar atividade JNI subsequente.

Smali Static Analysis / Rule-Based Hunting

Após decompilar com apktool, você pode scan Smali line-by-line com regras regex para identificar rapidamente lógica anti-analysis (root/emulator checks) e prováveis hardcoded secrets. Esta é uma técnica de fast triage: trate os hits como pistas que você deve verificar no Smali circundante ou no Java/Kotlin reconstruído.

Key ideas:

  • Library filtering: suprimir ou marcar achados sob namespaces de terceiros comuns para que você foque nos caminhos de código pertencentes ao app.
  • Context hints: exigir que strings suspeitas apareçam perto das APIs que as consomem (dentro do mesmo método, dentro de N linhas).
  • Confidence: use níveis simples (high/medium) para classificar as pistas e reduzir falsos positivos.

Example library prefixes to suppress by default:

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

Exemplos de regras de detecção (regex + heurísticas de contexto):

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

Heurísticas adicionais que funcionam bem na prática:

  • Root package/path checks: exigir chamadas próximas a PackageManager;->getPackageInfo ou File;->exists para strings como com.topjohnwu.magisk ou /data/local/tmp.
  • Emulator checks: emparelhar literais suspeitos (por exemplo, ro.kernel.qemu, generic, goldfish) com getters Build.* próximos e comparações de string (->equals, ->contains, ->startsWith).
  • Hardcoded secrets: sinalizar const-string apenas quando um identificador .field ou move-result próximo inclua palavras-chave como password, token, api_key. Ignorar explicitamente marcadores apenas de UI como AutofillType, InputType, EditorInfo.

Scanners guiados por regras como PulseAPK Core implementam esse modelo para rapidamente expor lógica anti-análise e potenciais segredos em Smali.

Referências

Tip

Aprenda e pratique AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Support HackTricks