Este reto se resolvió en el UTCTF 2021. El nombre del reto daba a entender que
se analizaría un ROM, el cual consiste en un archivo llamado
maze.gb, un juego que se puede ejecutar desde cualquier emulador de Gameboy.
Conociendo el ROM
Para ejecutar el ROM y analizarlo, inicialmente utilizé el emulador Visual Boy Advance con cual pude ver una simple pantalla de inicio (Figura 1).
Tecla | Función |
---|---|
Enter | Inicio(Start) |
Flecha Arriba | Movimiento Arriba |
Flecha Abajo | Movimiento Abajo |
Flecha Izquierda | Movimiento Izquierda |
Flecha Derecha | Movimiento Derecha |
El personaje se controla con las teclas de movimiento, los bloque negros sirven como pared y las llaves son objetos recolectables, cada vez que se recolecta una llave el contador incrementa en 1 (Figura 3).
Figura 3. Recoleccion de llave.
Es un laberinto relativamente pequeño y existen 10 llaves repartidas en el laberinto. Esta distribución esta ilustrada en el mapa de la Figura 4.
Al recolectar las 10 llaves en el laberinto se termina el juego, a lo que muestra un mensaje en pantalla (Figura 5).
Figura 5. YOU LOST OOPS SORRY LMOA.
Analizando el codigo
Para analizar el código del ROM utilizé la extención GhidraBoy que me permitió analizar de forma rápida el código. Para agregar la extención en Ghidra usé los siguientes comandos en la terminal:
#Primero descargar el repositorio git clone https://github.com/Gekkio/GhidraBoy.git #Accedemos a los archivos descargados cd GhidraBoy/ #Ejecutamos el archivo gradlew en la carpeta descargada #/opt/ghidra es el directorio donde tengo Ghidra ./gradlew -Pghidra.dir=/opt/ghidra/ #Tras esto se genera un archivo .zip en build/distributions/ #En mi caso es ghidra_9.2.1_PUBLIC_20210326_GhidraBoy.zip #Este se puede ver con el siguiente comando ls build/distributions/ #Movemos el archivo .zip generado a la subcarpeta de ghidra: Extensions/Ghidra mv build/distributions/ghidra_9.2.1_PUBLIC_20210326_GhidraBoy.zip /opt/ghidra/Extensions/Ghidra
Despues de esto solo hace falta abrir Ghidra y en el menú File seleccionar la opción Install Extensions.... Si todo salio bien, se puede ver la extención de GhidraBoy disponible para activarse (Figura 6).
Al añadir extenciones en Ghidra te pide que reinicies el programa, despues de hacerlo se puede analizar nuestro ROM. Ahora, tras el análisis que realiza Ghidra, podemos ver las distintas funciones en el ROM (Figura 7).
En este punto me puse a checar las distintas funciones del juego para ver si encontraba algo que me llamara la atencion, logrando encontrar sin mayor problema la siguiente función:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
void FUN_032f(void) { short sVar1; undefined *puVar2; char extraout_E; char extraout_E_00; byte bVar3; char cVar4; ushort uStack2; if (DAT_cb7c == '\0') { DAT_cd4c = 0; do { DAT_cd4d = 0; do { DAT_cd4e = DAT_cd4c + (DAT_cbe1 - 10); DAT_cd4f = (9 < DAT_cbe1) + -1 + CARRY1(DAT_cd4c,DAT_cbe1 - 10); uStack2 = (ushort)DAT_cd4d; _DAT_cd50 = uStack2 + CONCAT11((8 < DAT_cbe2) + -1,DAT_cbe2 - 9); sVar1 = ((ushort)DAT_cd4d * 4 + uStack2) * 4 + (ushort)DAT_cd4c; bVar3 = (byte)sVar1; puVar2 = (undefined *) CONCAT11((char)((ushort)sVar1 >> 8) + -0x35 + (0x1b < bVar3),bVar3 - 0x1c); if ((DAT_cd4f < (DAT_cd4e < 0x32)) && ((byte)((ushort)_DAT_cd50 >> 8) < ((byte)_DAT_cd50 < 0x32))) { DAT_cd52 = (&DAT_c0a0)[CONCAT11(DAT_cd4f,DAT_cd4e) + _DAT_cd50 * 0x32]; if (DAT_cd52 == '\0') { *puVar2 = DAT_cd53; } else { if (DAT_cd52 == '\x02') { *puVar2 = DAT_cd55; } else { *puVar2 = DAT_cd54; } } } else { *puVar2 = DAT_cd54; } DAT_cd4d = DAT_cd4d + 1; } while (DAT_cd4d < 0x12); DAT_cd4c = DAT_cd4c + 1; } while (DAT_cd4c < 0x14); DAT_cbe4 = DAT_cd55; FUN_0f1a(DAT_cb7a,10); DAT_cd56 = extraout_E; cVar4 = extraout_E; FUN_0f2d(10); DAT_cbe5 = cVar4 + '/'; DAT_cbe6 = extraout_E_00 + '/'; DAT_cd57 = extraout_E_00; FUN_1254(&DAT_1214,&DAT_cbe4); } else { if (DAT_cb7b == '\0') { FUN_0a45("YOU LOST",0x305); FUN_0a45(&DAT_0564,0x407); FUN_0a45("SORRY",0x506); FUN_0a45(&DAT_056f,0x607); } else { FUN_0a45("A WINNER IS YOU",0x302); FUN_0a45("CONGRATULATIONS",0x402); FUN_0a45("THE FLAG IS",0x603); FUN_0a45(&DAT_0551,&DAT_0702); DAT_cd58 = 0; do { *(byte *)CONCAT11((0xa6 < DAT_cd58) + -0x33,DAT_cd58 + 0x59) = *(byte *)CONCAT11((0x2a < DAT_cd58) + -0x35,DAT_cd58 - 0x2b) ^ *(byte *)CONCAT11((0x32 < DAT_cd58) + -0x35,DAT_cd58 - 0x33); DAT_cd58 = DAT_cd58 + 1; } while (DAT_cd58 < 8); DAT_cd61 = 0; FUN_0a45(&DAT_cd59,&DAT_0709); FUN_0a45(&DAT_0559,&DAT_0711); } } return; } |
Esta función llamo mi atencion debido a los mensajes que se encontraban a partir de la línea 56 en adelante. Donde reconocía el YOU LOST (línea 57) y el SORRY (línea 59), y tras ver la informacion de las variables que los acompañaban en las lineas 58 y 60 no me quedo duda de que era el mensaje que obtuve al conseguir las 10 llaves.
En este momento sabía que tenia dos posibles caminos para llegar a la solución; continuar analizando el código, donde podia ver una vertiente que se generaba a partir de la línea 63, que mostraba la impresion de un mensaje de victoria seguida de una especie de decodificación de datos mediante XOR (lineas 67 a la 74), o mi otra opción, la cual me parecio más atractiva en el momento, descubrir que secuencia de recollección de llaves me llevaría a la victoria. A partir de esta decisión le tome especial importancia a la variable de nombre DAT_cb7b (línea 56) al ser la determinadora del mensaje final.
LLegando a la Flag
Para encontrar esta secuencia utilizé la herramienta Wasmboy con la cual podría usar sus widgets en el ROM a mi favor. Utilizé 2 de sus widgets para esta tarea, el Memory Viewer y Playback Controls, los cuales se pueden agregar en el menú Widgets (Figura 8).
En Memory Viewer, con ayuda del campo Jump To Addres, se puede llegar rápidamente a la dirección de la variable DAT_cb7b (0xcb7b), una vez ahí solo hace falta seleccionar el campo W correspondeinte para agregar el Breakpoint en esta variable cuando vaya a ser escrita (Figura 9).
Una vez en el juego y tras éstos preparativos, cuando tomas una llave se activa nuestro breakpoint, donde podremos ver su valor en el momento en el memory viewer, así podemos detectar cambios en
la variable DAT_cb7b, si es la llave erronea la variable toma el valor de 0, lo que nos indica que debemos tomar una llave distinta (Figura 11). Para volver a un estado de nuestro agrado, solo entramos al menú de carga en Playback Controls (Load State) y seleccionamos el botón Play para continuar desde el estado seleccionado.
En cambio, si es la llave correcta, el valor de la variable continua siendo 1, a lo que se puede considerar un buen momento para guardar el estado, esto para no perder el progreso y no tener que repetir pasos. Para continuar en estos puntos solo se necesita seleccionar el botón Play (Figura 12).
Figura 12. Obtención de llave correcta.
Realizando esto con las demás llaves, se puede llegar a la secuencia que nos mostrará la flag. Esta secuencia esta ilustrada en la Figura 13.
Comentarios
Publicar un comentario