Cómo resolví un simple desafío de CrackMe con Ghidra de la NSA

¡Hola!

Recientemente he estado jugando un poco con Ghidra, que es una herramienta de ingeniería inversa que recientemente fue abierta por la NSA. El sitio web oficial describe la herramienta como:

Un conjunto de herramientas de ingeniería inversa de software (SRE) desarrolladas por la Dirección de Investigación de la NSA en apoyo de la misión de Ciberseguridad.

Estoy al comienzo de mi carrera de ingeniería inversa, así que no hice nada avanzado. No sé qué características esperar de una herramienta profesional como esta, si está buscando leer acerca de las características avanzadas de Ghidra, probablemente este no sea el artículo para usted.

En este artículo intentaré resolver un simple desafío de CrackMe que encontré en el sitio web root-me. El desafío que estoy resolviendo se llama ELF - CrackPass. Si desea intentarlo usted mismo, entonces debería considerar no leer este artículo porque le arruinará el desafío.

¡Empecemos! Abro Ghidra y creo un nuevo proyecto al que llamo RootMe.

Luego importo el archivo de desafío arrastrándolo a la carpeta del proyecto. Iré con los valores predeterminados.

Después de recibir información sobre el archivo binario, presiono OK, selecciono el archivo y hago doble clic en él. Esto abre la utilidad del navegador de código de Ghidra y me pregunta si quiero analizar el archivo, luego presiono Sí y continúo con los valores predeterminados.

Después de importar el archivo, obtenemos información sobre el archivo binario. Si presionamos OK y descartamos esta ventana, y luego hacemos doble clic en el archivo que importamos, esto abre la utilidad del navegador de código de Ghidra. Selecciono Sí cuando se me pide que analice el binario y sigo con los valores predeterminados.

El navegador de código es bastante conveniente. En el panel izquierdo podemos ver la vista de desmontaje y en el panel derecho la vista de descompilación.

Ghidra nos muestra directamente la información del encabezado ELF y el punto de entrada del binario. Después de hacer doble clic en el punto de entrada, la vista del dissembler salta a la función de entrada.

Ahora podemos identificar con éxito la función principal, que cambio de nombre a principal. Sería bueno si la herramienta intentara detectar automáticamente la función principal y cambiarle el nombre en consecuencia.

Antes de analizar la función principal, quería cambiar su firma. Cambié el tipo de retorno a int y corregí el tipo y nombre de los parámetros. Este cambio ha tenido efecto en la vista de descompilación, ¡lo cual es genial! ?

Al resaltar una línea en la vista de descompilación también se resalta en la vista de ensamblaje.

Exploremos la función FUN_080485a5, que cambiaré de nombre a CheckPassword.

El contenido de la función CheckPassword se puede encontrar a continuación. He copiado el código directamente desde la vista de descompilación de Ghidra, que es una característica interesante de la que carecen muchas herramientas de este tipo. Ser capaz de copiar ensamblado y código es una característica agradable.

