Solución al reto 9 del crackme#1 para Android [2 de 2]

En la primera parte del solucionario os hablábamos un poco del desarrollo de aplicaciones en Android y de cómo instalar el SDK y preparar un emulador para probar el paquete del reto.

Ahora en esta segunda parte entraremos en materia…

La ingeniería inversa de la aplicación

Lo primero que haremos será descomprimir el fichero crackme1hpys.apk:


Podemos ver que la estructura es similar a la de cualquier paquete java aunque, entre sus recursos, nos interesa especialmente el fichero classes.dex, que es el que contiene el bytecode de Dalvik.

Para obtener e interpretar sus opcodes para el reversing/cracking es necesario primero desensamblarlo.

Las dos herramientas principales disponibles en la actualidad son Smali/Baksmali y Apktool. Smali/Baksmali es un ensamblador/desensamblador que "sólo" decompila el código, mientras que Apktool es
capaz de depurar smali (actúa como una especie de wrapper de Smali/Baksmali) y además nos da la posibilidad de decodificar los ficheros de recursos e imágenes.

Para nuestro crackme, en el que perseguimos obtener las credenciales de login, nos bastará con Baksmali. Para ello descargamos el fichero jar baksmali-1.2.6.jar y ejecutaremos lo siguiente en el directorio dónde hemos descomprimido el paquete apk:


Si todo ha ido bien, tendremos en el directorio ‘resultado’ una estructura similar a la del paquete java:


A continuación, abriremos con nuestro editor los ficheros con extensión .smali. En mi caso utilizaré ultraedit, ya que también disponemos de un resaltador de sintáxis que podremos descargar desde el magnífico blog androidcracking. En este blog os recomiendo también la lectura de los papers ‘way of the Android cracker’.


Ahora que ya tenemos el código, para entenderlo debemos primero consultar una guía de referencia de opcodes como la de Gabor Paller y/o mirar algunos ejemplos útiles. Haber programado (especialmente en java) y experiencia en ensamblador nos ayudará también bastante.

Para ir por la vía rápida, comenzaremos buscando cadenas const-string:

----------------------------------------
Buscar 'const-string' en 'D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali':
D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali(79): const-string v3, "PBAGENFRAN456"
D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali(87): const-string v3, "admin3"
D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali(108): const-string v4, "\u00a1Bien hecho!"
D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali(124): const-string v4, "Login incorrecto."
Encontrado 'const-string' 4 veces.
Busqueda completa, Encontrado 'const-string' 4 veces. (1 archivo(s)).


Con estos resultados nos centramos más en el fichero crackme1hpys$1.smali y observamos especialmente el método onClick (.method public onClick(Landroid/view/View;)V). El usuario parece estar claro (‘admin3’) pero si lo probamos junto con la contraseña ‘PBAGENFRAN456’ en el emulador veremos que el login es incorrecto:


Si analizamos un poco más el código, nos daremos cuenta de que el valor ‘PBAGENFRAN456’ es pasado como parámetro en la función doConvert para posteriormente almacenar el resultado en la variable secreto:

