Decompilando aplicaciones Android

La máquina virtual Dalvic

Dentro de todo APK encontraremos el fichero classes.dex con el código de la aplicación. Los ficheros DEX contienen código ejecutable Android preparado para la máquina virtual Dalvik. Este código es muy similar a los byte codes utilizados en la máquina virtual Java en los ficheros .class. Varios ficheros .class suelen distribuirse de forma conjunta en ficheros JAR.

La gran diferencia entre estas dos máquinas virtuales es que la máquina Java tiene una arquitectura basada en pila mientras que la máquina Dalvik tiene una arquitectura basada en registros. Además se han introducido otras optimizaciones para que el código de la máquina Dalvik ocupe menos memoria y se ha cambiado los códigos de operación para que se ejecuten más rápido. La mayor diferencia es que los byte codes son de 8 bits, mientras que los códigos Dalvik son de 16 bits. Esto permite indicar directamente en la instrucción los operandos con el que ha de trabajar, para lo que se utilizan un campo de 4 bits, también conocido como registro virtual. Esto reduce el número de instrucciones que necesita un programa y aumenta su velocidad de interpretación.

A pesar de la gran diferencia entre los bytes codes y el código Dalvik, resulta sencillo convertir un código en el otro. Dentro del SDK de Android disponemos de la herramienta dx, que nos permite convertir ficheros CLASS en ficheros DEX. Además en este apartado usaremos una herramienta para convertir ficheros DEX en su equivalente JAR.

Para profundizar algo más en la diferencia entre los byte codes y el código Dalvik, vamos a estudiar cómo sería el resultado de compilar un pequeño ejemplo en Java para estas dos máquinas virtuales[1]


public static long sumArray(int[] arr) {
    long sum = 0;
    for (int i : arr) {
        sum += i;
    }
    return sum;
}
 

Figura 10:Código fuente en Java (.java).

0000: lconst_0             // pila ← 0
0001: lstore_1      // v1 ← pila
0002: aload_0              // pila ← v0 (parámetro arr)
0003: astore_3             // v3 ← pila
0004: aload_3              // pila ← v3
0005: arraylength  // pila ← long. del array en la pila
0006: istore 04    // v4 ← pila
0008: iconst_0             // pila ← 0
0009: istore 05            // v5 ← pila
000b: iload 05             // pila ← v5             (lect.local, escr.pila)
000d: iload 04             // pila ← v5             (lect.local, escr.pila
000f: if_icmpge 0024       // si pila >= pila a fin (lect.pila, lect.pila)
0012: aload_3              // pila ← v3       (lect.local, escr.pila)
0013: iload 05             // pila ← v5       (lect.local, escr.pila
0015: iaload               // pila ← pila[pila]     (2xlect.pila, escr.pila)
0016: istore 06            // v6 ← pila       (lect.pila, escr.pila)
0018: lload_1              // pila ← v1       (2xlect.local, 2xesc.pila)
0019: iload 06             // pila ← v6       (lect.local, escr.pila)
001b: i2l                         // pila ← (long)pila     (lect.pila, 2xescr.pila)
001c: ladd                 // pila ← pila + pila    (4xlect.pila, 2xescr.pila)
001d: lstore_1             // v1 ← pila       (2xlect.pila, 2xescr.pila)
001e: iinc 05, #+01        // v5++                   (lect.local, escr.local)
0021: goto 000b            // salta a principio del bucle
0024: lload_1              // pila ← v1
0025: lreturn              // retorna el valor en pila

Figura 11:Código generado para la máquina virtual Java (.class).

Estadísticas del bucle: 25 bytes, 14 instrucciones, 45 lecturas, 16 escrituras.

0000: const-wide/16 v0, #long 0   // V0 ← 0
0002: array-length v2, v8         // v2 ← long del array
0003: const/4 v3, #int 0          // v3 ← 0
0004: move v7, v3                                     // v7 ← v3
0005: move-wide v3, v0                         // v3 ← v0
0006: move v0, v7                                     // v0 ← v7
0007: if-ge v0, v2, 0010                // si v0 >= v2 salta fin     (2xlect.)
0009:aget v1, v8, v0                          // v1 ← v8[v0]          (2xlect. escr.)
000b:int-to-long v5, v1                // v5 ← (long)v1       (lect. 2xescr.)
000c:add-long/2addr v3, v5             // v3 ← v3 + v5       (3xlect. 2xescr.)
000d:add-int/lit8 v0, v0, #int 1       // v0 ← v0 + 1            (lect. escr.)
000f: goto 0007                                       // salta a principio bucle
0010: return-wide v3                                  // retorna v3
 

