web-dev-qa-db-fra.com

Linux Shellcode "Bonjour tout le monde!"

J'ai le code NASM de travail suivant:

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

qui imprime "Bonjour tout le monde!\n" à l'écran. J'ai également l'encapsuleur C suivant qui contient le code d'objet NASM précédent:

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)();
}

Cependant, lorsque j'exécute le code, il semble que le code assembleur ne soit pas exécuté, mais le programme se termine bien. Des idées?

Merci

27
user1408643

Lorsque vous injectez ce shellcode, vous ne savez pas ce qui se trouve à message:

mov ecx, message

dans le processus injecté, ça peut être n'importe quoi mais ce ne sera pas "Hello world!\r\n" car il se trouve dans la section des données lorsque vous ne videz que la section de texte. Vous pouvez voir que votre shellcode n'a pas "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";

C'est un problème commun dans le développement de shellcode, la façon de contourner ce problème est la suivante:

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

Maintenant, videz la section de texte:

$ 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                      <-+

$

Les lignes que j'ai marquées sont nos "Hello, World!\r\n" chaîne:

$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!

$ 

Notre wrapper C sera donc:

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;
}

Permet de le tester:

$ gcc test.c -o test
$ ./test 
Hello wolrd!
$ 

ça marche.

69
user1129665

Comme BSH mentionné, votre shellcode ne contient pas les octets de message. Passer à l'étiquette MESSAGE et appeler la routine GOBACK juste avant de définir l'octet msg était une bonne décision car l'adresse de msg serait en haut de la pile comme retour adresse qui pourrait être ajoutée à ecx, où l'adresse du msg est stockée.

Mais le vôtre et le code de BSH ont une légère limitation. Il contient NULL bytes ( \x00 ) qui serait considéré comme fin de chaîne lorsqu'il est déréférencé par le pointeur de fonction.

Il existe un moyen intelligent de contourner cela. Les valeurs que vous stockez dans eax, ebx and edx sont suffisamment petits pour être écrits directement dans les quartets inférieurs des registres respectifs en une seule fois en accédant à al, bl and dl respectivement. Le quartet supérieur peut contenir une valeur indésirable afin qu'il puisse être xoré.

b8 04 00 00 00 ------ mov $0x4,%eax


devient

b0 04          ------ mov $0x4,%al
31 c0          ------ xor    %eax,%eax


Contrairement au jeu d'instructions précédent, le nouveau jeu d'instructions ne contient aucun octet NULL.

Ainsi, le programme final ressemble à ceci:

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

Assemblage et liaison:

$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
 y0u sp34k 1337 ? $ 

Maintenant, extrayez le shellcode du binaire hello:

$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done

production:

\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

Maintenant, nous pouvons avoir notre programme de pilote pour lancer le 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;
}

Il existe certaines fonctionnalités de sécurité dans les compilateurs modernes comme protection NX qui empêche l'exécution de code dans un segment de données ou une pile. Nous devons donc spécifier explicitement le compilateur pour les désactiver.

$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher

Maintenant, le launcher peut être invoqué pour lancer le shellcode.

$ ./launcher
 y0u sp34k 1337 ? $ 

Pour les codes shell plus complexes, il y aurait un autre obstacle. Les noyaux Linux modernes ont ASLR ou Address Space Layout Randomization Vous devrez peut-être désactiver cette option avant d'injecter le shellcode, en particulier lorsqu'il survient à travers des dépassements de tampon.

root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space 
20
Akshay Krishnan R