by hdbreaker
Con la debida autorización de Ricardo Narvaja y los agradecimientos respectivos al grupo CrackLatinos se da comienzo al solucionario del ejercicio Stack1 del grupo CLS Exploits.
Enunciado:
Autor: Ricardo Narvaja ---------------------- Abrir el idb en ida y tratar de entender y mirar estaticamente el programa. Mirar la distancia exacta que se necesita para pisar la cookie y ponerla en el mail que me envian armar el script correcto y enviarmelo para que yo lo pruebe. Entre las cosas que se enviaron para bajar hay un esqueleto de script en python que puede servir cualquier duda ir al canal de telegram de exploit que alli estan en grupo trabajando en esto.
Binario: stack1.exe
Lo primero que debemos hacer en este ejercicio es abrir el idb con IDA PRO, y analizar estáticamente el programa en busca de la vulnerabilidad, para luego generar un exploit que se aproveche de la misma y logre controlar el flujo de la aplicación.
La estructura principal del programa es realmente simple y luce de la siguiente forma:
En la dirección 0x004012D4 comienza la sección que nos interesa:
En esta dirección podemos apreciar cómo el offset Format que contiene el string buf: %08x cookie: %08x\n se mueve dentro del puntero [esp+0D8], esto es una forma de pushear un argumento al stack, ya que el mismo sera utilizado por la función printf en la dirección 0x004012DB la cual se encarga de mostrar un mensaje con el formato especificado en pantalla.
En la dirección 0x004012E0 podemos ver como la instrucción lea eax, [ebp+Buffer] coloca el valor al que referencia el puntero [ebp+Buffer] en eax, el cual luego es movido al puntero [esp+0B8] en la dirección 0x004012E6 el cual identificamos como un push al stack anteriormente.
Este valor es enviado como argumento de la función gets, la cual es vulnerable a overflow ya que no valida el largo del buffer donde escribe en relación con la entrada dada por el usuario.
Luego del llamado a la función gets en la dirección 0x004012EE se encuentra la siguiente comparación:
cmp [ebp+var_C], 51525354
La misma modificara la conducta del salto condicional que se encuentra en la dirección 0x004012F5: jnz short locret_401303 (jz es equivalente a je)
Si la condición no se cumple (jmp if not equal) el salto condicional nos llevara al basic block que comienza en la dirección 0x00401303 el cual termina en un retn y concluye la aplicación.
Si la condición se cumple la aplicación sigue un flujo normal y llega al basic block que comienza en la dirección 0x004012F7 la cual coloca en el stack el mensaje "you are a winner man je\n" el cual es utilizado por la función printf para imprimir el mensaje solución del reto.
Teniendo esto en cuenta nuestro objetivo seria aprovecharnos de la función gets para enviar un valor mayor al esperado en Buffer con el fin de corromper el stack y lograr controlar el valor al que apunta el puntero [ebp+var_C]
Para lograr esto, primero debemos identificar el tamaño reservado para Buffer, IDA PRO nos permite hacer esto de forma sencilla de la siguiente forma:
1) Nos colocaremos sobre la variable Buffer y daremos doble click, esto nos situara en la vista Stack Frame View
2) Realizamos click derecho sobre Buffer y ingresamos en el submenu Array.
3) Aparecera la siguiente venta:
En esta ventana entran en juego 2 elementos Array element size y Array size, el tamaño del Buffer es equivalente a la multiplicación de estos 2 elementos, debido a que Array element size en este caso es 1 el tamaño de Buffer es igual a 140 bytes. Es importante destacar que debemos cerrar esta ventana clickeando el botón Cancelar.
Teniendo en cuenta lo anterior sabes que para desbordar Buffer debemos enviar un string mayor a 140 bytes, pero esto no es suficiente, debemos desbordar el buffer hasta el punto que logremos sobrescribir el valor de contenido en var_C.
Para calcular cuantos bytes más debemos enviar debemos bajar en el Stack Frame View hasta localizarnos en la próxima variable adyacente a Buffer.
Con esta información podemos apreciar que este ejercicio tiene 2 posibles soluciones:
1) Sobrescribir 140 bytes + 4 bytes (var_C = 0x51525354)
2) Sobrescribir 140 bytes + 4 bytes (var_C) + 8 bytes + 4 bytes (s => ebp) + 4 bytes (r => eip)
1) Sobrescribir 140 bytes + 4 bytes (var_C = 0x51525354)
2) Sobrescribir 140 bytes + 4 bytes (var_C) + 8 bytes + 4 bytes (s => ebp) + 4 bytes (r => eip)
Dicho esto procederemos a solucionar el ejercicio de ambas formas:
1) Sobrescribir 140 bytes + 4 bytes (var_C = 0x51525354)
import struct from subprocess import Popen, PIPE stdin = Popen("stack1.exe", stdin=PIPE, stdout=PIPE) buffer = "\x90"*140 # => Buffer length var_c = struct.pack("<I", 0x51525354) payload = buffer + var_c # => jmp equal true print "[#] Sending Payload" stdout = stdin.communicate(payload)[0] print stdout
Resultado:
2) Sobrescribir 140 bytes + 4 bytes (var_C) + 8 bytes + 4 bytes (s => ebp) + 4 bytes (r => eip)
Esta solución es más complicada y se puede lograr debido a que el binario no posee ASLR.
Al lograr controlar el valor del registro de retorno (EIP) somos capaces de alterar el flujo de ejecución del programa a nuestro gusto, por lo que podrÃamos decirle al programa que llegado el ultimo ret no finalice su ejecución, sino que siga su flujo llamando a la dirección 0x004012F7, donde comienza el basic block encargado de imprimir el mensaje "you are a winner man je\n".
Quedando nuestro exploit con la siguiente estructura:
import struct from subprocess import Popen, PIPE stdin = Popen("stack1.exe", stdin=PIPE, stdout=PIPE) buffer = "\x90" * 140 # Buffer length var_c = "\x41" * 4 junk = "\x41" * 8 s = "\x42" * 4 # JUNK r = struct.pack("<I", 0x004012F7) # => Control over EIP payload = buffer + var_c + junk + s + r # => jmp if not equal print "[#] Sending Payload" stdout = stdin.communicate(payload)[0] print stdout
El anterior programa deberÃa funcionar sin problemas, pero al ejecutarlo obtenemos el siguiente error:
import struct from subprocess import Popen, PIPE stdin = Popen("stack1.exe", stdin=PIPE, stdout=PIPE) buffer = "\x90" * 140 # Buffer length var_c = "\x41" * 4 junk = "\x41" * 8 s = "\x42" * 4 # JUNK r = struct.pack("<I", 0x004012F7) # => Control over EIP payload = buffer + var_c + junk + s + r # => jmp if not equal print "[#] Sending Payload" raw_input("Send any key to continue") stdout = stdin.communicate(payload)[0] print stdout
Esto lo haremos con el fin de poder attacharnos al programa antes de enviar el payload que produce el crash, para esto ejecutamos el programa:
Esto nos abrira una ventana en la cual debemos seleccionar el programa al cual attacharnos:
Seleccionamos el programa y presionamos el botón "OK", esto nos llevara hacia nuestro Debugger:
Llegado este punto debemos presionar "F9" y luego dirigirnos a nuestra terminal y continuar la ejecución:
Precionamos Ctrl+S para entrar al listado de segmentos:
import struct from subprocess import Popen, PIPE stdin = Popen("stack1.exe", stdin=PIPE, stdout=PIPE) buffer = "\x90" * 140 # Buffer length var_c = "\x41" * 4 junk = "\x41" * 8 s = struct.pack("<I", 0x004050A8) r = struct.pack("<I", 0x004012F7) # => Control over EIP payload = buffer + var_c + junk + s + r # => jmp if not equal print "[#] Sending Payload" raw_input("Send any key to continue") stdout = stdin.communicate(payload)[0] print stdout
Largamos el exploit, nos volvemos a attachar con IDA y nos posicionamos sobre el ultimo ret
Resultado:
Alternativas:
Se logran ejecutar dos acciones diferentes utilizando como registro alternativo EBP este registro nos permite mover el stack a nuestro gusto, por lo que luego de modificar el ultimo ret podrÃamos alterar el flujo de ejecución nuevamente alineando de forma correcta EBP y de esta forma controlar por segunda ves EIP sin necesidad de ROP.
PoC:
import struct from subprocess import Popen, PIPE stdin = Popen("stack1.exe", stdin=PIPE, stdout=PIPE) exit = struct.pack("<I", 0x75504416) # =>Dir KERNELBASE.EXITPROCESS buffer = "\x90" * 136 # => Buffer length var_c = "\x41" * 4 junk = "\x41" * 8 s = struct.pack("<I", 0x0022FEAC) # => Fix EBP jumping to exit r = struct.pack("<I", 0x004012F7) # => Control over EIP and jmp to printf good message payload = exit + buffer + var_c + junk + s + r # => jmp if not equal print "[#] Sending Payload" stdout = stdin.communicate(payload)[0] print stdout
Se controla EIP y se apunta al basic block que genera el mensaje buscado:
Resultado:
Debido a que el binario no pose DEP ni ASLR se podrÃa ingresar una shellcode en nuestro input, localizarlo en el stack y saltar a su dirección controlando el flujo mediante EIP y de esta forma ejecutar código arbitrario, pero esto quedara para próximas entradas.
Sin más me despido dejando la primer entrada de esta serie de soluciones, espero sea de su agrado.
Saludos!