Lo primero que haremos será abrir con objdump el ejecutable ./go, para conocer la dirección
de entrada (saber desde dónde, desde qué dirección de memoria, empieza el programa a ejecutarse):
sh-3.2$ objdump -x ./go
./go: file format elf32-i386
./go
architecture: i386, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x08048f50
Apuntamos la dirección de inicio: 0x08048f50
Necesitamos preparar el terreno un poco más. Vamos a examinar el ejecutable con elfsh:
$ elfsh
> load go
A continuación, dentro de elfsh, pediremos la lista de direcciones de las funciones a
las que llama el juego:
> rel
(elfsh-0.81-a8-dev@local) rel
[RELOCATION TABLES]
[Object go]
{Section .rel.dyn}
[000] R_386_GLOB_DAT 0x0804D488 sym[004] : __gmon_start__ => Create GOT entry
[001] R_386_COPY 0x0804DA40 sym[051] : stderr => Copy symbol at runtime
{Section .rel.plt}
[000] R_386_JMP_SLOT 0x0804D498 sym[001] : XDrawString => Create PLT entry
[001] R_386_JMP_SLOT 0x0804D49C sym[002] : XLookupKeysym => Create PLT entry
[002] R_386_JMP_SLOT 0x0804D4A0 sym[003] : sprintf => Create PLT entry
[003] R_386_JMP_SLOT 0x0804D4A4 sym[004] : __gmon_start__ => Create PLT entry
[004] R_386_JMP_SLOT 0x0804D4A8 sym[006] : XAutoRepeatOff => Create PLT entry
[005] R_386_JMP_SLOT 0x0804D4AC sym[007] : _ZdlPv => Create PLT entry
[006] R_386_JMP_SLOT 0x0804D4B0 sym[008] : XSetTile => Create PLT entry
[007] R_386_JMP_SLOT 0x0804D4B4 sym[009] : XSetClipMask => Create PLT entry
[008] R_386_JMP_SLOT 0x0804D4B8 sym[010] : XSetFont => Create PLT entry
[009] R_386_JMP_SLOT 0x0804D4BC sym[011] : XpmCreatePixmapFromData => Create PLT entry
[010] R_386_JMP_SLOT 0x0804D4C0 sym[012] : XSetFillStyle => Create PLT entry
[011] R_386_JMP_SLOT 0x0804D4C4 sym[013] : __libc_start_main => Create PLT entry
[012] R_386_JMP_SLOT 0x0804D4C8 sym[014] : XCreateSimpleWindow => Create PLT entry
[013] R_386_JMP_SLOT 0x0804D4CC sym[015] : XAutoRepeatOn => Create PLT entry
[014] R_386_JMP_SLOT 0x0804D4D0 sym[016] : XOpenDisplay => Create PLT entry
[015] R_386_JMP_SLOT 0x0804D4D4 sym[017] : gettimeofday => Create PLT entry
[016] R_386_JMP_SLOT 0x0804D4D8 sym[018] : XDrawLine => Create PLT entry
[017] R_386_JMP_SLOT 0x0804D4DC sym[019] : XCloseDisplay => Create PLT entry
[018] R_386_JMP_SLOT 0x0804D4E0 sym[020] : XSetForeground => Create PLT entry
[019] R_386_JMP_SLOT 0x0804D4E4 sym[021] : XFillRectangle => Create PLT entry
[020] R_386_JMP_SLOT 0x0804D4E8 sym[022] : fclose => Create PLT entry
[021] R_386_JMP_SLOT 0x0804D4EC sym[023] : XFreeGC => Create PLT entry
[022] R_386_JMP_SLOT 0x0804D4F0 sym[024] : XDrawRectangle => Create PLT entry
[023] R_386_JMP_SLOT 0x0804D4F4 sym[025] : fopen => Create PLT entry
[024] R_386_JMP_SLOT 0x0804D4F8 sym[026] : XNextEvent => Create PLT entry
[025] R_386_JMP_SLOT 0x0804D4FC sym[027] : XMapWindow => Create PLT entry
[026] R_386_JMP_SLOT 0x0804D500 sym[028] : _Znwj => Create PLT entry
[027] R_386_JMP_SLOT 0x0804D504 sym[029] : select => Create PLT entry
[028] R_386_JMP_SLOT 0x0804D508 sym[030] : XCreateGC => Create PLT entry
[029] R_386_JMP_SLOT 0x0804D50C sym[031] : fwrite => Create PLT entry
[030] R_386_JMP_SLOT 0x0804D510 sym[032] : XSelectInput => Create PLT entry
[031] R_386_JMP_SLOT 0x0804D514 sym[033] : XFlush => Create PLT entry
[032] R_386_JMP_SLOT 0x0804D518 sym[034] : XTextWidth => Create PLT entry
[033] R_386_JMP_SLOT 0x0804D51C sym[035] : XChangeGC => Create PLT entry
[034] R_386_JMP_SLOT 0x0804D520 sym[036] : XCopyColormapAndFree => Create PLT entry
[035] R_386_JMP_SLOT 0x0804D524 sym[037] : XLoadQueryFont => Create PLT entry
[036] R_386_JMP_SLOT 0x0804D528 sym[038] : XParseColor => Create PLT entry
[037] R_386_JMP_SLOT 0x0804D52C sym[039] : XAllocColor => Create PLT entry
[038] R_386_JMP_SLOT 0x0804D530 sym[040] : puts => Create PLT entry
[039] R_386_JMP_SLOT 0x0804D534 sym[041] : XSetStandardProperties => Create PLT entry
[040] R_386_JMP_SLOT 0x0804D538 sym[042] : XFreePixmap => Create PLT entry
[041] R_386_JMP_SLOT 0x0804D53C sym[043] : fread => Create PLT entry
[042] R_386_JMP_SLOT 0x0804D540 sym[044] : XCreatePixmap => Create PLT entry
[043] R_386_JMP_SLOT 0x0804D544 sym[055] : __gxx_personality_v0 => Create PLT entry
[044] R_386_JMP_SLOT 0x0804D548 sym[045] : _Unwind_Resume => Create PLT entry
[045] R_386_JMP_SLOT 0x0804D54C sym[046] : XSetWindowColormap => Create PLT entry
[046] R_386_JMP_SLOT 0x0804D550 sym[047] : XPending => Create PLT entry
Conviene fijarse en algunas funciones como: sprintf, fopen, fread, fclose…
En un post anterior vimos que el ejecutable busca un fichero desde el que leer el serial number, así que
saber dónde se hace un fopen/fread/fclose es sumamente interesante 😉
Ahora que tenemos unas pequeñas ayudas con las direcciones de esas funciones, y sabemos dónde poner
el primer punto de ruptura, podemos empezar a depurar con GDB:
gdb ./go
gdb> break *0x08048f50
gdb> run
gdb> set disassembly-flavor intel
gdb> disassemble 0x08048f50 0x8048fff
0x08048f50: xor ebp,ebp
0x08048f52: pop esi
0x08048f53: mov ecx,esp
0x08048f55: and esp,0xfffffff0
0x08048f58: push eax
0x08048f59: push esp
0x08048f5a: push edx
0x08048f5b: push 0x804b880
0x08048f60: push 0x804b890
0x08048f65: push ecx
0x08048f66: push esi
0x08048f67: push 0x8049330
0x08048f6c: call 0x8048d0c
0x08048f71: hlt
0x08048f72: nop
Comenzamos a meter puntos de ruptura en aquellas direcciones de las que se ha hecho push previo:
break *0x804b880
break *0x804b890
break *0x8049330
Y ahora, con un buen café delante, empezamos el mantra d ela depuración:
gdb> continue
gdb> disassemble 0x.... 0x....+20 ; miramos el contexto en el que estamos
gdb> break *0x...... ; ponemos un punto de ruptura en la dirección a la que apunten los "call" y en la llamada call+1
gdb> x/15x 0x....; examinamos el contenido de la memoria en cierta posición
Los pasos anteriores los ejecutaremos varias veces, hasta familiarizarnos con el código en ensamblador y/o hasta que veamos alguna zona de código «con sentido». ¿Cuándo ocurrirá esto último? Cuando nos encontremos con un «call 0xxxxxxx» donde 0xxxxxx sea alguna de las direcciones que apuntamos al principio con la ayuda de elfsh. Por ejemplo, en algún momento llegaremos a este punto:
0x8049370 call 0x8048dcc ; esto, según elfsh es una llamada a fopen
Además, en ese momento el registro AX = BFA8AEB4 = license.txt (así que ya sabemos cómo ha de llamarse el fichero que contiene el serial number).
Veámoslo:
0x8049370: call 0x8048dcc ; fopen
0x8049375: mov DWORD PTR [ebp-8],eax
0x8049378: cmp DWORD PTR [ebp-8],0x0
0x804937c: sete al
0x804937f: test al,al
0x8049381: je 0x804939e ; si apertura de fichero correcta saltar a rutina fread
después de la instrucción «je 0x804939e» vendrá la parte «else» del if:
0x08049383: mov DWORD PTR [esp],0x804b970
0x0804938a: call 0x8048ebc ; puts ("error")
0x0804938f: mov DWORD PTR [ebp-0x1698],0xffffffff
0x08049399: jmp 0x804952e
0x0804939e: mov eax,DWORD PTR [ebp-8]
¿Que cómo sé que eso es la parte else? Así:
gdb>x/2s 0x804b970
0x804b970: "error: license file not found!"
0x804b98f: "error closing license file!"
La llamada «call 0x8048ebc» según elfsh es a la función «puts», y justo antes, esp apunta al string «error: el fichero de licencia no se encontró», así que no hay mucho que pensar 😉
A continuación, una ayudita en pseudocódigo de lo que nos espera:
(num_car = fread(license.txt))
si ( num_car < 19) // 0x12 en hexadecimal = 18, contando el 0 --> 19 caracteres en la licencia o cerramos
printf
gdb>x/15s 0x804b950
0x804b950: "error: wrong license number"
si no
goto 0x80493e6
Por tanto, si el fichero license.txt existe, leeremos 19 caracteres… si los hay, saltamos a la rutina de comprobación
de serial, si no, mostrar el error:
0x080493bc: call 0x8048eec ; fread
0x080493c1: cmp eax,0x12 ; (0x12 en hexa = 18 en decimal)
0x080493c4: setbe al
0x080493c7: test al,al
0x080493c9: je 0x80493e6 ; saltar a rutina de COMPARACIÓN de serial number
0x080493cb: mov DWORD PTR [esp],0x804b950 ; "error: wrong license number"
0x080493d2: call 0x8048ebc ; puts ("error")
Así que la rutina de comprobación del serial está en 0x80493e6. Vamos a ver si entendemos el algoritmo (las líneas interesantes con comentarios tras el punto y coma )
0x080493e6: movzx eax,BYTE PTR ds:0x804da48 ; en la posición 0 del serial
0x080493ed: cmp al,0x30 ; debe de haber un 0 (en la tabla ASCII 0x30 = 0)
0x080493ef: jne 0x80494b9 ; si no fuera así --> error
0x080493f5: movzx eax,BYTE PTR ds:0x804da5a ; en la última posición
0x080493fc: cmp al,0x46 ; una F
0x080493fe: jne 0x80494b9
0x08049404: movzx edx,BYTE PTR ds:0x804da49 ; la posición 1
0x0804940b: movzx eax,BYTE PTR ds:0x804da4e ; y la posición 6
0x08049412: cmp dl,al ; tienen que ser iguales
0x08049414: jne 0x80494b9
0x0804941a: movzx eax,BYTE PTR ds:0x804da4a ; la posición 2
0x08049421: movsx edx,al
0x08049424: movzx eax,BYTE PTR ds:0x804da4b ; respecto a la posición 3
0x0804942b: movsx eax,al
0x0804942e: add eax,0x3 ; es 3 unidades mayor
0x08049431: cmp edx,eax
0x08049433: jne 0x80494b9
0x08049439: movzx eax,BYTE PTR ds:0x804da4c ; la posición 4
0x08049440: cmp al,0x2d ; tiene un -
0x08049442: jne 0x80494b9
0x08049444: movzx edx,BYTE PTR ds:0x804da51 ; la posición 9
0x0804944b: movzx eax,BYTE PTR ds:0x804da4c ; igual a la 4 (tiene un -)
0x08049452: cmp dl,al
0x08049454: jne 0x80494b9
0x08049456: movzx edx,BYTE PTR ds:0x804da56 ; la posición 14
0x0804945d: movzx eax,BYTE PTR ds:0x804da51 ; igual a la 9 (tiene un -)
0x08049464: cmp dl,al
0x08049466: jne 0x80494b9
0x08049468: movzx eax,BYTE PTR ds:0x804da4f ; la posición 7
0x0804946f: movsx edx,al
0x08049472: movzx eax,BYTE PTR ds:0x804da50 ; respecto a la 8
0x08049479: movsx eax,al
0x0804947c: sub eax,0x2 ; es 2 unidades menor
0x0804947f: cmp edx,eax
0x08049481: jne 0x80494b9
0x08049483: movzx eax,BYTE PTR ds:0x804da52 ; la posición 10
0x0804948a: movsx edx,al
0x0804948d: movzx eax,BYTE PTR ds:0x804da53 ; la posición 11
0x08049494: movsx eax,al
0x08049497: add edx,eax
0x08049499: movzx eax,BYTE PTR ds:0x804da54 ; la posición 12
0x080494a0: movsx eax,al
0x080494a3: add edx,eax
0x080494a5: movzx eax,BYTE PTR ds:0x804da55 ; la posición 13
0x080494ac: movsx eax,al
0x080494af: lea eax,[edx+eax] ; sumadas
0x080494b2: cmp eax,0xd1 ; deben superar el valor 209
0x080494b7: jg 0x80494d1 ; si se cumplen todas las condiciones anteriores cerrar el archivo y SERIAL correcto
0x080494b9: mov DWORD PTR [esp],0x804b950 ; si no se cumplen las condiciones anteriores: "Error: wrong serial number"
0x080494c0: call 0x8048ebc
0x080494c5: mov DWORD PTR [ebp-0x1698],0xffffffff
0x080494cf: jmp 0x804952e
0x080494d1: mov eax,DWORD PTR [ebp-8]
0x080494d4: mov DWORD PTR [esp],eax
0x080494d7: call 0x8048d9 ; fclose
0x080494dc: test eax,eax
0x080494de: setne al
0x080494e1: test al,al
0x080494e3: je 0x80494fd
0x080494e5: mov DWORD PTR [esp],0x804b98f
0x080494ec: call 0x8048ebc
0x080494f1: mov DWORD PTR [ebp-0x1698],0xffffffff
0x080494fb: jmp 0x804952e
0x080494fd: mov edx,DWORD PTR [ebp-0x169c]
Bueno, pues no ha sido tan difícil. Probemos nuestras conjeturas:
$ cat license.txt
0141-C124-FFFF-ABCF
$ ./go
Parece que la cosa funciona. Comenzamos a jugar y vemos que el juego Breakout (o Arkanoid), es un tanto extraño. Ganas puntos cuando dejas que la bola rebote contra el suelo (en el juego original, como bien sabemos, en esa situación perderíamos bola)… el caso es que, hacer 1000 puntos con estas reglas es muy complicado. ¿Qué hacer? Reverse Engineering, logicamente.
Continuamos nuestra excursión por GDB, aplicando el mantra anteriormente citado, y unas cuantas tazas de café después, nos encontramos con esta curiosa instrucción de comparación:
0x804b32a cmp eax, 0x3e6
El enunciado dice que hay que llegar a unos mil puntos para ver la contraseña al siguiente nivel. ¿Cuál es la representación en hexadecimal de 1000?
$ echo ‘ibase=10;obase=16;1000’ | bc
3E8
0x3e6 son unos mil puntos 🙂 Así que en esa línea se esconde el truco.
0x804b32a: cmp eax,0x3e6
0x804b32f: jg 0x804b350
0x804b331: mov eax,DWORD PTR [ebp+8]
0x804b334: mov eax,DWORD PTR [eax+28]
0x804b337: mov DWORD PTR [esp+8],eax
0x804b33b: mov DWORD PTR [esp+4],0x804baac
Ese jg viene a decir: «si eax es mayor que 0xe36 saltar a 0x804b350″… EAX lleva la puntuación (podemos jugar un rato y ver cóm EAX va cambiando).
Así que, sin más dilación, podemos indicarle a GDB que nos muestre el secreto que da acceso al siguiente nivel:
gdb> jump *0x804b350
Con lo que obtendremos por pantalla el tan ansiado password: 4r0wn3d
¿Te ha gustado? Pues en el HackIt! 2008, dentro de dos meses, tendrás más pruebas… y ahora puedes ayudar a que sea la mejor edición de la historia de la Euskal…
Wow, impresionante 🙂
Este nivel era largo de verdad, da vertigo pensar como tienen que ser los siguientes.