Le crackme étudié dans cet article provient
d’ici.
Quatrième crackme, et cette fois nous allons rencontrer notre premier
mécanisme anti-débogueur.
Nous avons donc à faire à un exécutable statique avec un strings
de 2069 lignes qui ne contient pas de mot de passe (cette fois j’ai vérifié
^^")
Ok, sortons le débogueur (comme pour le crackme 2, sauf que cette
fois c’est justifié :-P). Mais avant cela, voyons d’abord une exécution
normale :
Maintenant, avec notre ami GDB :
Hoho, voyez-vous ça ? Monsieur n’aime pas être surveillé. Bon, on va y aller
un peu plus doucement cette fois :
Tiens, tiens, mais que vois-je ? Un appel à
ptrace à l’adresse
0x08048410. Il faut savoir que, sous Linux du moins, ptrace est
l’appel système à la base des débogueurs. Il permet de mettre en pause un
programme, de faire du pas à pas, de lire et écrire sa mémoire et ses
registres. Il permet également de détecter si un programme est sous la
surveillance de ptrace (et donc, potentiellement sour la
surveillance d’un débogueur). Et c’est ce qui est fait ici. Le code :
correspond à :
Qui permet de savoir si le processus courant est sous surveillance.
Maintenant que l’on sait comment le binaire se défend, nous allons pouvoir
contourner cette protection. Pour cela, nous pouvons placer un point d’arrêt
sur l’instruction qui teste la valeur de retour de ptrace et la
changer (sachant que l’appel renvoi -1 si le processus est tracé et 0 si ce
n’est pas le cas) avant de continuer l’exécution.
Avant de continuer, regardons le code de plus près. Bon déjà, il n’y a pas
d’appel à strcmp. Cependant, en examinant le code avec attention
on remarque cette partie :
Tiens tiens, on dirait bien qu’il y a un pattern ici :
on charge un octet dans dl
on charge une valeur dans eax
on fait une opération arithmétique sur eax (add
ou inc)
on ramène eax sur un octet.
on compare al avec dl
on saute à l’adresse 0x80484e4 si la comparaison précédente génère un
résultat différent de 0 (ce qui signifie que les deux opérandes étaient
différents).
Cela ressemble étrangement à une boucle (boucle déroulée certes, mais boucle
quand même) pour comparer deux chaînes de caractères. Et comme
ce pattern se répète quatre fois, on pourrait émettre l’hypothèse que
le mot de passe recherché a une longueur de 4 caractères.
Vérifions nos hypothèses en examinant les valeurs de al
et dl à chaque cmp. Commençons par poser un point
d’arrêt sur le premier cmp.
Ok, dl semble contenir notre chaîne (on reconnaît notre A), c’est
donc al qui va contenir les caractères du mot de passe à trouver
(le premier caractère du mot de passe est donc e).
Bien, maintenant que l’on sait quel registre surveiller, continuons avec les
autres cmp. Mais avant cela, il va falloir bidouiller un peu. Et
oui, le cmp ne va pas produire le résultat attendu et la
prochaine instruction va nous faire sauter (ce qui va nous empêcher d’analyser
les autres cmp). La prochaine instruction est un jne
ce qui signifie Jump if Not Equal, qui est le parfait équivalent de
l’instruction jnz (qui signifie Jump if Not Zero). Or le
second nom (jnz) nous met la puce à l’oreille : cette instruction
se base sur la valeur du flag z. Pour éviter le saut, il va donc
falloir mettre ce flag à 1 après l’instruction cmp.
Allons-y :
Et voilà, le flag z est positionné (il y a peut-être une syntaxe
plus simple pour faire ça, mais je n’ai pas trouvé). D’où sort le 64 ? Et bien
c’est tout simple : d’après
cette page,
le flag z correspond au bit 6, et comme chacun sait 26
= 64. S’en suit un petit OU bit à bit pour positionner le flag voulu. Et voilà
le travail.
Allez, continuons et découvrons ce fichu mot de passe :