Shellcode de Linux "¡Hola, mundo!"
assembly nasm (2)
Tengo el siguiente código NASM funcional:
global _start
section .text
_start:
mov eax, 0x4
mov ebx, 0x1
mov ecx, message
mov edx, 0xF
int 0x80
mov eax, 0x1
mov ebx, 0x0
int 0x80
section .data
message: db "Hello, World!", 0dh, 0ah
que imprime "Hello, World! / n" en la pantalla. También tengo el siguiente contenedor C que contiene el código de objeto NASM anterior:
char code[] =
"/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";
int main(void)
{
(*(void(*)())code)();
}
Sin embargo, cuando ejecuto el código, parece que el código del ensamblador no se ejecuta, pero el programa sale bien. ¿Algunas ideas?
Gracias
Como mencionó BSH , su shellcode no contiene los bytes del mensaje. Saltar a la etiqueta MESSAGE
y llamar a la rutina GOBACK
justo antes de definir el byte msg
fue una buena jugada ya que la dirección de msg estaría en la parte superior de la pila como dirección de retorno que podría aparecer en ecx
, donde se almacena la dirección de msg .
Pero tanto el suyo como el código de BSH tienen una pequeña limitación. Contiene NULL bytes ( /x00 )
que se considerarían como fin de cadena cuando el puntero de función desreferencia.
Hay una manera inteligente de evitar esto. Los valores que almacena en eax, ebx and edx
son lo suficientemente pequeños para escribirse directamente en los nibbles inferiores de los registros respectivos de una vez accediendo a al, bl and dl
respectivamente. El nibble superior puede contener valor no deseado por lo que puede ser definido.
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
Ensamblaje y enlace:
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
Ahora extrae el código de shell del hola binario:
$ 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 de controlador para iniciar 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;
}
Existen ciertas características de seguridad en compiladores modernos como la protección NX que evita la ejecución de código en el segmento de datos o la pila. Por lo tanto, debemos especificar explícitamente el compilador para deshabilitar estos.
$ 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 núcleos Linux modernos tienen ASLR o Address Space Layout Randomization
Es posible que deba deshabilitar esto antes de inyectar el código de shell, especialmente cuando se trata de desbordamientos de búfer.
root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space
Cuando inyectas este Shellcode, no sabes qué hay en el 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 evitarlo es de esta manera:
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 nuestra cadena "Hello, World!/r/n"
:
$ printf "/x48/x65/x6c/x6c/x6f/x2c/x20/x57/x6f/x72/x6c/x64/x21/x0d/x0a"
Hello, World!
$
Entonces nuestro envoltorio de C será:
char code[] =
"/xe9/x1e/x00/x00/x00" // jmp 8048083 <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 8048065 <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:
$ gcc test.c -o test
$ ./test
Hello wolrd!
$
funciona.