Smali - Descompilar/[Modificar]/Compilar

Reading time: 8 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

A veces es interesante modificar el código de la aplicación para acceder a información oculta para ti (quizá contraseñas bien ofuscadas o flags). Entonces, puede ser interesante descompilar el APK, modificar el código y recompilarlo.

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

Método rápido

Usando Visual Studio Code y la extensión APKLab, puedes descompilar automáticamente, modificar, recompilar, firmar e instalar la aplicación sin ejecutar ningún comando.

Otro script que facilita mucho esta tarea es https://github.com/ax/apk.sh

Descompile el APK

Usando APKTool puedes acceder al código smali y los recursos:

bash
apktool d APP.apk

Si apktool te da algún error, intenta installing the latest version

Algunos archivos interesantes que deberías revisar son:

  • res/values/strings.xml (and all xmls inside res/values/*)
  • AndroidManifest.xml
  • Any file with extension .sqlite or .db

Si apktool tiene problemas decoding the application echa un vistazo a https://ibotpeaches.github.io/Apktool/documentation/#framework-files o prueba usando el argumento -r (No decodificar los recursos). Entonces, si el problema estaba en un recurso y no en el código fuente, no tendrás ese problema (tampoco decompilarás los recursos).

Cambiar código smali

Puedes change instructions, cambiar el value de algunas variables o add nuevas instrucciones. Yo modifico el código Smali usando VS Code, luego instalas la smalise extension y el editor te dirá si alguna instruction is incorrect.
Some examples can be found here:

O puedes check below some Smali changes explained.

Recompilar el APK

Después de modificar el código puedes recompile el código usando:

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

Se compilará el nuevo APK dentro de la carpeta dist.

Si apktool lanza un error, prueba installing the latest version

Firmar el nuevo APK

Luego, necesitas generar una clave (se te pedirá una contraseña y algunos datos que puedes rellenar aleatoriamente):

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

Finalmente, firma el nuevo APK:

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

Optimizar la nueva aplicación

zipalign es una herramienta de alineación de archivos que proporciona una optimización importante a los archivos de aplicación Android (APK). More information here.

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

Firma el nuevo APK (¿otra vez?)

Si prefieres usar apksigner en lugar de jarsigner, deberías firmar el apk después de aplicar la optimización con zipaling. PERO TEN EN CUENTA QUE SOLO TIENES QUE FIRMAR LA APLICACIÓN UNA VEZ CON jarsigner (antes de zipalign) O CON aspsigner (después de zipaling).

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

Modificando Smali

Para el siguiente código Java Hello World:

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

El código Smali sería:

java
.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

El conjunto de instrucciones de Smali está disponible here.

Cambios ligeros

Modificar los valores iniciales de una variable dentro de una función

Algunas variables se definen al principio de la función usando el opcode const; puedes modificar sus valores o definir nuevas:

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

Operaciones básicas

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

Cambios mayores

Registro

bash
#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>"

Recomendaciones:

  • Si vas a usar variables declaradas dentro de la función (declaradas v0,v1,v2...) coloca estas líneas entre la .local y las declaraciones de las variables (const v0, 0x1)
  • Si quieres poner el logging en medio del código de una función:
    • Suma 2 al número de variables declaradas: Ej: de .locals 10 a .locals 12
    • Las nuevas variables deben ser los siguientes números de las ya declaradas (en este ejemplo deben ser v10 y v11, recuerda que empieza en v0).
    • Cambia el código de la función de logging y usa v10 y v11 en lugar de v5 y v1.

Toasting

Recuerda sumar 3 al número de .locals al inicio de la función.

Este código está preparado para ser insertado en el medio de una función (cambia el número de las variables según sea necesario). Tomará el valor de this.o, lo transformará a String y luego hará un toast con su valor.

bash
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

Cargar una librería nativa al iniciar (System.loadLibrary)

A veces necesitas precargar una librería nativa para que se inicialice antes que otras JNI libs (p. ej., para habilitar telemetría/registro local del proceso). Puedes inyectar una llamada a System.loadLibrary() en un inicializador estático o al inicio de Application.onCreate(). Ejemplo smali para un inicializador de clase estática ():

smali
.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

Como alternativa, coloca las mismas dos instrucciones al inicio de tu Application.onCreate() para asegurar que la biblioteca se cargue lo antes posible:

smali
.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

Notas:

  • Asegúrate de que la variante ABI correcta de la biblioteca exista bajo lib// (p. ej., arm64-v8a/armeabi-v7a) para evitar UnsatisfiedLinkError.
  • Cargar muy pronto (inicializador estático de clase) garantiza que el logger nativo pueda observar la actividad JNI posterior.

Referencias

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks