Primera contramedida: ofuscar el código

Acabamos de ver lo sencillo que ha resultado eliminar el código en la aplicación de verificación de licencia. La primera acción a realizar para dificultar la ingeniería inversa de nuestra aplicación será ofuscar el código. Es muy fácil de hacer y evitamos que el código pueda transcribirse a Java, por lo que comprender el código será mucho más difícil.

Ejercicio paso a paso: Ofuscación de la aplicación con licencia.

1.    Ofusca la aplicación ObtencionLicencia. Para ello edita project.properties y crea el fichero proguard.cfg. tal y como se ha explicado en el apartado sobre ofuscación. Luego selecciona File/Export…/Export Android Application. Se creará un fichero APK firmado y ofuscado.

2.      Repite los puntos 1 al 4 del ejercicio anterior con este nuevo APK.

3.     Observa como ahora, dentro de Projects, se ha creado una estructura de carpetas diferente:

La clase org.tomas.girones.jesus.obtencionlicencia.MainAtivity es una de las pocas que conserva su nombre. Esta clase está referenciada desde un AndroidManifest.xml por lo que no puede cambiar de nombre. El resto de las clases que antes pertenecían a este mismo paquete ahora están en a.a.a.a.a y se llaman a, b, c, d, … ,u.

4.     Abre MainActivity.smali y compáralo con el obtenido en el ejercicio anterior:

.class public Lorg/tomas/girones/jesus/obtencionlicencia/MainActivity;

.super Landroid/app/Activity;

# interfaces

.implements La/a/a/a/a/m;

# static fields

.field private static final d:[B

# instance fields

.field a:La/a/a/a/a/i;

.field b:Z

.field c:Landroid/app/ProgressDialog;

# direct methods

.method static constructor <clinit>()V

    .locals 1

    const/16 v0, 0x14

    new-array v0, v0, [B

    fill-array-data v0, :array_0

    sput-object v0, Lorg/…/obtencionlicencia/MainActivity;->d:[B

    return-void

    ...

Observarás como han desaparecido algunas líneas (han sido subrayadas en el listado anterior). Observa cómo ha desaparecido el campo CLAVE_PUBLICA_LICENCIA. Esta constante se utiliza solo una vez, por lo que el ofuscador Proguard ha decidido usar directamente su valor, en vez de declararla. Puedes comprobarlo si miras dentro de onCreate() en la línea 256. De esta forma se ahorra código y disminuye la legibilidad. En la línea 256 también se aprecia como las cadenas de caracteres no son ofuscadas por Proguard. Otros ofuscadores más avanzados encriptan estas cadenas. Es interesante que observes como algunas referencias a clases externas han cambiado (los cambios se marcan en negrita y subrayado). Por ejemplo,

com/google/android/vending/licensing/LicenseCheckerCallback → a/a/a/a/a/m. Sin embargo, otras clases no han cambiado, por ejemplo: android/app/Activity o android/app/ProgressDialog. No pueden cambiar cuando están definidas fuera de nuestra aplicación. Algo parecido ha pasado con el nombre de los métodos. Han cambiado allow()→a(), applicationError()→b() y dontAllow()→c(). Sin embargo, no han cambiado comprobarLicencia(), entrar() y onCreate(). Los dos primeros corresponden a eventos onClick que han sido definidos desde XML. Podrían ser ofuscados si se cambiara el nombre en los dos sitios; pero Proguard no realiza esta tarea. El método onCreate() no puede ser ofuscado al ser de la clase Activity, definida fuera de nuestra aplicación. 

Práctica: Ingeniería inversa en una aplicación ofuscada.

Si tratas de obtener el código Java de un APK ofuscado, la herramienta jd-gui dará un resultado satisfactorio. Por lo tanto, tenemos que analizar el código a partir de los ficheros smali. El hecho de no disponer de los nombres de muchas de las clases, métodos y variables va a dificultar la tarea. ¿Qué estrategia seguirías para descubrir dónde se hace la comprobación de licencia?

Solución: Algunas posibilidades podrían ser

  • Buscar cadenas de caracteres relacionadas con la obtención de licencia.
  • Buscar una interfaz que tenga un perfil similar a LicenseCheckerCallback. En concreto, visualiza el fichero a/a/a/a/a/m.smali:

.class public interface abstract La/a/a/a/a/m;

.super Ljava/lang/Object;

# virtual methods

.method public abstract a(I)V

.end method

.method public abstract b(I)V

.end method

.method public abstract c(I)V

.end method

Está claro que una interfaz con tres métodos como los que se muestran tiene todo la pinta de ser la buscada. Lo que de momento no podemos saber es cuál de los métodos corresponde a allow(), dontAllow() o applicationError(). Una vez descubierto, podrías localizar qué clase implementa esta interfaz y copiar el mismo código que se ha puesto en allow(), dentro de dontAllow().

Práctica: Nuevas contramedidas para impedir la ingeniería inversa.

 

¿Qué contramedidas podrías implementar para impedir las acciones comentadas en la práctica anterior?

Solución: Algunas posibilidades se describen en el siguiente apartado.