Desensambladores smali
1. Qué es y por qué existe
smali es una sintaxis de texto para el bytecode Dalvik, y también el nombre del
ensamblador que la convierte en DEX. Su inverso, el desensamblador, se llama baksmali.
Entre los dos forman el único camino de ida y vuelta que existe para modificar el código de
una aplicación de la que no se tiene el fuente.
La diferencia con el documento anterior es de naturaleza, no de grado.
Decompilar a Java es reconstruir: se adivina qué texto
Java habría producido esas instrucciones, y la respuesta puede ser buena, mala o
silenciosamente falsa. Desensamblar a smali es transcribir: cada instrucción del DEX
se escribe como una línea de texto, con sus registros y sus operandos, sin interpretar nada.
De ahí la propiedad que define este documento entero:
El
smalies fiel. El Java es una hipótesis.
Un método puede fallar al decompilar; no puede fallar al desensamblar, salvo que el DEX
esté corrupto. Si el fichero es válido, hay smali. Ese es el suelo sobre el que se apoya
todo el trabajo de modificación.
Este documento cubre la sintaxis, las herramientas y —lo que más duele en la práctica— qué se puede editar a este nivel y qué no. El juego de instrucciones subyacente, con sus opcodes, formatos y descriptores, está en Bytecode Dalvik y no se repite aquí.
2. Cuándo hay que bajar a smali obligatoriamente
Cuatro situaciones. Fuera de ellas, leer Java es más cómodo y basta.
1. Cuando hay que modificar y reempaquetar. No existe ninguna herramienta que recompile a
DEX el Java que salió de un decompilador. Ese Java no compila: le faltan tipos, tiene
referencias sintéticas, y la sección 2 de
Decompiladores a Java explica por qué nunca compilará. El
smali, en cambio, vuelve a DEX byte a byte por construcción. Toda modificación real de
una aplicación pasa por aquí.
2. Cuando el decompilador ha fallado. Si jadx escribió JADX ERROR en el método o lo marcó
con Code decompiled incorrectly, el smali es lo único que queda. Y es suficiente: dice
exactamente lo que la máquina va a ejecutar.
3. Cuando hay que verificar una afirmación. Un informe que dice «esta aplicación envía el
IMEI a este dominio» se sostiene sobre lo que el smali muestra, no sobre lo que el Java
sugiere. En cualquier trabajo con consecuencias —auditoría, peritaje, análisis de malware— la
cita es del desensamblado.
4. Cuando el comportamiento depende de un detalle que el Java no muestra. Orden de
evaluación, aritmética de enteros con desbordamiento, comparaciones de referencias frente a
equals, el tipo exacto de una constante. El decompilador normaliza estas cosas; el
desensamblador no puede.
3. La sintaxis, directiva a directiva
Un fichero .smali describe una clase, y su nombre y ruta reproducen el nombre completo
de la clase: com/ejemplo/Utilidades.smali.
3.1 Directivas de clase
| Directiva | Qué declara |
|---|---|
.class |
Modificadores y descriptor de tipo de la clase. Siempre la primera línea |
.super |
Descriptor de la superclase |
.implements |
Una interfaz implementada. Se repite una vez por interfaz |
.source |
Nombre del fichero fuente original, si el DEX lo conserva |
.class public final Lcom/ejemplo/Utilidades;
.super Ljava/lang/Object;
.implements Ljava/io/Serializable;
.source "Utilidades.java"
Los tipos se escriben con descriptores, no con nombres de Java: I es int, Z es
boolean, [I es int[], Ljava/lang/String; es String. La tabla completa está en la
sección 9 de Bytecode Dalvik.
3.2 Directivas de campo
| Directiva | Qué declara |
|---|---|
.field |
Un campo: modificadores, nombre, :, descriptor de tipo y valor inicial si es constante |
.field private static final MAX:I = 0x64
.field private nombre:Ljava/lang/String;
El valor inicial solo aparece en campos static final con constante de compilación. Los demás
se inicializan en <clinit> o en <init>, y ahí es donde hay que buscarlos.
3.3 Directivas de método
| Directiva | Qué declara |
|---|---|
.method … .end method |
Delimitan el método: modificadores, nombre, firma |
.registers |
Total de registros del método |
.locals |
Registros no dedicados a parámetros. Alternativa a .registers |
.param |
Nombre y anotaciones de un parámetro |
.line |
Número de línea del fuente original, del debug_info |
.prologue |
Fin del preámbulo del método. Emitida por baksmali antiguo; hoy es rara |
.annotation … .end annotation |
Una anotación sobre la clase, campo, método o parámetro |
.catch |
Un bloque try/catch sobre un tipo concreto de excepción |
.catchall |
Un bloque try/finally: captura cualquier excepción |
.packed-switch … .end packed-switch |
Tabla de saltos de un switch con valores contiguos |
.sparse-switch … .end sparse-switch |
Tabla de saltos de un switch con valores dispersos |
.array-data … .end array-data |
Datos literales de un array inicializado |
Las etiquetas son identificadores precedidos de dos puntos en su definición y referidos sin
ellos: :cond_0, :goto_0, :try_start_0, :array_0. baksmali las genera con prefijo
según su uso y numeración correlativa. Son locales al método.
.catch referencia dos etiquetas de rango y una de destino:
:try_start_0
invoke-static {}, Lcom/ejemplo/Red;->conectar()V
:try_end_0
.catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
3.4 v frente a p
Dentro de un método hay un único banco de registros, numerados desde v0. Los parámetros
ocupan siempre los últimos, y baksmali les da un nombre alternativo con prefijo p para que
no haya que contar:
- En un método de instancia,
p0es siemprethis,p1el primer parámetro declarado,p2el segundo… - En un método estático,
p0es el primer parámetro declarado. - Los tipos anchos —
longydouble— ocupan dos registros consecutivos. Sip1es unlong, el siguiente parámetro esp3, nop2.
p0 y v1 pueden ser el mismo registro físico. La notación p es azúcar de baksmali, no
una categoría distinta de almacenamiento. La sección 5 lo demuestra sobre código real.
3.5 .registers frente a .locals: donde se equivoca todo el mundo
Las dos directivas declaran lo mismo con aritmética distinta, y solo puede aparecer una por método.
.registers N N = total de registros del método, parámetros incluidos
.locals M M = registros que NO son parámetros
N = M + (registros de parámetros)
Los «registros de parámetros» son el campo ins_size del code_item: el número de registros
que ocupan los argumentos entrantes, contando this en los métodos de instancia y contando
dos por cada long o double.
| Método | Parámetros | ins_size |
Si .locals 3 |
Equivale a |
|---|---|---|---|---|
static void f() |
ninguno | 0 | .locals 3 |
.registers 3 |
void f() (instancia) |
this |
1 | .locals 3 |
.registers 4 |
void f(int, int) (instancia) |
this, 2 int |
3 | .locals 3 |
.registers 6 |
static void f(long, Object) |
1 long, 1 ref | 3 | .locals 3 |
.registers 6 |
void f(long, boolean) (instancia) |
this, long, bool |
4 | .locals 3 |
.registers 7 |
El error clásico, y el motivo de este apartado: alguien necesita un registro más para su
parche, ve .locals 2 y lo cambia a .locals 3 pensando que ha añadido un registro al final.
Lo que ha hecho es insertar un registro: como los parámetros viven al final del banco,
subir el número de locales desplaza todos los p hacia arriba. Si el método tenía
.locals 2 y tres registros de parámetros, p0 era v2; con .locals 3 pasa a ser v3.
Todas las referencias explícitas a v2, v3 y v4 que hubiera en el cuerpo apuntan ahora a
otra cosa.
Como baksmali escribe los parámetros con notación p, el desplazamiento es transparente para
ellos — y por eso el error pasa desapercibido hasta que revienta. Lo que se rompe son las
referencias que usan la notación v sobre registros que resultaban ser parámetros, y el código
que se haya copiado de otro método.
Regla práctica: subir .locals es correcto y es la forma normal de hacer sitio, siempre
que el método esté escrito íntegramente en notación p para los parámetros. Mezclar v y p
para el mismo registro es lo que mata.
Hay un segundo límite, más duro: muchas instrucciones solo codifican registros de 4 bits, o
sea v0 a v15. Un método que ya usa dieciséis registros no admite uno más sin reescribir las
instrucciones afectadas a sus variantes /from16 o /16. La sección 3.2 de
Bytecode Dalvik explica esas variantes.
4. .registers sobre código real
Lo que sigue sí está ejecutado. La máquina de referencia no tiene baksmali, pero tiene
d8 y dexdump, que muestran los mismos campos del code_item que baksmali traduce a
directivas.
Clase de partida, escrita para este documento:
public class Saldo {
private int saldo;
public int retirar(int cantidad, boolean forzar) {
int restante = this.saldo - cantidad;
if (restante < 0 && !forzar) {
return -1;
}
this.saldo = restante;
return restante;
}
}
BT=~/Library/Android/sdk/build-tools/37.0.0
javac -g Saldo.java
$BT/d8 --release --min-api 24 --output . Saldo.class
$BT/dexdump -d classes.dex
Salida real, recortada al método que interesa:
Virtual methods -
#0 : (in LSaldo;)
name : 'retirar'
type : '(IZ)I'
access : 0x0001 (PUBLIC)
code -
registers : 4
ins : 3
outs : 0
insns size : 12 16-bit code units
000108: |[000108] Saldo.retirar:(IZ)I
000118: 5210 0000 |0000: iget v0, v1, LSaldo;.saldo:I // field@0000
00011c: b120 |0002: sub-int/2addr v0, v2
00011e: 3b00 0600 |0003: if-gez v0, 0009 // +0006
000122: 3903 0400 |0005: if-nez v3, 0009 // +0004
000126: 12f1 |0007: const/4 v1, #int -1 // #ff
000128: 0f01 |0008: return v1
00012a: 5910 0000 |0009: iput v0, v1, LSaldo;.saldo:I // field@0000
00012e: 0f00 |000b: return v0
catches : (none)
positions :
0x0000 line=4
0x0009 line=8
locals :
0x0000 - 0x000c reg=1 this LSaldo;
0x0000 - 0x000c reg=2 (null) I
0x0000 - 0x000c reg=3 (null) Z
Tres lecturas, todas verificables en ese volcado:
La aritmética. registers : 4 e ins : 3 dan .registers 4, y por tanto
.locals 1 — porque 4 − 3 = 1. Los tres registros de entrada son this, el int y el
boolean. El único local es v0, que sostiene restante.
El mapa v ↔ p. El bloque locals lo confirma sin ambigüedad: reg=1 es this, o sea
p0 es v1; reg=2 es el int, o sea p1 es v2; reg=3 es el boolean, o sea p2 es
v3. Y v0, que no aparece en la lista, es el local.
La prueba de que p no protege nada. En el offset 0007 está
const/4 v1, #int -1. v1 es p0, es decir this. El compilador ha decidido que en esa rama
this ya no hace falta y reutiliza su registro para el valor de retorno, que devuelve en
la instrucción siguiente. Un registro de parámetro es un registro normal: se puede pisar en
cuanto su valor deja de ser necesario, y D8 lo hace de forma rutinaria.
Un detalle más, que enlaza con la sección 2.3 de
Decompiladores a Java: la clase se compiló con javac -g, así
que el .class llevaba los nombres de los parámetros. En el volcado aparecen como (null).
Los ha borrado d8 --release. Los números de línea sí sobrevivieron —positions conserva
line=4 y line=8—, de modo que un DEX puede traer información de depuración parcial: las
líneas sí y los nombres no.
5. La misma clase en smali, comentada
⚠️ No ejecutado localmente: baksmali no está instalado en la máquina de referencia. El
smalique sigue está transcrito a mano desde el volcado real dedexdumpde la sección 4, aplicando las reglas de las secciones 3.4 y 3.5. Las instrucciones, los registros, los offsets y los números de línea son los reales; el nombre exacto de las etiquetas (:cond_0) es la convención de baksmali y podría numerarse de otro modo.
.class public LSaldo; # clase pública, descriptor de tipo con L…;
.super Ljava/lang/Object; # extiende Object
.source "Saldo.java" # nombre del fuente, del debug_info
# instance fields
.field private saldo:I # private int saldo — I es el descriptor de int
# direct methods
.method public constructor <init>()V
.registers 1 # 1 registro total; ins=1 (this) ⇒ .locals 0
.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V # super()
return-void
.end method
# virtual methods
.method public retirar(IZ)I # (int, boolean) → int
.registers 4 # 4 totales, ins=3 ⇒ equivale a .locals 1
# p0 = v1 = this
# p1 = v2 = cantidad (I)
# p2 = v3 = forzar (Z)
# v0 = el único local
.line 4
iget v0, p0, LSaldo;->saldo:I # v0 = this.saldo
# iget lee un campo de instancia int
sub-int/2addr v0, p1 # v0 = v0 - cantidad
# /2addr: destino y primer operando son el mismo
# registro. Ocupa 2 bytes en vez de 4
if-gez v0, :cond_0 # si v0 >= 0, saltar a :cond_0
# es la negación de (restante < 0) del fuente
if-nez p2, :cond_0 # si forzar != 0 (true), saltar a :cond_0
# negación de (!forzar). Los dos saltos juntos
# implementan el && con cortocircuito
const/4 p0, -0x1 # p0 (= v1 = this) pasa a valer -1
# se reutiliza el registro de this: ya no se usa
return p0 # return -1
.line 8
:cond_0
iput v0, p0, LSaldo;->saldo:I # this.saldo = v0
# ¡ojo! aquí p0 vuelve a ser this: este camino
# no pasó por el const/4 de arriba
return v0 # return restante
.end method
Merece la pena detenerse en el const/4 p0, -0x1. Escrito con notación p, parece que se
está machacando this, y eso es exactamente lo que ocurre — pero solo en la rama que retorna
inmediatamente después, donde this ya no se necesita. La rama de :cond_0 nunca ejecuta esa
instrucción, así que allí p0 sigue siendo this y el iput funciona.
Esta es la clase de razonamiento que hay que hacer para editar smali con seguridad, y la
razón por la que un parche que «solo añade una línea» puede romper un método que parecía no
tener nada que ver.
6. baksmali y smali: las herramientas
6.1 El cambio de manos, que conviene conocer
El proyecto lo creó Ben Gruver (JesusFreke) en 2010 y fue la referencia absoluta durante
más de una década. Ese repositorio original, JesusFreke/smali, está hoy archivado: último
push el 17 de enero de 2024, 6.631 estrellas, y sin desarrollo.
El proyecto vivo es google/smali, un fork que mantiene Google. Su README lo dice
explícitamente: el original ya no se mantiene activamente y Google se hizo cargo del código
para poder aplicarle los parches que necesitaba, cambiando el espacio de nombres de org.jf a
com.android.tools.smali. La primera release bajo Google fue la 3.0.0, construida sobre la
2.5.2 del original.
Quien busque documentación antigua se encontrará con clases org.jf.* y con URLs que ya no
llevan a ninguna parte. Es el mismo software.
6.2 Ficha de google/smali
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/google/smali |
| Licencia | BSD de 3 cláusulas (Ben Gruver, 2010), con partes de AOSP bajo Apache-2.0 |
| Lenguaje | Java |
| Versión estable | 3.0.6, publicada el 8 de mayo de 2024 |
| Último push | 14 de julio de 2026 |
| Creado (el fork) | 9 de diciembre de 2022 |
| Tracción | 390 ★ · 67 forks · 24 issues abiertas |
| Archivado | No |
Consultado el 12 de agosto de 2026 vía la API de GitHub. La API reporta la licencia como
NOASSERTION porque el fichero LICENSE no es un texto estándar sin modificar: empieza con
«The majority of smali/baksmali is written and copyrighted by me (Ben Gruver) and released
under the following license», seguido de una BSD de 3 cláusulas, y añade el aviso de Apache-2.0
para el código procedente de AOSP.
Lectura del mantenimiento: hay actividad reciente en el repositorio —commits en julio de 2026— pero la última versión publicada es de mayo de 2024, hace más de dos años. Es el mismo patrón que CFR: desarrollo sin publicación. La diferencia es que aquí el respaldo es Google y el componente está integrado en su propia cadena de herramientas, así que el riesgo de abandono es bajo.
El proyecto también publica dexlib2, la biblioteca de lectura y escritura de DEX que
hay debajo. Es la que usan apktool y buena parte del ecosistema; a efectos prácticos, es la
implementación de referencia del formato fuera de AOSP.
6.3 Opciones principales
⚠️ No ejecutado localmente: baksmali y smali no están instalados. La sintaxis procede de la documentación del proyecto y del uso establecido, consultada el 12 de agosto de 2026. ⚠️ sin verificar: el README de
google/smalino documenta las opciones de línea de órdenes —remite a--helpy a documentación externa—, así que la lista de flags de abajo no se ha podido contrastar contra una fuente oficial que los enumere.
Desensamblar un DEX a un árbol de ficheros .smali:
java -jar baksmali.jar disassemble classes.dex -o salida/
Volver a ensamblar:
java -jar smali.jar assemble salida/ -o classes.dex
Opciones que importan:
| Opción | Efecto |
|---|---|
-o, --output <dir> |
Directorio de salida |
-a, --api <n> |
Nivel de API. Determina qué opcodes se aceptan como válidos |
--parameter-registers / --no-parameter-registers |
Usar la notación p o solo v |
--sequential-labels |
Etiquetas numeradas correlativamente en vez de por offset |
-l, --use-locals |
Emitir .locals en lugar de .registers |
-b, --no-debug-info |
No emitir .line, .local ni .param |
--register-info |
Anotar cada instrucción con el estado de tipos de los registros |
--register-info es la opción infravalorada del lote: anota en comentarios qué tipo tiene cada
registro en cada punto. Es justo la información que hace falta para no romper la verificación
de bytecode al insertar código, y evita tener que deducirla a mano.
7. El crate smali de azw413
La única implementación de esta funcionalidad en Rust, y una pieza que condiciona decisiones de arquitectura por su licencia.
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/azw413/smali |
| Crate | https://crates.io/crates/smali |
| Licencia declarada en crates.io | GPL-3.0-only |
| Versión estable | 0.6.2, publicada el 5 de agosto de 2026 |
| Último push | 5 de agosto de 2026 |
| Creado | 22 de septiembre de 2022 |
| Descargas totales | 4.745 |
| Tracción | 23 ★ · 8 forks · 0 issues abiertas |
Consultado el 12 de agosto de 2026 vía la API de GitHub y la API de crates.io. Activo: las versiones 0.5.3, 0.6.0, 0.6.1 y 0.6.2 se publicaron todas entre el 4 y el 5 de agosto de 2026, y la 0.5.2 el 10 de junio de 2026. Es desarrollo reciente y vivo.
Qué cubre. Según su propio README: un parser, escritor y conjunto de tipos en Rust puro
para el formato smali, cubriendo la estructura completa de una clase de la VM Dalvik
incluidas todas sus instrucciones; lectura y escritura de ficheros DEX; soporte de multidex;
manejo de anotaciones de metadatos de Kotlin; edición de manifiestos de APK y de AAB; y
manejo de errores pensado para analizar ficheros no confiables sin romperse. Se declara
completamente autocontenido, sin dependencias de Java, y con el objetivo de ser 100 %
compatible con la implementación de smali/baksmali de Ben Gruver.
La licencia, que es el punto. crates.io declara GPL-3.0-only en todas sus versiones
publicadas, y el repositorio publica junto a la GPL un fichero LICENSE-commercial: es
decir, licenciamiento dual con opción comercial de pago.
⚠️ sin verificar: no se han podido leer los términos concretos del fichero
LICENSE-commercial; la consulta directa de su contenido no fue posible en esta revisión. Lo
verificado es que el fichero existe y que la licencia libre es GPL-3.0-only.
Eso es lo que importa. La GPL-3.0-only es una licencia copyleft fuerte: enlazar este crate dentro de un motor propio obliga a distribuir ese motor entero bajo GPL-3.0, lo que arrastra consigo el resto del código y descarta cualquier módulo propietario.
La consecuencia es la misma que con jadx: quien quiera integrarlo en un producto con licencia permisiva tiene que usarlo como binario aislado, en proceso separado, con frontera de licencia limpia —o adquirir la licencia comercial.
El punto ciego. 23 estrellas, 4.745 descargas y un solo autor. Es un proyecto personal, técnicamente ambicioso y activo, pero con un bus factor de uno. Apoyar sobre él una función crítica sin un plan de contingencia sería imprudente; usarlo tras una frontera de proceso acota el daño a tener que sustituir un ejecutable.
8. Qué se puede editar a nivel smali, y qué no
Aquí es donde la teoría se cobra el peaje.
8.1 Barato y seguro
Cambiar el valor de una constante. Sustituir const/4 v0, 0x0 por const/4 v0, 0x1 es la
edición canónica. No cambia el tamaño de la instrucción, no toca registros, no altera el flujo.
Cuidado con el rango: const/4 codifica un literal de 4 bits con signo, o sea de −8 a 7. Un
valor mayor exige const/16 o const, que ocupan más bytes — lo cual sigue siendo correcto,
porque el ensamblador recalcula todos los offsets, pero deja de ser una sustitución de un
carácter.
Invertir una condición. Cambiar if-eqz por if-nez, o if-gez por if-ltz. Mismo
tamaño, mismo formato, efecto opuesto. Es cómo se neutraliza una comprobación.
Eliminar una llamada sin retorno útil. Sustituir un invoke-* a un método void por
nop, o simplemente borrar la línea. El ensamblador recalcula offsets y etiquetas.
Cambiar el destino de un salto. Reapuntar una etiqueta a otra ya existente.
Editar el string de una constante. const-string v0, "..." admite cualquier contenido: el
ensamblador añade la entrada a la tabla de strings.
8.2 Caro, y por qué
Cambiar la firma de un método. Parece un cambio local y no lo es. La firma es parte de la
identidad del método en el DEX: aparece en el method_id_item, que referencia un
proto_id_item con la lista de tipos. Cambiarla implica que todas las invocaciones desde
todas las clases dejan de resolver — y esas invocaciones están repartidas por todos los
classes*.dex del APK, incluidos los que ni se han desensamblado.
Añadir un parámetro tiene un segundo efecto: sube ins_size, lo que desplaza todo el banco
de registros del método con el mecanismo de la sección 3.5. Hay que revisar el cuerpo entero.
Si el método sobrescribe uno de una superclase o implementa una interfaz, cambiar la firma
rompe además el enlace y ART lo detecta al cargar.
Añadir un campo. Hay que crear el .field, y luego inicializarlo — lo que significa tocar
<init> en todos sus constructores, o <clinit> si es estático. Un iput sobre un campo que
no existe en la clase produce error de verificación en tiempo de carga.
Añadir un método. Más fácil que lo anterior: un .method nuevo con .registers correcto
suele bastar. Pero si hay que llamarlo desde otra clase, esa clase también se toca.
Insertar código en medio de un método. El problema real, y merece su propio apartado.
8.3 El problema de los registros al insertar código
Insertar instrucciones requiere registros donde poner los valores intermedios. Y ahí empiezan las tres restricciones que se acumulan:
1. No hay registros libres. El compilador asigna el mínimo necesario. Un método con
.locals 2 tiene exactamente dos, y los dos están en uso. Hay que subir el número — con el
efecto de desplazamiento de la sección 3.5.
2. El techo de 4 bits. La mayoría de las instrucciones codifican sus registros en 4 bits:
solo alcanzan v0 a v15. Si el método ya usa dieciséis, subir .locals empuja los
parámetros por encima de v15, y todas las instrucciones que los referencian dejan de
poder codificarse. Hay que reescribirlas a /from16 o /16, lo que cambia sus tamaños.
La salida habitual es no tocar el banco: guardar el registro que se va a usar en uno alto con
move/16, usarlo, y restaurarlo. Funciona, y es tedioso.
3. La verificación de tipos de ART. Es la restricción que sorprende. ART verifica al
cargar que cada registro tiene un tipo coherente en cada punto del programa, y en
particular que en los puntos donde dos ramas confluyen el tipo coincide por ambos caminos. Un
parche que deja un registro conteniendo un int en una rama y una referencia en la otra
produce un error de verificación.
Y el error no ocurre donde está el bug: ocurre al cargar la clase, con un mensaje sobre tipos de registro que no señala la línea que se editó. La sección 10 de Bytecode Dalvik cubre el verificador; el diagnóstico de estos fallos está en Diagnóstico de fallos.
La consecuencia operativa: un DEX editado a mano que no supere la verificación falla al
cargar la clase, no al ejecutar la línea. La aplicación arranca y muere al tocar la
funcionalidad parcheada, con una traza que apunta a otro sitio.
8.4 Lo que no se toca desde aquí
Fuera del alcance del smali, aunque estén en el mismo APK:
- Los recursos.
resources.arscy losAXMLson otro formato. Se editan con las herramientas de Desempaquetado y reempaquetado. - El código nativo. Lo que está en
lib/<abi>/*.soes ELF, y se trata en Análisis binario y estático. Mover lógica ahí es precisamente una técnica para escapar del alcance de este documento. - La firma. Cualquier cambio la invalida, y hay que rehacerla: Firma y empaquetado.
Fuentes
- google/smali — repositorio oficial — https://github.com/google/smali
Consultado el 12 de agosto de 2026 vía
https://api.github.com/repos/google/smali. De aquí salen la fecha de creación del fork, el último push, los recuentos de estrellas, forks e issues, y la versión 3.0.6 del 8 de mayo de 2024 de la ficha de la sección 6.2. - google/smali — README — https://raw.githubusercontent.com/google/smali/main/README.md
Consultado el 12 de agosto de 2026. De aquí sale el relato del cambio de manos de la sección
6.1: que el original de JesusFreke ya no se mantiene, que Google se hizo cargo, el cambio de
espacio de nombres de
org.jfacom.android.tools.smaliy que la 3.0.0 se construyó sobre la 2.5.2. - google/smali — fichero LICENSE —
https://api.github.com/repos/google/smali/contents/LICENSE
Consultado el 12 de agosto de 2026. De aquí sale la licencia exacta: BSD de 3 cláusulas de
Ben Gruver, más Apache-2.0 para el código de AOSP, y la explicación de por qué la API de
GitHub la reporta como
NOASSERTION. - JesusFreke/smali — repositorio original — https://github.com/JesusFreke/smali Consultado el 12 de agosto de 2026 vía la API de GitHub. De aquí sale que está archivado, con último push el 17 de enero de 2024 y 6.631 estrellas.
- azw413/smali — repositorio del crate de Rust — https://github.com/azw413/smali
Consultado el 12 de agosto de 2026. De aquí salen la descripción de capacidades de la sección
7, la existencia del fichero
LICENSE-commercialjunto a la GPL-3.0, y los recuentos de estrellas, forks e issues. - crate
smalien crates.io — https://crates.io/api/v1/crates/smali Consultado el 12 de agosto de 2026. De aquí salen la licencia GPL-3.0-only declarada en todas las versiones, la versión 0.6.2 del 5 de agosto de 2026, el historial de versiones recientes y las 4.745 descargas totales. - Volcado de
dexdumpsobre una clase compilada al efecto — ejecutado el 12 de agosto de 2026 conbuild-tools37.0.0 (d8,dexdump) y OpenJDK 21.0.10 sobre la máquina de referencia. De aquí sale íntegra la sección 4: los valoresregisters : 4eins : 3, las ocho instrucciones con sus offsets, el bloquelocalscon el mapa de registros, y la evidencia de qued8 --releaseborra los nombres de parámetros pero conserva los números de línea.