Ofuscación del código

La ofuscación de código consiste en reordenar o alterar las instrucciones de un programa para que, aunque realice la misma función, sea más difícil su comprensión. En consecuencia, la ofuscación es la herramienta más importante para evitar la ingeniería inversa.

Como hemos podido comprobar en los apartados anteriores, cuando se compila Java se incluye información para depurar código en los byte codes (o código Dalvik). En esta información se incluyen los números de líneas a partir de las que se ha generado el código o los nombres de las variables. Por ejemplo, en la clase BaseSplashActivity de la aplicación Apalabrados, dentro del fichero DEX se ha incluido información como que existe una variable SPLASH_DURATION con nombre y que esta variable era inicializada en la línea 21 del fichero Java.

...

.field protected static SPLASH_DURATION:I

 

# direct methods

.method static constructor <clinit>()V

    .locals 1

    .prologue

    .line 21

...

Incluir esta información en una aplicación a la hora de distribuirla no parece muy razonable. No solamente estamos publicando los algoritmos que hemos desarrollado, sino que además estamos aumentando el tamaño del código con información que no es necesaria en tiempo de ejecución.

Una de las herramientas más utilizadas para ofuscar código Java es ProGuard. Además de ofuscar el código, lo optimiza, reduciendo su tamaño. En concreto esta herramienta realiza las siguientes acciones:

  • elimina variables, clases, métodos y atributos no utilizados;
  • elimina instrucciones innecesarias;
  • elimina la información de depuración;
  • renombra las clases, campos y métodos con nombres poco legibles.

ProGuard es parte del plugin de Android para Eclipse, por lo que no tienes que instalarlo ni invocarlo de forma manual. Solo tienes que activarlo en las propiedades del proyecto. En concreto, hay que establecer la propiedad proguard.config en el archivo project.properties.

Algunas situaciones son difíciles para ProGuard y podría equivocarse eliminando código que supuestamente no es utilizado, pero que en realidad es necesario para la aplicación. Algunos ejemplos incluyen:

  • una clase que se hace referencia solo desde AndroidManifest.xml;
  • un método llamado desde JNI;
  • campos y métodos referenciados dinámicamente.

Ejercicio paso a paso: Uso de Proguard para ofuscar una aplicación Android.

En este primer ejercicio vamos crear una aplicación sin activar las opciones de ofuscación y comprobaremos que resulta muy sencillo realizar la ingeniería inversa. Luego configuraremos Proguard para que ofusque el código y veremos que ya no es tan fácil realizar la ingeniería inversa.

1.    Puedes realizar este ejercicio con cualquier proyecto que tengas en Eclipse. Nosotros vamos a crear un nuevo de ejemplo: ApiDemos. Selecciona File/New/Project…/Android Sample Project. Selecciona Android 4.2 y pulsa «Next». Selecciona ApiDemos y pulsa «Finish».

2.     Vamos a crear el APK para distribuirlo. Selecciona File/Export…/Export Android Application. En Project selecciona ApiDemos y pulsa «Next».

3.     Necesitamos un certificado digital para firmar la aplicación. Puedes usar uno que ya tengas o crear uno nuevo seleccionando Create new keystore. Utiliza una de las claves existentes o crea una nueva.

4.     Finamente guarda el APK resultante en la carpeta de trabajo creada para este capítulo (C9).

5.     Renombra el fichero creado, ApiDemos.apk, a ApiDemos_sin.apk.

6.     Abre el fichero con un lector de ZIP y extrae el fichero clases.dex.

7.     Renombra este fichero para que se llame clases_sin.dex.

8.     Para transformar el fichero DEX en JAR, escribe el siguiente comando:


C:\C9>dex2jar\d2j-dex2jar clases_sin.dex

dex2jar clases_sin.dex -> clases_sin-dex2jar.jar

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

9.     Ejecuta jd-gui, selecciona la opción File/Open File… e indica el fichero clases_sin-dex2jar.jar.

10.     Observa como el código de las clases Java que componen la aplicación puede leerse perfectamente.

11.     Ahora vamos a crear de nuevo la aplicación ofuscando el código. Para ello, desde el explorador de proyectos de Eclipse, edita el fichero project.properties de ApiDemos.

12.     Añade la línea proguard.config=proguard.cfg, tal y como se muestra a continuación:

 

