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