Reverse engineer en OSX – Parte I
Jun 16, 2010 Sin categoria
Como verán, quien escribe no es quién esperan que escriba. Me tomé el atrevimiento de intrometerme en el blog de mi amigo @cuerty y dejarles una serie de posts para los switchers que se compraron una mac.
I. Introducción
Voy a hacer algunas entregas de una introducción a reverse engineer en OSX, siempre asm x86 32-bit. Mi idea es intentar desviar un poco la atención de la teoría (Que ya fue repetida hasta el hartazgo) y hacer un enfoque mas práctico: Voy a presentar una serie de crackme’s que bajé de otro sitio y explicar qué puede hacerse.
II. Requerimientos
Para mas o menos entender de qué viene el asunto, es necesario:
- Entender la sintaxis de Objective-C
- Conocer mas o menos los comandos básicos de gdb
- Estar familiarizado con la sintaxis Intel de asm.
II. b. Herramientas
- gdb
- otx
- class-dump
- Dado que OSX usa Mach-O que puede tener muchas arquitecturas, los offsets están corridos. Para solucionar esto pueden usar este script que hice
III. Lo básico.
Entender asm es fundamental para poder seguir adelante y a partir de este momento voy a asumir que esto último es un hecho.
Dicho esto, voy a recordar dos cosas: La primera es esta tabla de optcodes muy util (http://ref.x86asm.net/coder32.html) y la segunda la representación de los registros completos y parciales:
| Bits 31 – 24 | Bits 23 – 16 | Bits 15 – 8 | Bits 7 – 0 |
|---|---|---|---|
eax |
|||
ax |
|||
ah |
al |
||
ebx |
|||
bx |
|||
bh |
bl |
||
ecx |
|||
cx |
|||
ch |
cl |
||
edx |
|||
dx |
|||
dh |
dl |
||
ebp |
|||
bp |
|||
esp |
|||
sp |
|||
esi |
|||
si |
|||
edi |
|||
di |
|||
III b. ¿¡ Objective-Qué !?
Antes de empezar voy a hacer una brevísima introducción a la sintaxis de Objective-C.
Objective-C es un superset de C que provee programación orientada a objetos y conceptos de Messaging al estilo Smalltalk. Actualmente podemos compilar objective-c usando gcc.
¿Cómo se vé un hello world en Objective-C?
Normalmente tiene (Al igual que C) un archivo con los headers (.h) y otro con la implementación (.m), sin embargo no se parecen en nada a la sintaxis de C. Objective-C usa “interfaces” que vamos a llamar incorrectamente y para simplificarlo: “clases”.
Nuestro HelloWorld.h se vería:
@interface HelloWorld : NSObject - (void)imprimirFrase:(NSString *)frase @end
Esto puede traducirse a: La clase HelloWorld hereda de NSObject y tiene un método imprimirFrase que devuelve void y recibe un parámetro NSString.
Nuestro HelloWorld.m sería:
#import "HelloWorld.h"
@implementation HelloWorld
- (void)imprimirFrase:(NSString *)frase
{
NSLog(@"%@", frase);
}
@end
Si quisiera llamar a esta función debería hacer:
HelloWorld *hola = [[HelloWorld alloc] init]; [hola imprimirFrase:@"Hola mundo!"]; [hola release];
Como dije antes cuando hablamos de Objective-C no hacemos “llamadas a funciones” (A menos que sea una llamada a una función de C/C++) sino que “enviamos mensajes”. La sintaxis es esta:
[<objeto> <método>:<argumento_1> <nombre_argumento_2>:<argumento_2>....];
¿Y cómo funciona este envío de mensajes?
Lo primero que podemos ver es que los binarios están linkeados a libobjc.A.dylib. Por ahora lo único que vamos a ver de esta librería es una función que se llama “objc_msgSend”.
objc_msgSend es lo que el superset usa internamente para “enviar los mensajes” que vimos anteriormente. Esto es importante porque lo vamos a utilizar todo el tiempo en los análisis.
La función tiene esta declaración:
id objc_msgSend(id theReceiver, SEL theSelector, ...)
Devuelve un id (”id” es un puntero a objc_class que a su vez es un struct. Quizás en el siguiente tutorial entre en detalles sobre esto) y recibe como parámetros:
- El objeto que recibe el mensaje
- El método (SEL es un objc_selector, que a su vez es un (char *). SEL se utiliza en Objective-C para llamar a funciones dinámicamente en runtime).
- Los siguientes parámetros son los argumentos del método.
IV. A los bifes, #1 Crack me.
- El archivo se puede bajár de acá: SmellsGood
Voy a explicar un caso muy intuitivo; que no necesita mucho análisis y parece bastante straight forward, no obstante voy a pasar siempre por los mismos pasos:
- Recolección de información.
- Análisis de la información
- Debugging
- Cracking
Recolección de información
En esta primera etapa lo que vamos a hacer es recolectar todo lo necesario para entender qué sucede en la app y qué podemos hacer.
Lo primero que vay a hacer es desensamblarlo[0]. Para eso voy a usar otx, esta aplicación (otool eXtended) es muy similar a otool pero con un output mucho mas prolijo, optcodes, offsets y lo que es mejor: El nombre de los métodos.
Entonces:
$ otx SmellGood.app > SmellGood.asm
De esta manera tenemos en SmellGood.asm nuestra aplicación desensamblada. Ahora le toca el turno al class-dump. Lo que vamos a hacer en el siguiente paso es *intentar* hacer un dump de las definiciones de las clases usadas en SmellGood. El output debería ser similar a un .h de Objective-C con las interfaces.
$ class-dump SmellGood.app > SmellGood.h
Análisis de la información
Lo siguiente que voy a hacer es tratar de entender qué está sucediendo.
En el .h podemos ver qué métodos nos interesan tanto para entender qué sucede como para empezar a poner break-points. Nuestro caso es este:
- (void)check:(id)arg1; - (BOOL)checkCode:(id)arg1 forName:(id)arg2; - (id)generateCodeForName:(id)arg1; - (id)digest:(id)arg1; - (id)shuffle:(id)arg1;
Sin mucho análisis podemos ver que:
- El código depende del nombre.
- Hay un “digest” (¿md5?, ¿sha1?)
- Hay un shuffle
Ahora hagamos un análisis mas serio desde el assembler. Lo primero que vamos a ver es el método -(BOOL)[SmellsGood_AppDelegate checkCode:forName:]:
+30 00002b18 e855250000 calll 0x00005072 -[(%esp,1) componentsSeparatedByString:] +35 00002b1d 8b150c400000 movl 0x0000400c,%edx count +41 00002b23 89542404 movl %edx,0x04(%esp) +45 00002b27 890424 movl %eax,(%esp) +48 00002b2a e843250000 calll 0x00005072 -[(%esp,1) count] +53 00002b2f 83f805 cmpl $0x05,%eax +56 00002b32 754c jne 0x00002b80 +58 00002b34 a110400000 movl 0x00004010,%eax uppercaseString +63 00002b39 891c24 movl %ebx,(%esp) +66 00002b3c 89442404 movl %eax,0x04(%esp) +70 00002b40 e82d250000 calll 0x00005072 -[(%esp,1) uppercaseString] +75 00002b45 89c3 movl %eax,%ebx +77 00002b47 8b4514 movl 0x14(%ebp),%eax +80 00002b4a 89442408 movl %eax,0x08(%esp) +84 00002b4e a114400000 movl 0x00004014,%eax generateCodeForName: +89 00002b53 89442404 movl %eax,0x04(%esp) +93 00002b57 8b4508 movl 0x08(%ebp),%eax +96 00002b5a 890424 movl %eax,(%esp) +99 00002b5d e810250000 calll 0x00005072 -[(%esp,1) generateCodeForName:] +104 00002b62 891c24 movl %ebx,(%esp) +107 00002b65 89442408 movl %eax,0x08(%esp) +111 00002b69 a118400000 movl 0x00004018,%eax isEqualToString: +116 00002b6e 89442404 movl %eax,0x04(%esp) +120 00002b72 e8fb240000 calll 0x00005072 -[(%esp,1) isEqualToString:] +125 00002b77 ba01000000 movl $0x00000001,%edx +130 00002b7c 84c0 testb %al,%al +132 00002b7e 7502 jne 0x00002b82 +134 00002b80 31d2 xorl %edx,%edx +136 00002b82 83c414 addl $0x14,%esp +139 00002b85 89d0 movl %edx,%eax +141 00002b87 5b popl %ebx +142 00002b88 c9 leave +143 00002b89 c3 ret
Bueno, este es un caso que está clarísimo, incluso sin debuggearlo. No voy a detenerme en esta parte, ya que tiene demasiada meta-data. Lo que voy a hacer es traducir esto que vimos a un código que esté bastante cerca al que suponemos habrá sido el original:
-(BOOL)SmellsGood_AppDelegate checkCode:(NSString *)code forName:(NSString *)name
{
if ([[code componentsSeparatedByString:@"-"] count] != 5) return NO;
NSString *_code = [self generateCodeForName:[name uppercaseString]];
if ([_code isEqualToString:code])
return YES;
else
return NO;
}
¿Bastante facil no?. Lo mismo podemos hacer con generateCodeForName:.
-(NSString *)generateCodeForName:(NSString *)name
{
NSArray *data = [[self digest:[name dataUsingEncoding:4]] componentsSeparatedByString:@" "];
return [[[self shuffle:data] componentsJoinedByString:@"-"] uppercaseString];
}
Por cierto, ese “4″ ahí es del enum NSStringEncoding, en este caso NSUTF8StringEncoding = 4,
Y nuevamente podríamos hacer algo similar con shuffle, pero voy a cambiar de estrategia para que sea mas divertido.
Veamos el asm, separé las partes que nos importan:
+17 00002d0e c744240800000000 movl $0x00000000,0x08(%esp) ... +40 00002d25 e848230000 calll 0x00005072 -[(%esp,1) objectAtIndex:] +48 00002d2d c744240802000000 movl $0x00000002,0x08(%esp) ... +68 00002d41 e82c230000 calll 0x00005072 -[(%esp,1) objectAtIndex:] +76 00002d49 c744240801000000 movl $0x00000001,0x08(%esp) ... +96 00002d5d e810230000 calll 0x00005072 -[(%esp,1) objectAtIndex:] +104 00002d65 c744240803000000 movl $0x00000003,0x08(%esp) ... +123 00002d78 e8f5220000 calll 0x00005072 -[(%esp,1) objectAtIndex:] +131 00002d80 c744240804000000 movl $0x00000004,0x08(%esp) ... +150 00002d93 e8da220000 calll 0x00005072 -[(%esp,1) objectAtIndex:]
Bueno, ahí está bastante claro. Si recuerdan lo que dije de objc_msgSend no es dificil entender lo que sucede: Al principio está poniendo en el esp + 0×08 el número 0 (Recordemos que: esp tiene el objeto, +0×04 tiene un (char *) con el método y a partir de ahí los argumentos).
En resumen lo que está pasando acá es:
[array objectAtIndex:0]; [array objectAtIndex:2]; [array objectAtIndex:1]; [array objectAtIndex:3]; [array objectAtIndex:4];
Por otro lado sin mucho análisis uno puede deducir que el digest hace un sha1 (De nuevo, solo mirando el nombre de los métodos).
Debugging
Bueno, a decir verdad ya tenemos tanta información que no necesitamos mucho debugging. Este paso lo vamos a hacer mas en profundidad en otros crackme’s. Voy a hacer solamente una comprobación. ¿Se acuerdan en el primer código que hay un if que pregunta si el codigo ingresado es el mismo al generateCodeForName: ?. Bueno, vamos a localizarlo, para que se entienda lo que sucede. Según el .asm lo tenemos acá:
+130 00002b7c 84c0 testb %al,%al +132 00002b7e 7502 jne 0x00002b82
Corremos el gdb con el filename de nuestra app en el primer argumento.
$ gdb SmellsGood.app GNU gdb 6.3.50-20050815 (Apple version gdb-1461) (Wed Dec 23 06:11:18 UTC 2009) ...
Seteamos un breakpoint en checkCode:forName:
gdb$ break checkCode:forName: Breakpoint 1 at 0x2b01
Corremos el programa.
gdb$ run
Ahora tocamos en el botón de “check”.
Breakpoint 1, 0x00002b01 in -[SmellsGood_AppDelegate checkCode:forName:] () gdb$ disas Dump of assembler code for function -[SmellsGood_AppDelegate checkCode:forName:]: (...) 0x00002b7c <-[SmellsGood_AppDelegate checkCode:forName:]+130>: test al,al 0x00002b7e <-[SmellsGood_AppDelegate checkCode:forName:]+132>: jne 0x2b82 <-[SmellsGood_AppDelegate checkCode:forName:]+136> (...)
Bien, supongamos que queremos saber qué devuelve ese isEqualToString. (Asumamos por un segundo que sabemos que devuelve “false”… nota: Nunca asumir nada), podemos tomar muchos approaches de como encarar el patch, que vamos a analizar en el siguiente punto. Uno de ellos sería hacer que eso devuelva siempre “true” (o cambiar ese jne a un je). Veamos lo que devuelve esa función:
# Seguimos haciendo stepping hasta llegar a 0x00002b72 (Según lo que vimos en el disas ahí está el test al, al). gdb$ until *0x00002b72 0x00002ab1 in -[SmellsGood_AppDelegate check:] ()
¿Qué pasó?. No llegó nunca a la dirección que esperábamos. ¿Por qué?. Si analizamos el asm (O mas facil el código Objective-C que puse arriba) en un momento pasa esto:
0x00002b1d <-[SmellsGood_AppDelegate checkCode:forName:]+35>: mov edx,DWORD PTR ds:0x400c 0x00002b23 <-[SmellsGood_AppDelegate checkCode:forName:]+41>: mov DWORD PTR [esp+0x4],edx 0x00002b27 <-[SmellsGood_AppDelegate checkCode:forName:]+45>: mov DWORD PTR [esp],eax 0x00002b2a <-[SmellsGood_AppDelegate checkCode:forName:]+48>: call 0x5072 0x00002b2f <-[SmellsGood_AppDelegate checkCode:forName:]+53>: cmp eax,0x5 0x00002b32 <-[SmellsGood_AppDelegate checkCode:forName:]+56>: jne 0x2b80 <-[SmellsGood_AppDelegate checkCode:forName:]+134>
Supongamos por un momento que no tenemos el nombre de los métodos. Veamos que pasa acá:
Tocamos de nuevo “check” y vamos hasta este primer call que vemos arriba.
Breakpoint 1, 0x00002b01 in -[SmellsGood_AppDelegate checkCode:forName:] () gdb$ until *0x00002b2a 0x00002b2a in -[SmellsGood_AppDelegate checkCode:forName:] ()
Veamos a qué función va a llamar:
# Primero el objeto
gdb$ po *(void **)($esp)
{NSCFArray 0x241010} (
dasdasd
)
# Ahora el método gdb$ x/s *(char **)($esp + 0x4) 0x990bea10: "count"
Ok, básicamente lo que hace esto es fijarse si el tamaño de un array es 5. Si vemos mas arriba lo había separado por “-”. Algo ya podemos asumir: el código tiene que tener 5 partes separadas por “-”. Probemos ahora el código X-X-X-X-X y tocamos “check“.
Breakpoint 1, 0x00002b01 in -[SmellsGood_AppDelegate checkCode:forName:] () gdb$ until *0x00002b72 0x00002b72 in -[SmellsGood_AppDelegate checkCode:forName:] ()
Ahora si llegamos. Veamos ahí a qué llama:
gdb$ po *(void **)($esp) X-X-X-X-X gdb$ x/s *(char **)($esp + 0x4) 0x991037e8 <__PRETTY_FUNCTION__.202408+3076>: "isEqualToString:" gdb$ po *(void **)($esp + 0x4) 37E77CCE-999C2986-B5AC28E4-34238294-C7A9F84B
Well, well well.. Parece que ya tenemos el código para ese nombre!. Pero no vamos a parar acá ¿no?. Sigamos:
gdb$ nexti 0x00002b77 in -[SmellsGood_AppDelegate checkCode:forName:] () gdb$ nexti 0x00002b7c in -[SmellsGood_AppDelegate checkCode:forName:] ()
Bueno, ahí estamos en el test al, al. Como ya sabemos al es un partial de eax. Veamos que tiene:
gdb$ info reg eax eax 0x0 0x0
El test hace un AND sin guardar el resultado en ningún lado, solamente setea los flags// pero para qué digo todo esto si ya saben asm. Ya con esto estamos.
Cracking
Siempre hay mas de un camino para seguir a la hora de patchear o generar un keygen, acá es donde juega el ingenio de cada uno. Yo voy a encarar las dos opciones.
Patching (easy way)
La opción mas sencilla como ya vimos es cambiar el jne por un je. Para esto vamos a usar un editor hexadecimal.
Recordemos, lo que queremos cambiar es:
+132 00002b7e 7502 jne 0x00002b82
Como en este caso, la arquitectura x86 está primero en el mach-O no necesitamos calcular el offset. Directamente vamos a ese offset (0×2b7e) en el editor hexadecimal, tiene que haber un “75″ (optcode de jne). Cambiamos por “74″ (optcode de je), corremos el programa, llenamos el codigo con cualquier cosa de la forma X-X-X-X-X y vemos que pasa.
Voilá!.
Keygen
Ya que nos tomamos el trabajo de hacer todo el análisis y de comprender qué está pasando, deberíamos aprovechar y hacer un keygen que imite la lógica que ya pudimos deducir.
Este es el mio:
from hashlib import sha1
name = raw_input("Nombre?: ").strip()
sha1str = sha1(name).hexdigest().upper()
slots = [sha1str[i:i+8] for i in xrange(0, len(sha1str), 8)]
code = "%s-%s-%s-%s-%s" % (slots[4], slots[3], slots[1], slots[2], slots[0])
print code
[0] Si se me permite el neologismo
Tags: cracking, osx, reverse-engineer
Un día en internet
Dic 8, 2009 Sin categoria
Interesante infografía del volumen de datos que se mueven en internet por día:

Created by OnlineEducation.net
Tags: infografia, internet
Quotes del dia
Nov 19, 2009 Sin categoria
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
Martin Golding
Programming is like sex: one mistake and you’re providing support for a lifetime.
Michael Sinz
The trouble with programmers is that you can never tell what a programmer is doing until it’s too late.
Seymour Cray
Good code is its own best documentation.
Steve McConnell
Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves.
Alan Kay
Sacadas de WTF Code, una linda distracción mas para suscribirme por RSS.