void CheckPassword(char *param_1) { ushort **ppuVar1; int iVar2; char *pcVar3; char cVar4; char local_108c [128]; char local_100c [4096]; cVar4 = param_1; if (cVar4 != 0) { ppuVar1 = __ctype_b_loc(); pcVar3 = param_1; do { if (((byte )(ppuVar1 + (int)cVar4) & 8) == 0) { puts("Bad password !"); /* WARNING: Subroutine does not return */ abort(); } cVar4 = pcVar3[1]; pcVar3 = pcVar3 + 1; } while (cVar4 != 0); } FUN_080484f4(local_100c,param_1); FUN_0804851c(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar2 = strcmp(local_108c,local_100c); if (iVar2 == 0) { printf("Good work, the password is : \n\n%s\n",local_108c); } else { puts("Is not the good password !"); } return; }

Después de echar un vistazo al código, llegué a las siguientes conclusiones. El bloque con las ifverificaciones si el usuario ha proporcionado una contraseña e inspecciona la contraseña proporcionada para comprobar si es un carácter válido o algo así. No estoy exactamente seguro de lo que está buscando, pero esto es lo que dice la documentación de __ctype_b_loc ():

La función __ctype_b_loc () devolverá un puntero a una matriz de caracteres en la configuración regional actual que contiene características para cada carácter en el conjunto de caracteres actual. La matriz debe contener un total de 384 caracteres y puede indexarse ​​con cualquier carácter con o sin signo (es decir, con un valor de índice entre 128 y 255). Si la aplicación es multiproceso, la matriz será local al subproceso actual.

De todos modos, ese bloque de código realmente no vale la pena, porque no modifica nuestra contraseña de ninguna manera, solo la verifica. Entonces podemos omitir este tipo de verificación.

La siguiente función llamada es FUN_080484f4. Mirando su código, podemos decir que es solo una implementación personalizada de Memcopy. En lugar de copiar el código C de la vista del descompilador, copié el código ensamblador; sí, esto es divertido.

************************************************************* * FUNCTION ************************************************************* undefined FUN_080484f4 (undefined4 param_1 , undefined4 p undefined AL:1  undefined4 Stack[0x4]:4 param_1 XREF[1]: 080484f8 (R) undefined4 Stack[0x8]:4 param_2 XREF[1]: 080484fb (R) FUN_080484f4 XREF[1]: CheckPassword:080485f5 (c) 080484f4 55 PUSH EBP 080484f5 89 e5 MOV EBP ,ESP 080484f7 53 PUSH EBX 080484f8 8b 5d 08 MOV EBX ,dword ptr [EBP + param_1 ] 080484fb 8b 4d 0c MOV ECX ,dword ptr [EBP + param_2 ] 080484fe 0f b6 11 MOVZX EDX ,byte ptr [ECX ] 08048501 84 d2 TEST DL,DL 08048503 74 14 JZ LAB_08048519 08048505 b8 00 00 MOV EAX ,0x0 00 00 LAB_0804850a XREF[1]: 08048517 (j) 0804850a 88 14 03 MOV byte ptr [EBX + EAX *0x1 ],DL 0804850d 0f b6 54 MOVZX EDX ,byte ptr [ECX + EAX *0x1 + 0x1 ] 01 01 08048512 83 c0 01 ADD EAX ,0x1 08048515 84 d2 TEST DL,DL 08048517 75 f1 JNZ LAB_0804850a LAB_08048519 XREF[1]: 08048503 (j) 08048519 5b POP EBX 0804851a 5d POP EBP 0804851b c3 RETComment: param_1 is dest, param_2 is src. 08048501 checks if src is null and if it is it returns else it initializes EAX (index, current_character) with 0. The next instructions move bytes into EBX (dest) from EDX (src).The loop stops when EDX is null.

Y la otra función FUN_0804851c genera la contraseña a partir de la cadena “THEPASSWORDISEASYTOCRACK”. Mirando la vista descompilada. podemos ver aproximadamente cómo funciona esta función. Si no tuviéramos eso, tendríamos que analizar manualmente cada instrucción de ensamblaje de la función para comprender qué hace.

Luego, comparamos la contraseña generada previamente con la contraseña que obtuvimos del usuario (el primer argumento, argv [1]). Si coincide, el programa dice buen trabajo y lo imprime; de ​​lo contrario, imprime un mensaje de error.

A partir de este análisis básico, podemos concluir que si parcheamos el programa en varios lugares, podemos hacer que escupe la contraseña sin necesidad de revertir ninguna función C y escribir código. Parchear el programa significa cambiar algunas de sus instrucciones.

Veamos qué tenemos que parchear:

En la dirección 0x0804868c parcheamos la instrucción JNS en un JMP. Y voilà, el cambio se refleja en la vista del descompilador. Se omite la comprobación del resultado de ptrace.

{ ptrace(PTRACE_TRACEME,0,1,0); if (argc != 2) { puts("You must give a password for use this program !"); /* WARNING: Subroutine does not return */ abort(); } CheckPassword(argv[1]); return 0;}

En la dirección 0x080485b8 parcheamos la instrucción JZ en un JMP. Omitimos ese bloqueo de verificación de contraseña que vimos anteriormente.

void CheckPassword(undefined4 param_1) { int iVar1; char local_108c [128]; char local_100c [4096]; CustomCopy(local_100c,param_1); GeneratePassword(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar1 = strcmp(local_108c,local_100c); if (iVar1 == 0) { printf("Good work, the password is : \n\n%s\n",local_108c); } else { puts("Is not the good password !"); } return; }

En la dirección 0x0804861e parcheamos JNZ a JZ. Esto invierte la condición if / else. Como no conocemos la contraseña, vamos a enviar una contraseña aleatoria que no es igual a la generada, ejecutando printf en el bloque else.

void CheckPassword(undefined4 param_1) { int iVar1; char local_108c [128]; char local_100c [4096]; CustomCopy(local_100c,param_1); // constructs the password from the strings and stores it in // local_108c GeneratePassword(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar1 = strcmp(local_108c,local_100c); if (iVar1 == 0) { // passwords are equal puts("Is not the good password !"); } else { printf("Good work, the password is : \n\n%s\n",local_108c); } return; }

¡Eso es todo!

Ahora ejecutamos el programa. En otras herramientas simplemente guardamos el archivo y funciona, pero en Ghidra parece que necesitamos exportarlo.

Para exportar el programa, vamos a Archivo -> Exportar programa (O). Cambiamos el formato a binario y hacemos clic en Aceptar.

Recibo el programa exportado en mi escritorio pero no funciona; no pude ejecutar el programa exportado. Después de intentar leer su encabezado con el programa readelf -h, obtengo el siguiente resultado:

[email protected]:/mnt/c/users/denis/Desktop# readelf -h Crack.bin ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048440 Start of program headers: 52 (bytes into file) Start of section headers: 2848 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 7 Size of section headers: 40 (bytes) Number of section headers: 27 Section header string table index: 26 readelf: Error: Reading 1080 bytes extends past end of file for section headers

Shame. It looks like Ghidra has messed up the file header… and, right now I don’t want to manually fix headers. So I fired up another tool and applied the same patches to the file, saved it, ran it with a random argument and validated the flag.

Conclusions

Ghidra is a nice tool with a lot of potential. In its current state, it’s not that great but it works. I’ve also encountered a weird scrolling bug while running it on my laptop.

The alternatives would be to pay $$ for other tools of this kind, make your own tools, or work with free but not so user friendly tools.

Let’s hope that once the code is released, the community will start doing fixes and improve Ghidra.

Thanks for reading!