mardi 19 novembre 2013

Grehack 2013 writeup - 1337

Un des challenges de la grehack 2013 était du reverse linux.
Le binaire s'appelait 1337 (et les orgas avaient prévenus qu'ils aimaient le l33tsp34k). En attendant que l'orga mette les fichiers en libre-accès, je le pose sur un site de dl.

Writeup, première phase de découverte:
kevin@slack:~/chall/grehack$ file 1337
1337: ELF 32-bit LSB executable, Intel 80386,, statically linked, stripped
kevin@slack:~/chall/grehack$ readelf -s 1337
readelf: Error: Unable to read in 0x28 bytes of section headers
readelf: Error: Unable to read in 0x488 bytes of section headers
readelf: Error: Unable to read in 0x100 bytes of program headers

WUT? Un ELF illisible?

Bon, premier exercice, réparer le binaire:
kevin@slack:~/chall/grehack$ hexdump -C 1337 | head -2
00000000  7f 45 4c 46 01 01 01 13  37 13 37 13 37 13 37 00  |.ELF....7.7.7.7.|
00000010  02 00 03 00 01 13 37 00  90 84 04 08 34 13 37 00  |......7.....4.7.|
kevin@slack:~/chall/grehack$ hexdump -C /bin/ls | head -2
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 03 00 01 00 00 00  b4 c1 04 08 34 00 00 00  |............4...|

Il y a quand même beaucoup de 1337 dans ce binaire. On tente une approche basique: tous les 0000 sont remplacés par des 1337:
kevin@slack:~/chall/grehack$ cp 1337 wip
kevin@slack:~/chall/grehack$ sed -i 's/\x13\x37/\x00\x00/g' wip
kevin@slack:~/chall/grehack$ readelf -s wip

