Saltar a contenido

Shellcoding

Shellcode

En la mayoría de los casos, los programas que queremos explotar no tendrán simplemente funciones tipo "win" o "get shell". Sin embargo, aprovechando los conceptos que hemos aprendido sobre corrupción de memoria, podemos suministrar nuestro propio código y ejecutarlo.

Este nivel introducirá el concepto de shellcode. Shellcode típicamente se refiere a pequeños fragmentos escritos a mano en assembly cuyo propósito principal es ejecutar un shell mediante una explotación basada en corrupción de memoria.

Resultados de aprendizaje

  • Familiarizarse con el concepto de shellcode
  • Aprender cómo se realizan los syscalls a nivel de assembly

Shellcoding

En el capítulo anterior vimos cómo la corrupción de memoria dirigida puede usarse para influir en la ejecución del programa. En este capítulo, ampliaremos ese concepto inyectando nuestro propio código en un programa en ejecución. "Inyectar" código vía corrupción de memoria de esta manera se llama shellcoding.

Este binario coloca shellcode en la stack, y luego salta explícitamente hacia él. Ejecuta (run) el binario y observa qué ocurre.


Shellcode

El programa ejecutó un shell. Sin embargo, no hay nada en el código fuente que sugiera que este binario tenga esa capacidad. En su lugar, la variable shellcode contiene código assembly pre-compilado que ejecuta un shell. Investiguemos esto más a fondo: pon un breakpoint en la instrucción call rax en main, luego run el programa. Shellcode utilizado: https://www.exploit-db.com/exploits/36858


Execve: comando no encontrado

Parece que estás intentando ejecutar un comando, ya sea vía system() o directamente vía sys_execve, que es inválido o no está disponible.

En la mayoría de los casos, esto ocurre cuando uno de los argumentos que proporcionaste es incorrecto o está corrupto. La mejor forma de avanzar es poner un breakpoint justo antes de intentar ejecutar el comando y verificar que todos tus argumentos se estén pasando como esperas.

Presta especial atención en caso de que tus argumentos estén almacenados en la stack u otra región de memoria potencialmente volátil. Un problema bastante común es que el shellcode, funciones de librería (como system()), o incluso efectos secundarios de ROP gadgets modifiquen la memoria durante la ejecución de tu exploit y resulten en un argumento corrupto.


Ahora, el programa está a punto de ejecutar el shellcode llamando a la dirección en $rax. Confirmemos que los datos en $rax coinciden con la variable shellcode.

wdb> x/23bx $rax

Fíjate que los bytes coinciden con los datos que están en la variable shellcode.

Echemos un vistazo a qué instrucciones corresponden a esos mismos bytes:

wdb> x/10i $rax

Syscalls

Ahora estás viendo la versión "legible" del contenido de la variable shellcode.

Este código se está preparando para hacer un syscall. Puedes pensar en los syscalls como si fueran una API hacia el Sistema Operativo. Son la forma en que los programas en userland piden al kernel que realice ciertas acciones.

Específicamente, este shellcode realiza una llamada a sys_execve. Este syscall reemplaza el programa que se está ejecutando actualmente por uno nuevo, en nuestro caso, /bin/sh.

Al igual que las funciones, los syscalls también tienen una convención de llamadas. Veamos eso más de cerca:

Pon un breakpoint en la instrucción syscall, luego continue hasta ella. Una vez allí, ejecuta

wdb> info registers

wdb> break *0x4006b6

Breakpoint will be set at 0x4006b6

wdb> run

Started '04_execve'

------------------------------------------------------------

--[ Shellcoding - Execve Shellcode (x64)                    

------------------------------------------------------------

Calling shellcode...

Breakpoint 1: 0x4006b6, main+121

wdb> x/23bx $rax

0x7fffffffedb0: 0x31    0xf6    0x48    0xbb    0x2f    0x62    0x69    0x6e

0x7fffffffedb8: 0x2f    0x2f    0x73    0x68    0x56    0x53    0x54    0x5f

0x7fffffffedc0: 0x6a    0x3b    0x58    0x31    0xd2    0x0f    0x05

wdb> x/10i $rax

0x7fffffffedb0: xor esi, esi

0x7fffffffedb2: movabs rbx, 0x68732f2f6e69622f

0x7fffffffedbc: push rsi

0x7fffffffedbd: push rbx

0x7fffffffedbe: push rsp

0x7fffffffedbf: pop rdi

0x7fffffffedc0: push 0x3b

0x7fffffffedc2: pop rax

0x7fffffffedc3: xor edx, edx

0x7fffffffedc5:
wdb> break *0x7fffffffedc5

Breakpoint 2 set at 0x7fffffffedc5

Breakpoint 2 set on writable data, debugger may behave unexpectedly

wdb> continue

Breakpoint 2: 0x7fffffffedc5

wdb> info registers

rax: 0x000000000000003b

rbx: 0x68732f2f6e69622f

rcx: 0x00000000fbad2887

rdx: 0x0000000000000000

rsi: 0x0000000000000000

rdi: 0x00007fffffffed98

rbp: 0x00007fffffffedd0

rsp: 0x00007fffffffed98

rip: 0x00007fffffffedc5

r8:  0x00007f0000026e40

r9:  0x0000000000000000

r10: 0x0000000000000194

r11: 0x00007f0000297690

r12: 0x0000000000400520

r13: 0x00007fffffffeeb0

r14: 0x0000000000000000

r15: 0x0000000000000000

fs:  0x0000000000000000

gs:  0x0000000000000000

eflags: 0x0000000000000044 [ PF ZF ]

Para realizar un syscall, necesitamos conocer su syscall number, y luego proporcionar los parámetros apropiados para cualquier argumento que requiera. Para x86-64 Linux, la convención es similar a las llamadas a funciones regulares, con la adición de que el syscall number se pasa en $rax.

Syscall Number: $rax
Syscall Params: $rdi, $rsi, $rdx, $r10, $r8, $r9, ...

La "declaración" de la función para sys_execve es:

int execve(const char *filename, char *const argv[], char *const envp[]);

Podemos ver que $rax está establecido en 0x3b, y que $rsi y $rdx están en 0.

Vuelca el contenido de $rdi como una cadena usando el comando x:

wdb> x/s $rdi

0x7fffffffed98: "/bin//sh"

wdb>

$rdi está apuntando a la cadena /bin//sh. Podemos pensar en el syscall completo como

sys_execve("/bin//sh", NULL, NULL);

Como vimos, esto resulta en que el binario en ejecución sea reemplazado por /bin/sh.

La observación crítica es que este código estaba ubicado en la stack.

En las próximas lecciones combinaremos lo que aprendimos en el capítulo anterior (corrupción de memoria) con este concepto para tomar control arbitrario de programas vulnerables.