By Jesus
Dominguez aka n0tM4l4f4m4 @chucho_domz
Reto de la categoría de 'reverse engineering' del TUCTF 2019
Challenge
We've recovered this file but can't make much of it.
File recognition
Una vez descargado el archivo empezaremos con el reconocimiento
de este.
Comando 'file'.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# file run.o
run.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Al correr el comando podemos observar que es un archivo compilado pero no ejecutable, este tipo de archivos se denominan 'Object file' y contiene todas las instrucciones de bajo nivel que el CPU puede entender.
Posteriormente haremos de este archivo un ejecutable.
Vamos a revisar los ‘strings’ del archivo.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# strings run.o =;75 Close, but no flag Incorrect password Error at character: %d Correct Password! Enter password: %64s pass: %s GCC: (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008 run.c passlen password checkPassword _GLOBAL_OFFSET_TABLE_ strlen puts printf main stdout setvbuf stdin malloc memset __isoc99_scanf .symtab .strtab .shstrtab .rela.text .data .bss .rodata .rela.data.rel.local .comment .note.GNU-stack .note.gnu.property .rela.eh_frame
Vemos que nos muestra los nombres de lo que parecen ser
funciones locales y otras llamadas al sistema.
Procederemos “linkear” el archivo objeto para hacerlo
ejecutable. Esto lo haremos con GCC.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# gcc -o run run.o
Esto
nos genera un archivo ejecutable 'run '.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# file run
run: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ca3a52375a57f83dda4a902e60ac44f38b82621d, for GNU/Linux 3.2.0, not stripped
Vamos
a ejecutarlo para ver que sucede.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# ./run
Enter password:
> 12345
pass: 12345
Close, but no flag
Nos pide un 'password' y a la hora de meterlo nos imprime nuestro ‘input’ y nos dice ‘Close, but no flag’. Que es una de las 'strings' que vimos anteriormente. Con esto deducimos que al meter el 'password' correctamente nos da la 'flag'.
Finalmente
usaremos 'ltrace' para ver las llamadas que hace el ejecutable.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# ltrace ./run
setvbuf(0x7faa5d7dd760, 0, 2, 20) = 0
setvbuf(0x7faa5d7dca00, 0, 2, 20) = 0
malloc(64) = 0x556177ab8260
memset(0x556177ab8260, '\0', 64) = 0x556177ab8260
printf("Enter password:\n> "Enter password:
> ) = 18
__isoc99_scanf(0x556176091098, 0x556177ab8260, 0x7faa5d7df580, 012345
) = 1
printf("pass: %s\n", "12345"pass: 12345
) = 12
strlen("12345") = 5
puts("Close, but no flag"Close, but no flag
) = 19
+++ exited (status 1) +++
Como podemos observar, nos imprime le mensaje para meter el 'password', después llama a 'scanf', 'strlen' y finalmente a 'puts', para el mensaje
final.
Analisys
Para esta etapa usaremos IDA 7.0 para decompilar y usaremos
el archivo run.o ya que es el mas simple al estar directamente ya con las
instrucciones de bajo nivel.
Al decompilar vemos las siguientes funciones. (Fig1)
(Fig1) |
Tenemos el 'main' del programa y una función llamada ‘checkPassword’
Procederemos con el ‘main’ . (Fig2)
(Fig2) |
Al ver el ensamblador de esta función vemos que corresponde
a la lógica que vimos al ejecutar el programa y notamos que después del ‘scanf’
pasa nuestro 'input' a la función ‘printf’ para posteriormente pasarlo a la
función ‘checkPassword’ . (Fig.3)
(Fig.3) |
Al echar un vistazo rápido a esta función vemos que tienes
un ciclo 'for' y un par que condiciones que nos llevan a un 'password' correcto o
incorrecto. (Fig4).
(Fig4) |
Pues con esto en mente empezaremos a analizar el ensamblador desde
el principio de la función.
Al empezar vemos las líneas que ya conocemos que son las que
pasan los valores de RDI a las variables locales, teniendo la variable local [rbp-18h]
como nuestro 'input'.
Posteriormente este es pasado a la función 'strlen' que nos
retorna el largo del 'string', para guardarlo en [rbp-4], siguiendo con la comparación
de un valor guardado en CS (code segment) que es movido a EAX y si estos no son
iguales el programa termina su ejecución. (Fig5)
(Fig5) |
Al inspeccionar el CS vemos que el valor que es movido a EAX
es 2Ch, esto en decimal es 44,con lo que concluimos que el largo del 'password' debe de ser de 44 caracteres. (Fig6)
(Fig6) |
Después de estos seguimos por el camino donde la condición se
cumple, para llegar a un ciclo for de 0 hasta el valor de passlen (2C). (Fig7)
(Fig7) |
Donde [rbp-8] será nuestro iterador.
Ya dentro del ciclo básicamente tenemos 3 pasos.
El primero es pasar uno por uno todos los caracteres de
nuestro 'input' a la variable [rbp-A0h].(Fig8)
(Fig8) |
Posteriormente se hace lo mismo con valor guardado en el CS
llamado 'password', este se pasa a la variable [rbp-9].(Fig9)
(Fig9) |
Al inspeccionar que hace en cs:password, vemos que tenemos
la dirección de un 'offset'. (Fig10)
(Fig10) |
Y al ir hacia esta tenemos lo siguiente:(Fig11)
(Fig11)
|
Tenemos 44 bytes de información, pero como podemos ver solo
algunos tienen representación ASCII, aun así veremos que nos servirán en un
paso adelante.
El tercer paso son una serie de operaciones a nivel binario con
el caracter de nuestro 'input', para posteriormente compararla con el 'byte' del 'password' del code segment, en caso de que no sea correcto nos imprime un
mensaje y termina la ejecución, caso contrario continua con la iteración. (Fig12)
(Fig12) |
Observamos que las operaciones son:
- Shitf left 1
- Not
- Xor AAh
Nótese la palabra byte, esto quiere decir que son
operaciones con 8 bits.
Después de las operaciones viene la comparación, esto quiere
decir que el 'byte' de nuestro 'input' después de todas estas operaciones tiene que
ser igual al 'byte' de cs:password.
Solution
Con todo lo anterior y el análisis de la lógica procedemos
con el siguiente 'script' para encontrar el 'password'.
#!/usr/bin/python # -*- coding: utf-8 -*- ## Author: Jesus Dominguez aka n0tM4l4f4m4 import numpy as np bytes = [0xFD,0xFF,0xD3,0xFD,0xD9,0xA3,0x93,0x35,0x89,0x39, 0xB1,0x3D,0x3B,0xBF,0x8D,0x3D,0x3B,0x37,0x35,0x89, 0x3F,0xEB,0x35,0x89,0xEB,0x91,0xB1,0x33,0x3D,0x83, 0x37,0x89,0x39,0xEB,0x3B,0x85,0x37,0x3F,0xEB,0x99, 0x8D,0x3D,0x39,0xAF] #bytes code segmente password flag = '' for value in bytes: for i in range(256): #iteramos desde 0 hasta 255, 8 bits res = (~(i << 1)) ^ 0xAA #operaciones con el input if np.int8(res) == np.int8(value): #condicion flag += chr(i) break pass pass pass print flag
Ejecutamos el 'script'.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# ./solver.py
TUCTF{c0n6r47ul4710n5_0n_br34k1n6_7h15_fl46}
Probamos el 'password' en el ejecutable.
root@n0tM4l4f4m4:~/Documents/CTFsGames/2019/tuCTF/rev/object# ./run
Enter password:
> TUCTF{c0n6r47ul4710n5_0n_br34k1n6_7h15_fl46}
pass: TUCTF{c0n6r47ul4710n5_0n_br34k1n6_7h15_fl46}
Correct Password!
Flag
TUCTF{c0n6r47ul4710n5_0n_br34k1n6_7h15_fl46}
Go Mayas!
Comentarios
Publicar un comentario