GNU/Linux >> Tutoriales Linux >  >> Linux

Shellcode de Linux ¡Hola, mundo!

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.


Linux
  1. Utilice el comando grep de Linux

  2. Ejemplo de Smalltalk Hello World:cómo escribir y ejecutar el programa Smalltalk en el sistema operativo Linux

  3. Ejemplo de Fortran Hello World:cómo escribir y ejecutar el programa Fortran en el sistema operativo Linux

  4. Ejemplo de XQuery Hello World:cómo escribir y ejecutar el programa XQuery en el sistema operativo Linux

  5. ¿Existe un equivalente a .Net FileSystemWatcher en el mundo de Linux?

Comando W en Linux

Un chico de Windows en un mundo Linux:VS Code y SSH remoto

Un tipo de Windows en un mundo Linux:configurar el comando SSH en Linux

Un chico de Windows en un mundo Linux:usuarios y permisos de archivo

Un tipo de Windows en un mundo Linux:YaST y el escritorio

¿Cómo agregar mi propio software a un paquete Buildroot Linux?