Un challenge pour un boulot

Comme vous le savez sûrement l’édition 2016 du challenge SSTIC est sortie (et de ce que nous avons pu en voir la forme ressemble étrangement au Holidays Hack Challenge du gars de la SANS SEC560). Bref, les lapins en chocolat ne nous ont pas encore permis de nous lancer sur le sujet, mais cela ne serait tarder… Mais comme d’habitude la retro-ingéniérie est au programme.

N’ayant pas eu de préchauffe officielle cette année, nous en avons débusqué une. En effet, une grande organisation a récemment publié une fiche de poste avec un challenge à résoudre (le recrutement est terminé depuis le 6 avril).

On dispose de deux fichiers :
– o-hsucrpt-e : binaire qui chiffre des données
– crypt.bin : un message chiffré

Objectif : Retrouver le message en clair !
Donc un bon exercice pour monter un peu en compétence sur l’ingénierie inverse,

Nous présentons ici notre méthode de résolution :

Tout d’abord on commence sans trop réfléchir :

jshmendes@vima:~/omc$ hexdump -C crypt.bin
00000000  45 84 85 b8 a1 dc 4b e0  5b 2e 0f 55 07 a2 77 8e  |E.....K.[..U..w.|
00000010  4e b7 35                                          |N.5|
00000013
jshmendes@vima:~/omc$ cat crypt.bin | ./o-hsucrpt-e | hexdump -C
00000000  57 fc c0 90 e1 fd 50 06  77 d9 21 c1 a8 e0 46 43  |W.....P.w.!...FC|
00000010  69 0f d9                                          |i..|
00000013
jshmendes@vima:~/omc$ printf "EW" | ./o-hsucrpt-e | hexdump -C
00000000  57 2f                                             |W/|
00000002
jshmendes@vima:~/omc$ printf "W/" | ./o-hsucrpt-e | hexdump -C
00000000  45 c3                                             |E.|
00000002
jshmendes@vima:~/omc$ printf "EWW" | ./o-hsucrpt-e | hexdump -C
00000000  57 2f 2d                                          |W/-|
00000003
jshmendes@vima:~/omc$ printf "EWWW" | ./o-hsucrpt-e | hexdump -C
00000000  57 2f 2d 36                                       |W/-6|
00000004
jshmendes@vima:~/omc$ printf "EWWWW" | ./o-hsucrpt-e | hexdump -C
00000000  57 2f 2d 36 9a                                    |W/-6.|
00000005
jshmendes@vima:~/omc$ printf "W/-6" | ./o-hsucrpt-e | hexdump -C
00000000  45 c3 8f 6b                                       |E..k|
00000004
jshmendes@vima:~/omc$ printf "F" | ./o-hsucrpt-e | hexdump -C
00000000  54                                                |T|
00000001
jshmendes@vm:~/omc$ printf "T" | ./o-hsucrpt-e | hexdump -C
00000000  46                                                |F|
00000001
jshmendes@vima:~/omc$ printf "W" | ./o-hsucrpt-e | ./o-hsucrpt-e ; echo ""
W
jshmendes@vima:~/omc$ printf "M" | ./o-hsucrpt-e | ./o-hsucrpt-e ; echo ""
M

Ainsi peut-on supposer les choses suivantes :

  1. Le clair et le chiffré ont toujours la même longueur : ça ressemble à un chiffrement par flot
  2. Les caractères sont chiffrés les uns après les autres : ça ressemble encore plus à un chiffrement par flot
  3. Le premier caractère en clair est toujours le chiffré de son chiffré, ie E(E(M)) = M : cela laisse penser qu’une fonction XOR est utilisée

Nous avons déjà grâce à l’assertion 3, le premier caractère. Et la longueur du chiffré étant de 0x13 (19) octects, cela ne devrait pas être très long de déchiffrer ce message par force brute. Nous passons donc en mode cryptobourrin.

jshmendes@vima:~/omc$ printf "E" | ./o-hsucrpt-e ; echo ""
W

Notre message commence donc par W. On essaie donc « WTO » pour World Trade Organisation, nom anglais de l’OMC :

jshmendes@vima:~/omc$ printf "WTO" | ./o-hsucrpt-e | hexdump -C
00000000  45 b8 48                                          |E.H|
00000003

Pas de chance, ça ne colle pas. A tâtons, nous tentons plusieurs valeurs et finissons rapidement par trouver :

jshmendes@vima:~/omc$ printf "What a" | ./o-hsucrpt-e | hexdump -C
00000000  45 84 85 b8 a1 dc                                 |E.....|
00000006

Du coup, n’ayant pas que cela à faire, on va faire les petits bourrins :
(Que les puristes du serpent constrictor nous pardonnent, nous balbutions)

from subprocess import Popen, PIPE

def encrypt(message):
        p1 = Popen(["cat", message], stdout=PIPE)
        p2 = Popen(["./o-hsucrpt-e"], stdin=p1.stdout, stdout=PIPE)
        output = p2.communicate()[0]
        return output

fi = open("crypt.bin", "rb")

ok = 1
ind = 5
message = "What "
for line in fi:
        while True:
                for i in range(256):
                        ok = 1
                        message+= str(chr(i)) + '\n'
                        fo = open("foo.txt", "wb")
                        fo.write( message)
                        fo.close()
                        output = encrypt("foo.txt")
                        for j in range(len(str(output))-1):
                                if str(output)[j] != str(line)[j]:
                                        ok = 0
                                        break
                        if ok == 1:
                                ind+=1
                                message = message[:ind]
                                break
                        message = message[:ind]
                if (ind == len(str(line))):
                        print message
                        break

Et quasi instantanément, nous avons notre sésame :

jshmendes@vima:~/omc$ python craker.py
 What a lovely day!

Voilà c’est fait mais on reste un peu sur notre faim : comment fonctionne ce petit programme ?

Tout d’abord, essayons d’en savoir un peu plus :

jshmendes@vima:~/omc$ file o-hsucrpt-e
o-hsucrpt-e: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=d100acad15a7f26eb019e597d48a3e04c86fc950, not stripped
jshmendes@vima:~/omc$ ldd o-hsucrpt-e
        linux-vdso.so.1 =>  (0x00007ffe8759a000)
        libmcrypt.so.4 => /usr/lib/libmcrypt.so.4 (0x00007fcaf07ae000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcaf03e9000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fcaf09e2000)

On remarque que le binaire est pour une architecture 64 bits et est dynamiquement lié avec la bibliothèque mcrypt, il doit donc utiliser des fonctions de chiffrement standard (ou du moins implémentée dans cette bibliothèque). On y reviendra mais continuons :

Un coup de strings :

jshmendes@vima:~/omc$ strings  o-hsucrpt-e | grep -v _ | grep -v '\.'
strcpy
stdin
calloc
strlen
stdout
rand
malloc
fwrite
fread
memmove
code = lH
ib man pH
goto fail;
twofish
;*3$"
main

Ah on commence à voir des choses intéressantes ; enfin surtout une chaîne « twofish ». Cherchons maintenant un mode de chiffrement. Par défaut, strings ne cherche que les chaînes de 4 caractères et plus, or les modes les plus communs sont abréviés en 3 lettres (ECB, CBC, OFB, …). Utilisons donc l’option -n :

jshmendes@vima:~/omc$ strings  -n 3 o-hsucrpt-e | grep -C 2 twofish
ff.
goto fail;
twofish
cfb
;*3$"

Bingo ! La chaine cfb est présente et correspond à un mode de chiffrement le Cipher FeedBack. Ce qui ne nous étonne guère car le mode CFB a la particularité de pouvoir transformer un chiffrement par bloc en chiffrement par bloc de taille arbitraire inférieure à celle de l’algorithme utilisée. Ce n’est pas très claire plus d’info ici ou

Nous supposons fortement que le programme utilise twofish en mode CFB. Reste à savoir dans quelle fonction ces deux paramètres sont utilisés, et ainsi retrouver la clé et le vecteur d’initialisation. Un petit coup de ‘nm’ devrait nous mettre sur la voie :

jshmendes@vima:~/omc$ nm o-hsucrpt-e | grep mcrypt
                 U mcrypt_enc_get_iv_size //Donne la taille de l'IV pour le mode et l'algo choisis
                 U mcrypt_generic // Chiffre un blop de donné
                 U mcrypt_generic_end // Finalize le chiffrement
                 U mcrypt_generic_init // Créer un contexte de chiffrement (IV + Key)
                 U mcrypt_module_open // Définit une struct algo+mode
                 U mcrypt_perror

Plus d’information sur les fonctions peuvent être trouvées ici.

Maintenant que nous avons tous ces éléments, nous pouvons utiliser gdb

(gdb) break mcrypt_module_open
Breakpoint 1 at 0x4008d0
(gdb) run
Starting program: /home/owner/omc/o-hsucrpt-e

Breakpoint 1, 0x00007ffff7bad110 in mcrypt_module_open () from /usr/lib/libmcrypt.so.4
(gdb) info frame
Stack level 0, frame at 0x7fffffffe3b0:
 rip = 0x7ffff7bad110 in mcrypt_module_open; saved rip = 0x400bcb
 called by frame at 0x7fffffffe490
 Arglist at 0x7fffffffe3a0, args:
 Locals at 0x7fffffffe3a0, Previous frame's sp is 0x7fffffffe3b0
 Saved registers:
  rip at 0x7fffffffe3a8
(gdb) x /96xh 0x7fffffffe3a0
0x7fffffffe3a0: 0x0001  0x0000  0x0000  0x0000  0x0bcb  0x0040  0x0000  0x0000
0x7fffffffe3b0: 0x0001  0x0000  0x7fff  0x0000  0xe1c8  0xf7ff  0x7fff  0x0000
0x7fffffffe3c0: 0x0013  0x0000  0x0010  0x0000  0x3010  0x0060  0x0000  0x0000
0x7fffffffe3d0: 0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
0x7fffffffe3e0: 0x6f67  0x6f74  0x6620  0x6961  0x3b6c  0xf700  0x7fff  0x0000
0x7fffffffe3f0: 0xe420  0xffff  0x7fff  0x0000  0xe410  0xffff  0x7fff  0x0000
0x7fffffffe400: 0x6f63  0x6564  0x3d20  0x6c20  0x6269  0x6d20  0x6e61  0x7020
0x7fffffffe410: 0x6761  0x0065  0x0000  0x0000  0xe578  0xffff  0x7fff  0x0000
0x7fffffffe420: 0x7774  0x666f  0x7369  0x0068  0x9990  0xf7ff  0x7fff  0x0000
0x7fffffffe430: 0xe1c8  0xf7ff  0x7fff  0x0000  0x0000  0x0000  0x0000  0x0000
0x7fffffffe440: 0x6663  0x0062  0x0000  0x0000  0x0d8d  0x0040  0x0000  0x0000
0x7fffffffe450: 0xe480  0xffff  0x7fff  0x0000  0x0000  0x0000  0x0000  0x0000

Nous retrouvons notre « twofish » et « cfb ». Rajoutons un breakpoint sur la seconde fonction intéressante :

(gdb) break mcrypt_generic_init
Breakpoint 2 at 0x7ffff7babc80
(gdb) c
Continuing.
Breakpoint 2, 0x00007ffff7babc80 in mcrypt_generic_init () from /usr/lib/libmcrypt.so.4
(gdb) info frame
Stack level 0, frame at 0x7fffffffe3b0:
 rip = 0x7ffff7babc80 in mcrypt_generic_init; saved rip = 0x400c6e
 called by frame at 0x7fffffffe490
 Arglist at 0x7fffffffe3a0, args:
 Locals at 0x7fffffffe3a0, Previous frame's sp is 0x7fffffffe3b0
 Saved registers:
  rip at 0x7fffffffe3a8

Comme vu plus haut, d’après la documentation cette fonction prend bien 4 paramètres. De plus rappelons que les paramètres d’une fonction lorsque peu nombreux sont placé dans les registres par ordre inverse (le dernier paramètre sera donc le registre le « plus petit » ici rcx)

(gdb) info registers
rax            0x603030 6303792
rbx            0x60311f 6304031
rcx            0x603110 6304016   # VI
rdx            0x10     16        # Taille du Bloc
rsi            0x603010 6303760   # Clé
rdi            0x603030 6303792   # Mcrypt_Struct
rbp            0x7fffffffe480   0x7fffffffe480
rsp            0x7fffffffe3a8   0x7fffffffe3a8
r8             0xff     255
r9             0xffffffffff000000       -16777216
r10            0x7fffffffe170   140737488347504
r11            0x7ffff7babc80   140737349598336
r12            0x4009d0 4196816
r13            0x7fffffffe560   140737488348512
r14            0x0      0
r15            0x0      0
rip            0x7ffff7babc80   0x7ffff7babc80 
eflags         0x202    [ IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) x /48xh 0x603030
0x603030:       0xffff  0xffff  0xffff  0xffff  0x7774  0x666f  0x7369  0x0068
0x603040:       0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
0x603050:       0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
0x603060:       0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
0x603070:       0x0000  0x0000  0x0000  0x0000  0xffff  0xffff  0xffff  0xffff
0x603080:       0x6663  0x0062  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
(gdb) x /8xh 0x603110
0x603110:       0xc667  0x7369  0xff51  0xec4a  0xcd29  0xabba  0xfbf2  0x46e3
(gdb) x /8xh 0x603010
0x603010:       0x6f67  0x6f74  0x6620  0x6961  0x3b6c  0x0000  0x0000  0x0000
(gdb)

Nous avons donc ici des sérieux candidats pour la clé et le vecteur d’initialisation.

Enfin par acquis de conscience, nous pouvons aller « pièger » la bibliothèque :

apt-get  source libmcrypt-dev

Nous modififions la fonction mcrypt_generic_init dans lib/mcrypt.c comme suit :

int mcrypt_generic_init(const MCRYPT td, const void *key, int lenofkey, const void *IV)
{
        int i = 0 ;
        fprintf(stdout,"Captain on the bridge : The key is ");
        for( i =0 ; i<16; i++)
                fprintf(stdout,"%02x", ((unsigned char *)key)[i] );
        printf("\nThis is the IV you are looking for :");

        for( i =0 ; i<16; i++)
                fprintf(stdout,"%02x", ((unsigned char *) IV)[i]);
        printf("\n");

        return internal_init_mcrypt(td, key, lenofkey, IV);
}

On recompile, on met à jour le lien symbolique :

jshmendes@vima:~/omc/libmcrypt-2.5.8$ ./configure ; make
jshmendes@vima:~/omc/libmcrypt-2.5.8$ sudo ln -sf  /home/jshmendes/omc/libmcrypt-2.5.8/lib/.libs/libmcrypt.so.4.4.8 /usr/lib/libmcrypt.so.4
jshmendes@vima:~/omc$ printf "blabla" | ./o-hsucrpt-e ; echo ""
Captain on the bridge : The key is 676f746f206661696c3b000000000000
This is the IV you are looking for :67c6697351ff4aec29cdbaabf2fbe346
pspq

Et voilà ! Cela confirme notre analyse précédente. Notons ici qu’utiliser la librairie permet aussi d’avoir plus informations dans gdb :

jshmendes@vima:~/omc$ gdb ./o-hsucrpt-e
[...]
(gdb) break mcrypt_generic_init
Breakpoint 1 at 0x400900
(gdb) run
Starting program: /home/owner/omc/o-hsucrpt-e

Breakpoint 1, mcrypt_generic_init (td=0x603030, key=0x603010, lenofkey=16, IV=0x603110) at mcrypt.c:156
156     {
(gdb) info args
td = 0x603030
key = 0x603010
lenofkey = 16
IV = 0x603110

Bon c’est bien, mais d’où sortent cette clé et ce vecteur ?

La clé n’est autre qu’une chaine ASCII qui nous avions sous les yeux depuis le début : « goto fail; »

Mais le vecteur est lui plus abscons, une recherche dans le binaire ne donne rien. Il doit être généré par le code. Allez on y retourne : on va voir l’assembleur :

jshmendes@vima:~/omc/$ objdump -d o-huscrpt-e >  output.asm
 400bc6:       e8 05 fd ff ff          callq  4008d0 
 400bcb:       48 89 85 50 ff ff ff    mov    %rax,-0xb0(%rbp)
 400bd2:       48 83 bd 50 ff ff ff    cmpq   $0x0,-0xb0(%rbp)
 400bd9:       00
 400bda:       75 0a                   jne    400be6 
 400bdc:       b8 01 00 00 00          mov    $0x1,%eax
 400be1:       e9 2e 01 00 00          jmpq   400d14 
 400be6:       48 8b 85 50 ff ff ff    mov    -0xb0(%rbp),%rax
 400bed:       48 89 c7                mov    %rax,%rdi
 400bf0:       e8 bb fd ff ff          callq  4009b0 
 400bf5:       48 98                   cltq
 400bf7:       48 89 c7                mov    %rax,%rdi
 400bfa:       e8 61 fd ff ff          callq  400960 
 400bff:       48 89 85 58 ff ff ff    mov    %rax,-0xa8(%rbp)
 400c06:       c7 85 40 ff ff ff 00    movl   $0x0,-0xc0(%rbp)
 400c0d:       00 00 00
 400c10:       eb 22                   jmp    400c34 
 400c12:       8b 85 40 ff ff ff       mov    -0xc0(%rbp),%eax   // Début de boucle
 400c18:       48 63 d0                movslq %eax,%rdx
 400c1b:       48 8b 85 58 ff ff ff    mov    -0xa8(%rbp),%rax
 400c22:       48 8d 1c 02             lea    (%rdx,%rax,1),%rbx
 400c26:       e8 95 fd ff ff          callq  4009c0 
 400c2b:       88 03                   mov    %al,(%rbx)
 400c2d:       83 85 40 ff ff ff 01    addl   $0x1,-0xc0(%rbp)
 400c34:       48 8b 85 50 ff ff ff    mov    -0xb0(%rbp),%rax
 400c3b:       48 89 c7                mov    %rax,%rdi
 400c3e:       e8 6d fd ff ff          callq  4009b0 
 400c43:       3b 85 40 ff ff ff       cmp    -0xc0(%rbp),%eax  
 400c49:       7f c7                   jg     400c12  
 400c4b:       48 8b 8d 58 ff ff ff    mov    -0xa8(%rbp),%rcx
 400c52:       8b 95 44 ff ff ff       mov    -0xbc(%rbp),%edx
 400c58:       48 8b b5 48 ff ff ff    mov    -0xb8(%rbp),%rsi
 400c5f:       48 8b 85 50 ff ff ff    mov    -0xb0(%rbp),%rax
 400c66:       48 89 c7                mov    %rax,%rdi
 400c69:       e8 92 fc ff ff          callq  400900 


On constate une boucle itérative sur la taille du VI. Egalement, on remarque l’utilisation d’une fonction « rand » de stdlib. Ce qui est intéressant ici, c’est que la fonction srand utilisée pour définir la graine du générateur pseudo-aléatoire n’est pas appelé dans le code. Et si l’on se fit à la « man page », il n’y a des lors rien de vraiment aléatoire dans la génération du vecteur d’initialisation puisque pour chaque exécution, le VI sera toujours identique car la graine utilisée par rand() sera toujours 1.

Du coup si nous pouvons le reproduire :

#include <stdlib.h>;
#include <stdio.h>;

int main(int argc, char *argv[])
{
  int j ;
  for (j = 0; j &amp;lt; 16; j++)
     fprintf(stdout, &quot;%02x&quot;, (unsigned char ) rand());
  return 0;
}
jshmendes@vima:~/omc$ gcc testrand.c -o iv
jshmendes@vima:~/omc$ ./iv
67c6697351ff4aec29cdbaabf2fbe346

Donc voilà, nous savons comment est généréré l’IV.

Nous pouvons donc dire que nous avons tout. Reste maintenant à coder un programme qui fasse la même chose. Mais, en y regardant de plus pres, les concepteurs avaient laissé volontairement une indication sur la source du challenge qui se trouve dans la bibliothèque mcrypt elle-même… On vous laisse chercher 🙂

Publicités

A propos JoMendes

Amateur de mathématiques et d'hexadécimal. Je m'intéresse de près ou de loin suivant mon niveau à tous les sujets de sécurité de l'information.
Cet article, publié dans cryptobourrin, Cryptographie Asymétrique, Cryptographie Symétrique, Uncategorized, est tagué , , , , . Ajoutez ce permalien à vos favoris.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s