Symbol table '.dynsym' contains 12 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.0 (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.0 (2)
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND fgets@GLIBC_2.0 (2)
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND malloc@GLIBC_2.0 (2)
     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.0 (2)
     6: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.0 (2)
     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND strlen@GLIBC_2.0 (2)
     9: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
    10: 0804889c     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used
    11: 08049aa0     4 OBJECT  GLOBAL DEFAULT   26 stdin@GLIBC_2.0 (2)
kevin@slack:~/chall/grehack$

Ok, ça semble bon.

kevin@slack:~/chall/grehack$ ./wip
Ready to become 1337?
yes
fail

Ok. Il est temps de regarder un peu à quoi ressemble ce challenge, pour une approche rapide, je prends metasm. Je démarre par l'entrypoint (0x8048490) et je navigue un peu jusqu'à 0x0804857c qui semble intéressant.
Et ce qui est à l'adresse 0x080488a0 est encore plus intéressant: on découvre trois manières d'écrire FAIL, puis un message de félicitations.

Si je regarde une vue d'avion du programme, on se rend compte que l'on peut dérouler l'exécution en plusieurs phases:
Pour ne pas alourdir le post, je ne met pas l'ensemble des vérifications, mais on voit deux boucles qui semblent faire des choses, puis des séries de checks qui mènent aux trois différents FAIL. C'est sympathique, car selon le message, on peut tout de suite savoir si on a passé l'étape. Analysons la Boucle1 et sa condition en sortie:

suite au strlen, on met la taille dans esp+0x10, on met 0 dans esp+0x1c, et on itère jusqu’à ce que le second égale le premier (autrement dit, on parse la chaîne). Pendant l'itération, on prend caractère par caractère du mot de passe, et on additionne en hexa (et attention car on compte aussi le caractère de fin de chaine 0xa).

En résumé, nous avons à esp+0x14 l'adresse de la clé entrée, et en esp+0x18 la somme des valeurs hexa de chacun des caractères.

En fin de boucle, on compare si la somme vaut 0x539 (ce qui fait 1337 en décimal, il y a du l33t dans ce chall). Vérifions si on prend 11 (0xa en hexa) caractères 'y' (0x79 en hexa) et un 'u' (0x75):
'0x79'*a+'0x75'+'0x0a' = 0x539
kevin@slack:~/chall/grehack$ ./wip
Ready to become 1337?
yyyyyyyyyyu
f41l

Le message d'erreur a changé, nous avons f41l, ce qui signifie que la première étape est donc passée.

La seconde étape fait 4 comparaisons:

Comparaisons très simples d'ailleurs.
On vérifie que le second caractère (eax+1) vaut 0x31 (1)
On vérifie que le quatrième caractère (eax+3) vaut 0x33 (3)
On vérifie que le quatrième caractère (eax+3) vaut 0x33 (3)
On vérifie que le huitième caractère (eax+7) vaut 0x37 (7)
Du 1337 encore. Si je modifie ma chaine par:
y1y3yyy7yyu, il va me manquer des points pour aboutir à 0x539, je complète donc avec d'autres caractères pour que la somme fasse 0x539 pour continuer à passer la première étape, comme par exemple: y1y3yyy7yyuHDD

kevin@slack:~/chall/grehack$ ./wip
Ready to become 1337?
y1y3yyy7yyuHDD
0xPh41L


Ok, on a bien le second message d'erreur, signe que la deuxième étape est passée.

La troisième étape démarre par la boucle nommée boucle 2 sur l'image vue d'avion:
Le programme va aller du côté de esp+0x14 (la clé) et faire deux XOR successifs, 13 puis 37 (oooooh, encore 1337). La clé va donc être XORée par 0x24.

La suite du programme est une longue suite de comparaison dont voici le début:
On voit que différents caractères du mot de passe sont comparés entre eux. Si on suit la chaîne complète, on observe que les caractères: 0,2,4,5,6,8,9,a,b,c,d sont identiques, et que le caractère en e-ième position vaut v (0x76). Si cela est vrai alors le message de succès est affiché:
Le dernier caractère est comparé à 'v', mais est XORé. 'v' XORé à 0x24 vaut 0x52 autrement dit 'R'.

 Si on résume le tout, on voit que le mot de passe doit répondre à ces caractéristiques:
.1.3...7......R+padding
avec tous les caractères '.' identiques, et que la somme en hexa de l'ensemble des chiffres vaut 0x539. Si je compte:
0x539 - 0x0a (retour chariot) - 0x31 - 0x33 - 0x37 - 0x52 = 0x442
J'ai besoin au minimum de 11 caractères et que le reste soit de l'ascii inscriptible. Avec une calculette hexa, je trouve une solution avec onze 'Y' et un 'o': Y1Y3YYY7YYYYYYRo
kevin@slack:~/grehack$ ./wip
Ready to become 1337?
Y1Y3YYY7YYYYYYRo
W3LL d0|\|3, y0u'r 50 L337 |\|0W...

Ok, chall done.

Et puisque je suis devenu un l33t et que le challenge permet plusieurs solutions, je propose sans trop de difficultés avec plein d'31337ness :
kevin@slack:~/grehack$ ./wip
Ready to become 1337?
31333337333333R^k3v1n
W3LL d0|\|3, y0u'r 50 L337 |\|0W...

EDIT 20/11/2013:  A la réflexion, je me rends compte qu'il y a une autre solution. Si on supprime le retour chariot de fin de chaine, alors on obtient une solution plus simple:

kevin@slack:~/grehack$ echo -n d1d3ddd7ddddddR | ./wip 
Ready to become 1337?
W3LL d0|\|3, y0u'r 50 L337 |\|0W...


PS: Je teste aussi un nouveau layout du blog, dites moi ce que vous en pensez, ça me paraît plus clair lorsque je copie colle du terminal et que je met des images. Si vous connaissez une CSS qui affiche correctement de l'hexa, je suis preneur aussi.

Aucun commentaire:

Publier un commentaire