Introducción al Reversing – 0x0A Hack.lu CTF 2018, baby reverse

Reversing

Muy buenas a todos, hacía tiempo que no redactaba un tutorial debido a mi escaso tiempo últimamente. Pero como no puede ser de otra manera mi compromiso con la comunidad me hace escribir y poder compartir conocimiento en este caso sobre ingenieria inversa.

Seguimos con esta saga de tutoriales sobre el uso de radare2 en la cual veremos un reto del CTF del congreso de seguridad hack.lu de este año. Es un reto muy fácil de hecho, esta en la categoría baby de la lista de reversing donde alojo los diferentes retos con sus soluciones que voy haciendo. https://github.com/naivenom/reversing-list

Con este reto crackme aprenderemos básicamente dos cosas:

  • Averiguar el algoritmo para poder obtener la password
  • Scripting usando r2pipe

Primero deberemos testear el binario para saber la iteracción como usuarios y ver como poder empezar a reversearlo.

naivenom@vuln64:~/Escritorio/RE/hacklu$ ./chall 
Welcome to this Chall! 
Enter the Key to win: aa

Vemos que nos pide una key pasado por stdin.  Abrimos el framework radare2 en modo debugging y desensamblamos el entrypoint:

[0x00400080]> pd
            ;-- section..text:
            ;-- segment.LOAD0:
            ;-- rip:
/ (fcn) entry0 72
|   entry0 (int arg_6eh);
|           ; arg int arg_6eh @ rbp+0x6e
\           0x00400080      eb50           jmp loc.004000d2            ; [01] -rwx section size 186 named .text

Realiza un salto a una localización del código y seguidamente llama a una función.

loc.004000d2 (int arg1, int arg_6eh);
|      ||   ; arg int arg_6eh @ rbp+0x6e
|      ||   ; arg int arg1 @ rdi
|      ||   ; CODE XREF from entry0 (0x400080)
|      ||   0x004000d2      e8abffffff     call fcn.00400082

Justo en la función observamos una string podríamos localizarla usando el siguiente comando con radare2

[0x00400080]> / Ya
Searching 2 bytes in [0x400000-0x401000]
hits: 1
0x004000c0 hit0_0 .~HwHuI4/hYay!H4.

Si analizamos un poco el código a grandes rasgos necesitamos que nos imprima por pantalla esa string y sería un win por lo tanto deberemos reversear las instrucciones anteriores a la instrucción test rcx, rcx
Pondremos un breakpoint [0x00400080]> db 0x00400082 justo al inicio de la función fcn.00400082 y veremos debuggeando si encontramos la forma de obtener nuestra key.
Despues del segundo syscall nos pide un input correspondiente al mismo que vimos testeando el binario introducimos como prueba AAAA

0x00400096      0f05           syscall
0x00400098      480fb67e01     movzx rdi, byte [rsi + 1]       ; [0x1:1]=255 ; 1 ; arg2
0x0040009d      48313e         xor qword [rsi], rdi
0x004000a0      48ffc6         inc rsi                         
0x004000a3      48ffca         dec rdx
0x004000a6      75f0           jne 0x400098                ;[2]         

En la siguiente instrucción movzx rdi, byte [rsi + 1] mueve un byte correspondiente a nuestro input, es decir 0x41.
La siguiente tenemos una operación XOR xor qword [rsi], rdi del contenido de nuestro input, es decir, el byte anterior en el registro rdi y el contenido de la dirección de memoria correspondiente al registro rsi. Podemos fácilmente ver el contenido de esa dirección de memoria usando el siguiente comando.

[0x00400080]> px@rsi
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x004000d7  4141 4141 0a6d 6520 746f 2074 6869 7320  AAAA.me to this 
0x004000e7  4368 616c 6c21 200a 456e 7465 7220 7468  Chall! .Enter th
0x004000f7  6520 4b65 7920 746f 2077 696e 3a20 0031  e Key to win: .1
0x00400107  c0b0 3c0f 050a 0d06 1c22 3818 2636 0f39  ..<......"8.&6.9
0x00400117  2b1c 5942 2c36 1a2c 261c 172d 3957 4301  +.YB,6.,&..-9WC.
0x00400127  072b 3809 071a 0117 1313 172d 390a 0d06  .+8........-9...
0x00400137  465c 7d00 2e73 6873 7472 7461 6200 2e74  F\}..shstrtab..t
0x00400147  6578 7400 0000 0000 0000 0000 0000 0000  ext.............

Si os dais cuenta esta haciendo una operación con el mismo input que introdujimos pero no nos dimos cuenta de una cosa, y es que en la instrucción anterior al xor es rsi+1 por lo tanto si hubiésemos introducido ABCD seria un xor de 0x42 con 0x41, por lo tanto es una operación usando el mismo input pero con +1 (en posición) en el segundo operando. Continuamos e incrementa a uno la dirección contenida en el registro rsi y luego decrementa rdx saltando al inicio del bucle hasta llegar a cero. Ese decremento podemos deducir que es la longitud de la password siendo de 46 de largo usando la utilidad de radare2 ? 0x2e
Después de la finalización del bucle en la instrucción lea rdi, [rsi + 7] mueve la dirección de memoria de un contenido que aún desconocemos correspondiendo a:

