Solución reto Australia – Final CTF NcN 2013
A continuación la solución al reto de Australia, que por desgracia no nos dio tiempo a resolver en el mismo CTF, pero que hemos resuelto posteriormente y ahora os exponemos.
Al acceder a la IP del servidor correspondiente al país «Australia», se podía descargar un binario para proceder a su análisis. El binario era un ejecutable de Linux (ELF), que pedía introducir una clave que, principalmente, consistía en el flag que debíamos introducir para obtener la puntuación del juego.
En el CTF optamos por hacer un reversing estático del programa con IDA, y para ganar tiempo, se obtuvo el código C mediante el empleo de HexRays:
signed int __cdecl main() { signed int result; // eax@2 int v1; // [sp+1Ch] [bp-4h]@1
print_header(); v1 = malloc(goodboy_len); if ( v1 ) { sub_80481A0(); if ( fgets(v1, goodboy_len - 1, stdin[0]) ) { if ( check_buffer(v1, goodboy_len - 2) ) puts("Winner! Post your flag."); else fwrite("Sorry, that is not correct.\n", 1, 28, stderr); result = 0; } else { fwrite("Could not read user input.\n", 1, 27, stderr); free(v1); result = 2; } } else { fwrite("Could not allocate buffer.\n", 1, 27, stderr); result = 1; } return result; }
signed int __cdecl check_buffer(int a1, unsigned int a2) { unsigned int i; // [sp+Ch] [bp-4h]@1
for ( i = 0; i < a2; ++i ) { if ( not_flag[i] != (*(_BYTE *)(a1 + i) ^ 0xCC) ) return 0; } return 1; }
Si analizamos el código, se puede observar que el resultado del programa depende del valor de retorno de la función check_buffer(), en la cual el valor del flag XOR’ed con el valor 0xCC debe ser igual al array «not_flag»:
not_flag[i] != (*(_BYTE *)(a1 + i) ^ 0xCC)
Por lo que para calcular el valor del flag, no hay más que conmutar la función XOR:
0xCC ^ not_flag[i] != (*(_BYTE *)(a1 + i)
Cabe considerar que la entrada se comprueba BYTE a BYTE, pero eso ya lo analizaremos con detalle más adelante.
Después de varios intentos, pudimos comprobar que el flag no era correcto, por lo que algo fallaba.
Por lo que vamos a utilizar GDB para hacer un análisis dinámico y ver realmente que es lo que se estaba fallando.
Desensamblamos la función check_buffer:
Analizamos el punto donde se realiza la XOR:
El BYTE con el cual se realiza una XOR con la entrada del usuario se almacena en la posición -0x7(%ebp).
El siguiente código corresponde al cálculo del valor que será utilizado para la operación XOR:
0x08048433 <+25>: movl $0xcd000000,-0xc(%ebp) --> -0xc(%ebp) = 0xcd000000 [...] 0x0804843c <+34>: mov -0xc(%ebp),%eax --> EAX = 0xcd000000 0x0804843f <+37>: shr $0x18,%eax --> Shift_Right(EAX,0x18) EAX = 0xCD 0x08048442 <+40>: mov %al,-0x7(%ebp) --> -0x7(%EBP) = 0xCD 0x08048445 <+43>: movzbl -0x7(%ebp),%eax --> %eax = 0xCD 0x08048449 <+47>: and $0xf,%EAX--> %eax = 0xd 0x0804844c <+50>: mov %al,-0x5(%ebp) --> -0x5(%ebp) = 0x0d 0x0804844f <+53>: movzbl -0x7(%ebp),%eax --> %eax = 0xcd 0x08048453 <+57>: and $0xfffffff0,%eax --> %eax = 0xc0 0x08048456 <+60>: mov %al,-0x6(%ebp) --> -0x6(%ebp) = 0xc0 0x08048459 <+63>: movzbl -0x6(%ebp),%eax --> %eax = 0xc0 0x0804845d <+67>: mov %eax,%edx --> %edx = 0xc0 0x0804845f <+69>: shr $0x4,%dl --> %dl (0-7bit) = 0xc 0x08048462 <+72>: movzbl -0x5(%ebp),%eax --> %eax = 0x0d 0x08048466 <+76>: shl $0x4,%eax --> %eax = 0xd0 0x08048469 <+79>: add %edx,%eax --> %eax = 0xdc 0x0804846b <+81>: mov %al,-0x7(%ebp) -->0x7(%ebp) = 0xdc
Con GDB no es necesario realizar todos los cálculos, sino únicamente con insertar un breakpoint en el punto en el que se realiza la XOR y ver qué hay almacenado en el registro %eax:
Por lo que, en lugar de hacer una XOR con el valor 0xCC como se indicaba en el código, realmente se hace una XOR con el valor 0xDC.
Llegados a este punto, únicamente nos queda definir el formato mediante el cual se ha de introducir el flag.
Según el código analizado, los valores introducidos por el usuario se almacenan en el registro %eax como resultado de la operación XOR, y los valores son comparados byte a byte mediante el uso del registro %al.
Los valores son comparados en formato hexadecimal, en cambio los valores son obtenidos de la entrada del usuario mediante la función fgets, por lo que los datos son obtenidos en formato ASCII. Por este motivo, para obtener los valores del flag hay que hacer una XOR del byte de la variable «not_flag» con el valor 0xDC y hacer una conversión a ASCII, tal y como se muestra a continuación:
Así que para finalizar, solo hay que obtener los bytes de la variable «not_flag»:
Y automatizar el proceso para obtener el valor del flag:
#!/usr/bin/ruby
not_flag = [0xeb, 0xe8, 0xbf, 0xe4, 0xea, 0xbe, 0xba, 0xe4, 0xe5, 0xea, 0xe8, 0xea, 0xe8, 0xee, 0xe9, 0xba, 0xea, 0xe8, 0xeb, 0xba, 0xbf, 0xba ,0xeb, 0xea, 0xe8, 0xef, 0xbd, 0xba, 0xed, 0xe9, 0xba, 0xee, 0xe9, 0xed, 0xbe, 0xed, 0xe4, 0xea, 0xbe, 0xba, 0xe9, 0xe4, 0xbd, 0xea, 0xb8, 0xe9, 0xb8, 0xbf, 0xeb, 0xb9, 0xbe, 0xe4, 0xbe, 0xba, 0xe5, 0xbf, 0xba, 0xbf, 0xe5, 0xb8, 0xec, 0xe8, 0xbf, 0xb8]
puts "Solucion: " not_flag.each { v | p (0xdc ^ v).chr } puts
Y finalmente, podremos verificar que el flag introducido es correcto 🙂
Disfrutamos mucho del CTF. Esperamos ansiosos poder participar en otro bien pronto.
Saludos!
Deja una respuesta