Un blog de Seguridad Informática, desde un punto de vista tecnológico

Archivo para diciembre, 2013

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.

Screen Shot 2013-11-30 at 10.11.14 AM

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:

Screen Shot 2013-11-30 at 10.43.11 AM

Analizamos el punto donde se realiza la XOR:

Screen Shot 2013-11-30 at 10.52.05 AM

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:

Screen Shot 2013-11-30 at 11.04.07 AM

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.

Screen Shot 2013-11-30 at 1.23.41 PM

Screen Shot 2013-11-30 at 11.04.07 AM

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:

Screen Shot 2013-11-30 at 1.35.17 PM

Así que para finalizar, solo hay que obtener los bytes de la variable «not_flag»:

Screen Shot 2013-11-30 at 1.38.48 PM

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 🙂

Screen Shot 2013-11-30 at 1.45.23 PM

Disfrutamos mucho del CTF. Esperamos ansiosos poder participar en otro bien pronto.

Saludos!