Banner 1

Solución al reto 9 del crackme#1 para Android

Estaba por blogear un reto que tenia en temporales que me pareció muy interesante (ya tiene sus años) pero sigue siendo muy practico, solo queda dar las gracias a la comunidad hackplayers por hacerlo y compartirlo:



A finales del mes pasado tuvimos el placer de presentaros nuestro primer crackme para sistemas Android. A continuación a mostraremos la solución al reto pero, cómo de momento sólo ha sido superado por unas pocas personas, vamos a dividir el solucionario en dos partes: la primera introduciendo unos conceptos básicos y preparando un emulador para probar la aplicación, y la segunda que publicaremos un poco más tarde y que contendrá la ingeniería inversa del apk del reto.

¡Esperamos que con este primer post os animéis todos los que todavía no lo habéis hecho! };)

Introducción

Lo primero que tenemos que tener claro antes de jugar con nuestro apk es el proceso de desarrollo de una aplicación Android. El lenguaje de programación de sus aplicaciones es Java y la gran diferencia es que el sistema de Google utiliza una máquina virtual llamada Dalvik, que se encarga de optimizar el bytecode compilado para su ejecución en teléfonos inteligentes o smartphones, es decir, sistemas con recursos más limitados.

A propósito de la relación entre Java y Android os recomiendo echar un vistazo a este excelente artículo de RevistaLiNUX.net. En él, podréis observar que Dalvik principalmente es una máquina basada en registros y no en pila, lo cual le permite utilizar una cantidad baja de memoria, y está diseñada para ejecutar cada aplicación aislada una máquina virtual diferente.

Resumiendo el proceso, Android primero compilará una clase escrita en Java y generará un fichero .class con el bytecode. Luego a su vez, el bytecode será transformado en el formato binario propio de la máquina Dalvik: .dex (Dalvik EXecutable):

Finalmente el fichero .dex será empaquetado en un único paquete .apk (Android application package) junto con resources, assets y el fichero manifest.

Si descomprimimos el paquete apk del reto lo veremos claramente:

Preparando el entorno

Es evidente que lo primero que necesitamos es un entorno de Java funcionando en nuestro equipo. Al menos JRE (Java Runtime Environment), aunque es recomendable JDK (Java Development Kit) porque incluye un jarsigner.
Una vez instalado procederemos a comprobar que java está incluido en la variable PATH y funcionando:

C:\Users\vmotos>java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Client VM (build 16.3-b01, mixed mode, sharing)

A continuación necesitamos el SDK de Android que contiene, entre otras herramientas, ADB (Android Debug Bridge) que nos permitirá mover ficheros, instalar aplicaciones o conectarnos al shell o intérprete de comandos de un teléfono (necesitaremos el driver USB) o un emulador (el SDK también incluye emuladores de Android).

Para instalarlo primero debemos descargarlo desde http://developer.android.com/sdk/index.html y seguir las instrucciones.

En Windows, básicamente sólo hay que instalar el ejecutable o descomprimir el zip:


Y añadir al menos una plataforma y los componentes mediante el SDK y AVD Manager (SDK Manager.exe):

Como nota adicional, os recomendamos instalar Eclipse con el plugin ADT (Android Development Tools) para realizar pruebas y disponer de un entorno de desarrollo muy versátil.


Una vez instalado el SDK de Android y para probar el crackme del reto, procederemos también a levantar un emulador virtual con la versión Froyo actual (Android 2.2).

A continuación veréis que el proceso de creación es muy sencillo:

1.- En ‘Virtual devices’ del Manager pulsaremos el botón ‘New’:

2.- Posteriormente seleccionaremos el nombre del emulador, el tipo de plataforma (target), el tamaño de la SD Card virtual, el tema y el hardware emulado:


3.- Finalmente pulsaremos el botón ‘start’ para que arranque el emulador:



Ahora que ya tenemos el emulador levantando, procederemos a instalar el paquete del crackme mediante ADB. Su instalación también es muy sencilla: descargamos el apk y ejecutamos lo siguiente:
D:\android-sdk-windows\platform-tools>adb.exe devices
List of devices attached
emulator-5554 device

D:\android-sdk-windows\platform-tools>adb.exe install crackme1hpys.apk
112 KB/s (11836 bytes in 0.102s)
pkg: /data/local/tmp/crackme1hpys.apk
Success

Después de la instalación del apk, podremos ver el icono del crackme en el menú de aplicaciones:

Haremos clic en el icono y entraremos en la pantalla principal del crackme:

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:



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



Fuente: http://www.hackplayers.com/2011/01/solucion-al-reto-del-crackme1-para_18.html

No hay comentarios:

Powered by Bad Robot
Helped by Blackubay