.line 61
.local v0, password:Ljava/lang/String;
const-string v3, "PBAGENFRAN456"
invoke-static {v3}, Lcom/hpys/crackmes/crackme1hpys;->doConvert(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
.line 63
.local v1, secreto:Ljava/lang/String;

Está claro que la función doConvert debe realizar algún tipo de conversión para ofuscar la cadena de la contraseña. Buscamos en los ficheros abiertos el nombre de la función para intentar entender su funcionamiento:

----------------------------------------
Buscar 'doConvert' en 'D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali':
D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys$1.smali(81): invoke-static {v3}, Lcom/hpys/crackmes/crackme1hpys;->doConvert(Ljava/lang/String;)Ljava/lang/String;
Encontrado 'doConvert' 1 veces.
----------------------------------------
Buscar 'doConvert' en 'D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys.smali':
D:\crackme1hpys\resultado\com\hpys\crackmes\crackme1hpys.smali(79): .method public static doConvert(Ljava/lang/String;)Ljava/lang/String;
Encontrado 'doConvert' 1 veces.
Busqueda completa, Encontrado 'doConvert' 2 veces. (2 archivo(s)).

El código del método doConvert se encuentra el fichero crackme1hpys.smali y es el siguiente:

.method public static doConvert(Ljava/lang/String;)Ljava/lang/String;
.registers 7
.parameter "in"

.prologue
const/16 v5, 0x41

.line 23
new-instance v2, Ljava/lang/StringBuffer;

invoke-direct {v2}, Ljava/lang/StringBuffer;->()V

.line 26
.local v2, tempReturn:Ljava/lang/StringBuffer;
const/4 v1, 0x0

.local v1, i:I
:goto_8
invoke-virtual {p0}, Ljava/lang/String;->length()I

move-result v3

if-lt v1, v3, :cond_13

.line 36
invoke-virtual {v2}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

move-result-object v3

return-object v3

.line 28
:cond_13
invoke-virtual {p0, v1}, Ljava/lang/String;->charAt(I)C

move-result v3

sput v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

.line 29
sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

and-int/lit8 v0, v3, 0x20

.line 30
.local v0, cap:I
sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

xor-int/lit8 v4, v0, -0x1

and-int/2addr v3, v4

sput v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

.line 31
sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

if-lt v3, v5, :cond_43

sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

const/16 v4, 0x5a

if-gt v3, v4, :cond_43

sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

sub-int/2addr v3, v5

add-int/lit8 v3, v3, 0xd

rem-int/lit8 v3, v3, 0x1a

add-int/lit8 v3, v3, 0x41

:goto_37
or-int/2addr v3, v0

sput v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

.line 32
sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

int-to-char v3, v3

invoke-virtual {v2, v3}, Ljava/lang/StringBuffer;->append(C)Ljava/lang/StringBuffer;

.line 26
add-int/lit8 v1, v1, 0x1

goto :goto_8

.line 31
:cond_43
sget v3, Lcom/hpys/crackmes/crackme1hpys;->abyte:I

goto :goto_37
.end method


# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.registers 4
.parameter "savedInstanceState"

.prologue
.line 42
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

.line 45
const/high16 v0, 0x7f03

invoke-virtual {p0, v0}, Lcom/hpys/crackmes/crackme1hpys;->setContentView(I)V

.line 48
const v0, 0x7f050006

invoke-virtual {p0, v0}, Lcom/hpys/crackmes/crackme1hpys;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/EditText;

iput-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->etUsername:Landroid/widget/EditText;

.line 49
const v0, 0x7f050004

invoke-virtual {p0, v0}, Lcom/hpys/crackmes/crackme1hpys;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/EditText;

iput-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->etPassword:Landroid/widget/EditText;

.line 50
const v0, 0x7f050003

invoke-virtual {p0, v0}, Lcom/hpys/crackmes/crackme1hpys;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/Button;

iput-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->btnLogin:Landroid/widget/Button;

.line 51
const v0, 0x7f050002

invoke-virtual {p0, v0}, Lcom/hpys/crackmes/crackme1hpys;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/Button;

iput-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->btnCancel:Landroid/widget/Button;

.line 52
const v0, 0x7f050001

invoke-virtual {p0, v0}, Lcom/hpys/crackmes/crackme1hpys;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/TextView;

iput-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->lblResult:Landroid/widget/TextView;

.line 55
iget-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->btnLogin:Landroid/widget/Button;

new-instance v1, Lcom/hpys/crackmes/crackme1hpys$1;

invoke-direct {v1, p0}, Lcom/hpys/crackmes/crackme1hpys$1;->(Lcom/hpys/crackmes/crackme1hpys;)V

invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 70
iget-object v0, p0, Lcom/hpys/crackmes/crackme1hpys;->btnCancel:Landroid/widget/Button;

new-instance v1, Lcom/hpys/crackmes/crackme1hpys$2;

invoke-direct {v1, p0}, Lcom/hpys/crackmes/crackme1hpys$2;->(Lcom/hpys/crackmes/crackme1hpys;)V

invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 76
return-void
.end method

Como podréis comprobar, seguir el código del método aún teniendo una guía a mano resultará bastante tedioso. De hecho, haréis que me quite el sombrero si alguno habéis conseguido saber que hace exactamente el método sólo siguiéndolo :D.

Para facilitar la obtención de la contraseña cambiaremos
entonces de táctica. Intentaremos decompilar para obtener el código en un lenguaje de más alto nivel, es decir en java y/o ejecutaremos nosotros mismos el método doConvert sobre la cadena ofuscada, es decir, haremos doConvert(‘PBAGENFRAN456’).

Primero comenzaremos con dex2jar, una herramienta para convertir del formato de un ejecutable de Dalvik (.dex) al formato .class de java.


La ejecución de la herramienta dará como resultado el fichero jar classes.dex.dex2jar.jar, que descomprimiremos seguidamente para observar el resto los ficheros en formato .class:


Ahora que tenemos los ficheros .class, mediante la herramienta jad decompilaremos crackme1hpys, que contenía el método doConvert:


Si leemos crackme1hpys.java, ¡ya tenemos el código fuente de la aplicación!:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: crackme1hpys.java

package com.hpys.crackmes;

import android.app.Activity;
import android.os.Bundle;
import android.widget.*;

public class crackme1hpys extends Activity
{

public crackme1hpys()
{
}

public static String doConvert(String s)
{
StringBuffer stringbuffer = new StringBuffer();
int i = 0;
do
{
int j = s.length();
if(i >= j)
return stringbuffer.toString();
abyte = s.charAt(i);
int k = abyte & 0x20;
int l = abyte;
int i1 = ~k;
abyte = l & i1;
int j1;
char c;
StringBuffer stringbuffer1;
if(abyte >= 65 && abyte <= 90)
j1 = ((abyte - 65) + 13) % 26 + 65;
else
j1 = abyte;
abyte = j1 | k;
c = (char)abyte;
stringbuffer1 = stringbuffer.append(c);
i++;
} while(true);
}

public void onCreate(Bundle bundle)
{
super.onCreate(bundle);
setContentView(0x7f030000);
EditText edittext = (EditText)findViewById(0x7f050006);
etUsername = edittext;
EditText edittext1 = (EditText)findViewById(0x7f050004);
etPassword = edittext1;
Button button = (Button)findViewById(0x7f050003);
btnLogin = button;
Button button1 = (Button)findViewById(0x7f050002);
btnCancel = button1;
TextView textview = (TextView)findViewById(0x7f050001);
lblResult = textview;
Button button2 = btnLogin;
_cls1 _lcls1 = new _cls1();
button2.setOnClickListener(_lcls1);
Button button3 = btnCancel;
_cls2 _lcls2 = new _cls2();
button3.setOnClickListener(_lcls2);
}

private static int abyte = 0;
private Button btnCancel;
private Button btnLogin;
private EditText etPassword;
private EditText etUsername;
private TextView lblResult;





private class _cls1
implements android.view.View.OnClickListener
{

public void onClick(View view)
{
String s = etUsername.getText().toString();
String s1 = etPassword.getText().toString();
String s2 = crackme1hpys.doConvert("PBAGENFRAN456");
if(s.equals("admin3") && s1.equals(s2))
lblResult.setText("\241Bien hecho!");
else
lblResult.setText("Login incorrecto.");
}

final crackme1hpys this$0;

_cls1()
{
this$0 = crackme1hpys.this;
super();
}
}


private class _cls2
implements android.view.View.OnClickListener
{

public void onClick(View view)
{
finish();
}

final crackme1hpys this$0;

_cls2()
{
this$0 = crackme1hpys.this;
super();
}
}

}

Mirando un poco el código java y si os manejáis con soltura en criptografía seguro que descubriréis que se trata de un algoritmo de rotación, concretamente ROT13 (j1 = ((abyte - 65) + 13) % 26 + 65;).

Si no, como os comentaba, lo mejor es compilar y ejecutar una clase de prueba con el método
doConvert, al que pasaremos la cadena 'PBAGENFRAN456'.

Así que ¡manos a la obra!; Primero sacamos el método para probarlo:

import java.io.*;

public class retohpys_rot13 {
public static String doConvert(String s)
{
int abyte = 0;
StringBuffer stringbuffer = new StringBuffer();
int i = 0;
do
{
int j = s.length();
if(i >= j)
return stringbuffer.toString();
abyte = s.charAt(i);
int k = abyte & 0x20;
int l = abyte;
int i1 = ~k;
abyte = l & i1;
int j1;
char c;
StringBuffer stringbuffer1;
if(abyte >= 65 && abyte <= 90)
j1 = ((abyte - 65) + 13) % 26 + 65;
else
j1 = abyte;
abyte = j1 | k;
c = (char)abyte;
stringbuffer1 = stringbuffer.append(c);
i++;
} while(true);
}

public static void main (String args[]) {
String password = doConvert("PBAGENFRAN456");
System.out.println("La contraseña del reto es = " + password);
}
}

A continuación lo compilamos con nuestro JDK y lo ejecutamos:


¡Y ya está, tenemos la contraseña ('CONTRASENA456')! La probamos en el emulador y confirmamos que es correcta:


Ganador del reto

El ganador del reto, nuestro primer crackme para Android, ha sido Ian Mess (sí, te prometemos otros retos para Android más complicados ;) ).

Otros acertantes fueron Simon Roses y un tercero que ha prefererido mantenerse en el anonimato.

A ellos y al resto de participantes muchas gracias. ¡Nos vemos en el próximo reto!


Solución al reto del crackme#1 para Android
- Parte 1 de 2
- Parte 2 de 2

Comentarios