Back to the basics: Reversing a simple password


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