[0x00400098]> px@rdi
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0040010c  0a0d 061c 2238 1826 360f 392b 1c59 422c  ...."8.&6.9+.YB,
0x0040011c  361a 2c26 1c17 2d39 5743 0107 2b38 0907  6.,&..-9WC..+8..
0x0040012c  1a01 1713 1317 2d39 0a0d 0646 5c7d 002e  ......-9...F\}..
0x0040013c  7368 7374 7274 6162 002e 7465 7874 0000  shstrtab..text..

En las dos siguientes instrucciones mueve la dirección de memoria al registro rsi cuyo contenido es el resultado de la operación xor anterior y luego su correspondiente comparación byte a byte con el registro rdi que podemos deducir ahora mismo que es una key que usa el programa para verificar si lo introducido por stdin por el usuario junto con la

;-- rip:
0x004000b2      488d77cb       lea rsi, [rdi - 0x35]
0x004000b6      f3a6           repe cmpsb byte [rsi], byte ptr [rdi]
0x004000b8      4885c9         test rcx, rcx

operación xor, es valido o no. Por tanto sabiendo que la operación xor es reversible podríamos obtener la password haciendo uso de la key encontrada hardcoded en el binario.

[0x00400096]> px@rsi
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  0123456789ABCD
0x004000d7  0301 074e 6708 4554 1b4f 541c 011a  ...Ng.ET.OT...
0x004000e5  5363 2b09 0d00 4d01 2a4f 2b1a 1117  Sc+...M.*O+...
0x004000f3  5254 1c0d 456b 2e1c 5954 1b4f 571e  RT..Ek..YT.OW.
0x00400101  0754 1a20 0031 c0b0 3c0f 050a 0d06  .T. .1..<.....
0x0040010f  1c22 3818 2636 0f39 2b1c 5942 2c36  ."8.&6.9+.YB,6
0x0040011d  1a2c 261c 172d 3957 4301 072b 3809  .,&..-9WC..+8.

Comprobamos que rsi es el resultado de la operación xor debido a que ? 0x42^0x41= 0x3
Para key[0] , deberemos encontrar un valor que nuestro input[0] ^ input[0+1] = 0xa. Debemos estimar para el primero ya que no tenemos ningún valor y existen muchas posibilidades para que el resultado del xor sea 0xa, sin embargo para los siguientes ya no porque es un input[n+1] y siempre sabremos input[n].
Por lo tanto tendríamos algo así:

0xa = input[0] ^ input[1]
0xd = input[1] ^ input[2]
0x6 = input[2] ^ input[3]
…

Scripting con r2pipe

Posible solución al reto usando r2pipe:

import random
import sys
import r2pipe
import time

'''profile: chall.rr2
#!/usr/bin/rarun2
program=./chall
stdin="AAAA"
stdout=
'''

r = r2pipe.open("./chall")
r.cmd('e dbg.profile=chall.rr2')
r.cmd('doo') # initially you are debugging rarun2
r.cmd('db 0x0040008e')
r.cmd('dc')
print r.cmd('drj')
print (chr(27) + "[0;33m" + "[+]Key: "+chr(27) + "[0m")
key = r.cmdj('pxj 46@%s'%0x0040010c)
print(key)
key_value = 10

def check_key(key):
    char_sum = 0
    for c in key:
        char_sum += ord(c)
    sys.stdout.write("{0:3} | {1}      \r".format(char_sum, key))
    sys.stdout.flush()
    return char_sum

timeout = time.time()+1
print (chr(27) + "[1;36m" + "[+] STAGE 1 - RECON: Estimate first values" + chr(27) + "[0m")
while True:
    test = 0
    if test == 1 or time.time() > timeout:
        break
    a = ""
    a += random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_!{}")
    b = ""
    b += random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_!{}")
    rand_a = check_key(a)
    rand_b = check_key(b)


    xored = rand_a ^ rand_b
    if key_value == xored:
        print (chr(27) + "[0;33m" + "\n\tEstimate values: %s, %s "%(chr(rand_a),chr(rand_b))+chr(27) + "[0m")
    test = test-1

print (chr(27) + "[1;36m" + "[+] STAGE 2 - SOLVE" + chr(27) + "[0m")
input_zero = raw_input("Input[0]>>>")
input_one = raw_input("Input[1]>>>")
input_n = ord(input_one)
flag = []
for i in range(len(key)-1):
    input_n = key[i+1]^input_n
    flag.append(chr(input_n))
print("".join(input_zero)+"".join(input_one)+"".join(flag))


'''
<output>
108 | l      
	Estimate values: f, l
 70 | F      
	Estimate values: L, F 
 90 | Z      
	Estimate values: P, Z 
113 | q      
	Estimate values: {, q 
[+] STAGE 2 - SOLVE
Input[0]>>>f
Input[1]>>>l
flag{Yay_if_th1s_is_yer_f1rst_gnisrever_flag!}
'''

Un saludo, @naivenom