13.     Pulsa con el botón derecho sobre el nombre del proyecto y selecciona New/File. En el campo File name introduce proguard.cfg.

14.     Se creará un fichero con este nombre en el proyecto. Reemplaza su contenido por el siguiente:


-optimizationpasses 5

-dontusemixedcaseclassnames

-dontskipnonpubliclibraryclasses

-dontpreverify

-verbose

-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

 

-keep public class * extends android.app.Activity

-keep public class * extends android.app.Application

-keep public class * extends android.app.Service

-keep public class * extends android.content.BroadcastReceiver

-keep public class * extends android.content.ContentProvider

-keep public class * extends android.app.backup.BackupAgentHelper

-keep public class * extends android.preference.Preference

-keep public class com.android.vending.licensing.ILicensingService

 

-keepclasseswithmembernames class * {

    native <methods>;

}

 

-keepclasseswithmembers class * {

    public <init>(android.content.Context, android.util.AttributeSet);

}

 

-keepclasseswithmembers class * {

    public <init>(android.content.Context, android.util.AttributeSet, int);

}

 

-keepclassmembers class * extends android.app.Activity {

    public void *(android.view.View);

}

 

-keepclassmembers enum * {

    public static **[] values();

    public static ** valueOf(java.lang.String);

}

 

-keep class * implements android.os.Parcelable {

    public static final android.os.Parcelable$Creator *;

}

15.     Repite los pasos 2 al 9; pero esta vez renombra los ficheros APK y DEX a ApiDemos_con.apk y clases_con.dex.

16.     Observa como ahora no ha sido posible realizar la decompilación.

17.     Utilizar Proguard para ofuscar el código tiene la ventaja adicional de que el código generado es más pequeño. Compara el tamaño de los dos ficheros DEX generados.

Nombre                                    Tamaño

clases_sin.dex                        806 KB

clases_con.dex                        546 KB

18.     Vamos a comparar las dos aplicaciones creadas con APK-Multi-Tool. Para ello, copia los dos ficheros APK a la carpeta place-apk-here-for-modding.

19.     Ejecuta Script y selecciona la opción 24Set current Project. Indica uno de los dos APK copiados y luego selecciona la opción 9Decompile apk.

20.     Vuelve a seleccionar la opción 24Set current Project. Indica el otro APK copiado y luego selecciona la opción9Decompile apk.

21.     Abre las carpetas: projects\ApiDemos_sin.apk\smali\com\example\android\apis y projects\ApiDemos_con.apk\smali\com\example\android\apis.

22.     Compara como los nombres de las clases han sido modificados, aunque solo si no van a ser referenciadas desde fuera de la aplicación:

23.     Abre ApiDemos.smali en la versión ofuscada. El inicio de este fichero se muestra a continuación:

.class public Lcom/example/android/apis/ApiDemos;

.super Landroid/app/ListActivity;

 

# static fields

.field private static final a:Ljava/util/Comparator;

 

# direct methods

.method static constructor <clinit>()V

    .locals 1

    new-instance v0, Lcom/example/android/apis/a;

    invoke-direct {v0}, Lcom/example/android/apis/a;-><init>()V

    sput-object v0, Lcom/example/android/apis/ApiDemos;->
                                                                                 a:Ljava/util/Comparator;

    return-void

.end method

...

24.  Abre ApiDemos.smali en la versión no ofuscada. El inicio de este fichero se muestra a continuación. Para una comparación más rápida se ha tachado el código eliminado en la versión ofuscada y se ha subrayado el cambio de identificadores:

.class public Lcom/example/android/apis/ApiDemos;

.super Landroid/app/ListActivity;

.source "ApiDemos.java"

 

# static fields

.field private static final sDisplayNameComparator:Ljava/util/Comparator;

    .annotation system Ldalvik/annotation/Signature;

        value = {

            ...

        }

    .end annotation

.end field

 

# direct methods

.method static constructor <clinit>()V

    .locals 1

    .prologue

    .line 113

    new-instance v0, Lcom/example/android/apis/ApiDemos$1;

    invoke-direct {v0}, Lcom/example/android/apis/ApiDemos$1;-><init>()V

    .line 112

    sput-object v0, Lcom/example/android/apis/ApiDemos; ->
                                        sDisplayNameComparator: Ljava/util/Comparator;

    .line 119

    return-void

.end method

...

 

Preguntas de repaso: Ofuscación en Android.