Figura 12:Código generado para la máquina virtual Dalvik (.dex).
Estadísticas del bucle: 18 bytes, 6 instrucciones, 19 lecturas, 6 escrituras.

 

El proceso de compilación de una aplicación Android no se realiza de forma directa desde código en Java a código Dalvik. Por el contrario, primero se obtienen los byte codes con un compilador de Java convencional y luego se convierte a código Dalvik por medio de la herramienta dx.

Figura 13: Proceso de compilación de una aplicacón Android.

 

Decompilando aplicaciones Android

En el punto anterior hemos descrito como un compilador Java genera el código para que pueda ser ejecutado por la máquina virtual. En este apartado, vamos a describir el proceso inverso, cómo a partir del código ejecutable podemos obtener una versión vastante aproximada del código Java a partir del que se creó. Esto se conoce como decompilar, y constituye el proceso más importante en la ingeniería inversa del software. Una de las herramientas más interesantes para decompilar los byte codes es jd-gui. Sin embargo, esta herramienta toma como entrada un fichero JAR y el código de las aplicaciones Android se almacena en ficheros DEX. Para salvar este problema vamos a utilizar la herramienta dex2jar, que permite transformar el formato. La siguiente figura muestra el proceso a realizar:   

Figura 14: Proceso de decompilación de una aplicacón Android.

Ejercicio paso a paso: Obtención del código Java de una aplicación

 

  1. Crea una nueva carpeta (por ejemplo C:\C9) y descomprime en ella el fichero clases.dex que encontrarás dentro de Apalabrados.apk.
  2. Accede a la URL: https://code.google.com/p/dex2jar/
  3. En las sección de Downloads descarga el fichero con la última versión (en nuestro caso dex2jar-0.0.9.13.zip)
  4. Descomprime este zip en la carpeta anterior.
  5. Todos los ficheros de la herramienta serán almacenados en la carpeta dex2jar-0.0.9.13 (o similar). Renombra esta carpeta para que se llame dex2jar. De esta forma será más sencillo acceder a ella.
  6. Abre un intérprete de comandos y sitúate en la carpeta creada en este ejercicio.
  7. Para transformar el fichero DEX en JAR, escribe el siguiente comando:

Verifica que se ha creado el fichero classes-dex2jar.jar.

  1. Vamos a descargar el decompilador en la web http://jd.benow.ca. Observa como existe una versión en modo comando y un plug-in para Eclipse e  IntelliJ IDEA.
  2. A nosotros nos interesa la versión con interfaz gráfico. Accede a la opción JD-GUI y descarga la versión adecuada para tu sistema operativo. Está disponible para Windows, Linux y OS X.
  3. La instalación en Windows es muy sencilla. No tienes más que copiar e fichero jd-gui.exe que encontrarás en el fichero comprimido, dentro de la carpeta creada en este ejercicio. Ejecuta este fichero.
  4.  Selecciona la opción File / Open File… e indica el fichero classes-dex2jar.jar. Si necesidad de realizar más acciones esta herramienta habrá decompilado todas las clases y te permitirá visualizar el código obtenido.
  5. Las clases son agrupadas según el paquete al que pertenecen. Explora alguna de estas clases:

En la imagen se muestra la primera actividad que ejecutará la aplicación, LiteSplashActivity_. Veremos más adelante cómo se ha obtenido esta información.

  1. Observa como esta clase extiendo LiteSplashActivity. Si pulsas sobre el nombre de la clase la abrirá.
  2. Repite este proceso hasta que no te permita continuar. La jerarquía de clases se muestra a continuación: 
LiteSplashActivity_
|
LiteSplashActivity
|
ApalabradosSplashActivity
|
BaseSplashActivity
|
FragmentActivity
|
Activity

Más adelante utilizaremos esta información en otro ejercicio: 

Preguntas de repaso y reflexión: Decompilando aplicaiones en Android

 

Pincha aquí para hacer un test.

 


[1] Bornstein, Dan (2008-05-29). "Presentation of Dalvik VM Internals" (PDF). Google. p. 22. Retrieved 2010-08-16.