Como mencionó BSH, su shellcode no contiene los bytes del mensaje. Saltando al MESSAGE
etiqueta y llamando al GOBACK
rutina justo antes de definir el msg
byte fue un buen movimiento ya que la dirección de msg estaría en la parte superior de la pila como dirección de retorno que podría colocarse en ecx
, donde se almacena la dirección de msg.
Pero tanto el código suyo como el de BSH tienen una pequeña limitación. Contiene NULL bytes ( \x00 )
que se consideraría como el final de la cadena cuando el puntero de función no haga referencia a ella.
Hay una forma inteligente de evitar esto. Los valores que almacenas en eax, ebx and edx
son lo suficientemente pequeños como para escribirse directamente en los nibbles inferiores de los respectivos registros de una sola vez accediendo a al, bl and dl
respectivamente. El mordisco superior puede contener valor no deseado, por lo que puede eliminarse.
b8 04 00 00 00 ------ mov $0x4,%eax
se convierte
b0 04 ------ mov $0x4,%al
31 c0 ------ xor %eax,%eax
A diferencia del conjunto de instrucciones anterior, el nuevo conjunto de instrucciones no contiene ningún byte NULL.
Entonces, el programa final se ve así:
global _start
section .text
_start:
jmp message
proc:
xor eax, eax
mov al, 0x04
xor ebx, ebx
mov bl, 0x01
pop ecx
xor edx, edx
mov dl, 0x16
int 0x80
xor eax, eax
mov al, 0x01
xor ebx, ebx
mov bl, 0x01 ; return 1
int 0x80
message:
call proc
msg db " y0u sp34k 1337 ? "
section .data
Montaje y enlace :
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
Ahora extraiga el shellcode del binario hello:
$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
salida:
\xeb\x19\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xb3\x01\xcd\x80\xe8\xe2\xff\xff\xff\x20\x79\x30\x75\x20\x73\x70\x33\x34\x6b\x20\x31\x33\x33\x37\x20\x3f\x20
Ahora podemos tener nuestro programa controlador para iniciar el shellcode.
#include <stdio.h>
char shellcode[] = "\xeb\x19\x31\xc0\xb0\x04\x31\xdb"
"\xb3\x01\x59\x31\xd2\xb2\x12\xcd"
"\x80\x31\xc0\xb0\x01\x31\xdb\xb3"
"\x01\xcd\x80\xe8\xe2\xff\xff\xff"
"\x20\x79\x30\x75\x20\x73\x70\x33"
"\x34\x6b\x20\x31\x33\x33\x37\x20"
"\x3f\x20";
int main(int argc, char **argv) {
(*(void(*)())shellcode)();
return 0;
}
Hay ciertas funciones de seguridad en los compiladores modernos, como la protección NX, que evita la ejecución de código en el segmento o la pila de datos. Así que deberíamos especificar explícitamente el compilador para deshabilitarlos.
$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
Ahora el launcher
se puede invocar para iniciar el shellcode.
$ ./launcher
y0u sp34k 1337 ? $
Para shellcodes más complejos, habría otro obstáculo. Los kernels de Linux modernos tienen ASLR o Address Space Layout Randomization
Es posible que deba deshabilitar esto antes de inyectar el shellcode, especialmente cuando se trata de desbordamientos de búfer.
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
Cuando inyectas este shellcode, no sabes qué hay en message
:
mov ecx, message
en el proceso inyectado, puede ser cualquier cosa pero no será "Hello world!\r\n"
ya que está en la sección de datos mientras está descargando solo la sección de texto. Puedes ver que tu shellcode no tiene "Hello world!\r\n"
:
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";
Este es un problema común en el desarrollo de shellcode, la forma de solucionarlo es esta:
global _start
section .text
_start:
jmp MESSAGE ; 1) lets jump to MESSAGE
GOBACK:
mov eax, 0x4
mov ebx, 0x1
pop ecx ; 3) we are poping into `ecx`, now we have the
; address of "Hello, World!\r\n"
mov edx, 0xF
int 0x80
mov eax, 0x1
mov ebx, 0x0
int 0x80
MESSAGE:
call GOBACK ; 2) we are going back, since we used `call`, that means
; the return address, which is in this case the address
; of "Hello, World!\r\n", is pushed into the stack.
db "Hello, World!", 0dh, 0ah
section .data
Ahora descargue la sección de texto:
$ nasm -f elf shellcode.asm
$ ld shellcode.o -o shellcode
$ ./shellcode
Hello, World!
$ objdump -d shellcode
shellcode: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
8048060: e9 1e 00 00 00 jmp 8048083 <MESSAGE>
08048065 <GOBACK>:
8048065: b8 04 00 00 00 mov $0x4,%eax
804806a: bb 01 00 00 00 mov $0x1,%ebx
804806f: 59 pop %ecx
8048070: ba 0f 00 00 00 mov $0xf,%edx
8048075: cd 80 int $0x80
8048077: b8 01 00 00 00 mov $0x1,%eax
804807c: bb 00 00 00 00 mov $0x0,%ebx
8048081: cd 80 int $0x80
08048083 <MESSAGE>:
8048083: e8 dd ff ff ff call 8048065 <GOBACK>
8048088: 48 dec %eax <-+
8048089: 65 gs |
804808a: 6c insb (%dx),%es:(%edi) |
804808b: 6c insb (%dx),%es:(%edi) |
804808c: 6f outsl %ds:(%esi),(%dx) |
804808d: 2c 20 sub $0x20,%al |
804808f: 57 push %edi |
8048090: 6f outsl %ds:(%esi),(%dx) |
8048091: 72 6c jb 80480ff <MESSAGE+0x7c> |
8048093: 64 fs |
8048094: 21 .byte 0x21 |
8048095: 0d .byte 0xd |
8048096: 0a .byte 0xa <-+
$
Las líneas que marqué son nuestras "Hello, World!\r\n"
cadena:
$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!
$
Entonces nuestro envoltorio C será:
char code[] =
"\xe9\x1e\x00\x00\x00" // jmp (relative) <MESSAGE>
"\xb8\x04\x00\x00\x00" // mov $0x4,%eax
"\xbb\x01\x00\x00\x00" // mov $0x1,%ebx
"\x59" // pop %ecx
"\xba\x0f\x00\x00\x00" // mov $0xf,%edx
"\xcd\x80" // int $0x80
"\xb8\x01\x00\x00\x00" // mov $0x1,%eax
"\xbb\x00\x00\x00\x00" // mov $0x0,%ebx
"\xcd\x80" // int $0x80
"\xe8\xdd\xff\xff\xff" // call (relative) <GOBACK>
"Hello wolrd!\r\n"; // OR "\x48\x65\x6c\x6c\x6f\x2c\x20\x57"
// "\x6f\x72\x6c\x64\x21\x0d\x0a"
int main(int argc, char **argv)
{
(*(void(*)())code)();
return 0;
}
Vamos a probarlo, usando -z execstack
para habilitar read-implies-exec (en todo el proceso, a pesar de "stack" en el nombre) para que podamos ejecutar el código en el .data
o .rodata
secciones:
$ gcc -m32 test.c -z execstack -o test
$ ./test
Hello wolrd!
Funciona. (-m32
también es necesario en sistemas de 64 bits. El int $0x80
ABI de 32 bits no funciona con direcciones de 64 bits como .rodata
en un ejecutable PIE. Además, el código máquina se ensambló para 32 bits. Sucede que la misma secuencia de bytes se decodificaría en instrucciones equivalentes en modo de 64 bits, pero no siempre es así).
GNU moderno ld
pone .rodata
en un segmento separado de .text
, por lo que puede ser no ejecutable. Solía ser suficiente usar const char code[]
para poner código ejecutable en una página de datos de sólo lectura. Al menos para el shellcode que no quiere modificarse a sí mismo.