Rapport de reverse engineering sur la série de crackmes Crack My Owl.

Résumé des résultats

CibleMot de passe
./00mistigri
./01ecret
./0243ys
./03Very/Easy
./04hiddenPhrase
./05ROBINET
./061950
./072849
./08g00d3n0ugh
./099876543210
./owl3333333333333333333333333333333333333
./scorpionFAIL!

Méthodologie

Outils utilisés : gdb-gef, objdump, strings, grep, hexdump, readelf.

Protocole

  • exécution du programme afin d’observer son comportement ;
  • appel à strings <exe> afin d’observer les chaînes interprétées dans la section de données ;
  • utilisation de gdb afin d’afficher les fonctions (info functions) :
    • s’il y a des strcmp, on positionne des points d’arrêt (breakpoint) afin d’analyser les registres et/ou la mémoire ; et subséquemment, d’en déduire le mot de passe ;
    • sinon, on évalue le flot d’exécution du programme ;
  • on effectue des hypothèses, testées en modifiant manuellement la valeur des registres si besoin.

Résolutions

00 — Crackme 00

Contexte. Binaire simple : comparaison directe via strcmp.

Méthodologie.

(gdb) b strcmp
(gdb) b main
(gdb) r

Constat / analyse. Comparaison directe contre une chaîne stockée en dur.

Résultat. Mot de passe : mistigri.

01 — Crackme 01

Contexte. Binaire contenant une chaîne en dur, accessible via la table de fonctions.

Méthodologie.

(gdb) info functions
# repérer fonction cible et ses constantes
(gdb) b <fonction_identifiée>
(gdb) r

Constat / analyse. La chaîne est écrite en dur dans le binaire avant l’entrée dans la fonction de vérification.

Résultat. Mot de passe : ecret.

02 — Crackme 02

Contexte. Vérification via strcmp ; utile d’inspecter les registres passés en argument.

Méthodologie.

(gdb) b strcmp
(gdb) r
(gdb) print $rdi   # pointeur vers la chaîne attendue

Constat / analyse. rdi pointe vers la chaîne attendue — la lecture directe en mémoire permet la reconstruction.

Résultat. Mot de passe : 43ys.

03 — Crackme 03

Contexte. Vérification par comparaison entre registres (eax vs edx). L’analyse montre une transformation (décalage) appliquée aux caractères d’entrée.

Méthodologie.

# breakpoint autour de la routine de comparaison
(gdb) b *<addr_cmp>
(gdb) r
# observer eax/edx et les valeurs des caractères en boucle

Constat / analyse. Chaque caractère d’entrée est comparé avec la cible après l’application d’un chiffrement de César. Inverser ce décalage permet de retrouver la chaîne correcte.

Résultat. Mot de passe : Very/Easy.

04 — Crackme 04

Contexte. Comparaisons itératives caractère par caractère ; registre al utilisé pour les comparaisons successives.

Méthodologie.

(gdb) info functions
(gdb) b strcmp
(gdb) r

En particulier, itérer et lire al à chaque comparaison impose a fortiori de relancer le programme à chaque fois avec la valeur adéquate du caractère décodé pour accéder à l’itération suivante.

Constat / analyse. En itérant les valeurs de al à chaque comparaison, on reconstitue la chaîne caractère par caractère.

Résultat. Mot de passe : hiddenPhrase.

05 — Crackme 05

Contexte. Vérification impliquant des opérations bitwise et arithmétiques sur des registres (dil, sil).

Méthodologie.

(gdb) b *<addr_op>
(gdb) r
# step, observer dil/sil, noter les transformations (sub, shl, etc.)

Constat / analyse. Séquence observée : sub dil puis shl (shift left). Les valeurs observées de dil sont indépendantes de l’entrée utilisateur : la condition finale s’attend à un résultat précis égal à 0x80.

Résultat. Mot de passe : ROBINET.

06 — Crackme 06

Contexte. La vérification compare le résultat d’une addition d’arguments à une constante (0x79e).

Méthodologie.

(gdb) b atoi
# éviter la terminaison immédiate du programme
(gdb) b *main+<offset_ret>
(gdb) r
# examiner les valeurs passées à atoi et l'addition

Constat / analyse. On identifie l’addition arg1 + arg2 comparée à 0x79e (hexadécimal). La résolution de l’équation donne la valeur attendue.

Résultat. Mot de passe : 1950 (0x79e = 1950).

07 — Crackme 07

Contexte. Comparaison entre 0x0b2b et l’entrée.

Méthodologie.

(gdb) b *<addr_cmp>
(gdb) r
# suivre les deux comparaisons successives

Constat / analyse. Le flot effectue une comparaison ; la résolution de la contrainte donne la chaîne attendue.

Résultat. Mot de passe : 2849.

08 — Crackme 08

Contexte. Pattern mémoire observé ; lecture sur rdi + rcx - 0x4. Pattern répétitif tous les deux caractères.

Méthodologie.

  1. Analyse statique :
objdump -d crackme08 > dump.txt
# repérer .text et adresses pertinentes
  1. Débogage dynamique :
(gdb) b *0x<addr_text>
(gdb) r
# observer accès mémoire (x/s, x/b) sur les offsets rdi+rcx-0x4

Constat / analyse. Accès mémoire répétitif révélant un motif sur deux caractères — reconstitution progressive de la chaîne cible.

Résultat. Mot de passe : g00d3n0ugh.

09 — Crackme 09

Contexte. Vérification directe, simple comparateur.

Méthodologie.

(gdb) b strcmp
(gdb) r
# observer la chaîne attendue

Constat / analyse. Comparaison explicite.

Résultat. Mot de passe : 9876543210.

Scorpion (binaire corrompu)

Format ELF corrompu

Le binaire reste exécutable mais n’est pas déboguable sans correction des headers.

Étapes pour rétablir / analyser.

  1. Diagnostic :
readelf -l -a scorpion
  1. Observations :

    • shnum = 7 erroné (en effet, la taille des headers de section est quant à elle nulle, ce qui est incohérent avec la valeur de shnum).
  2. Corrections appliquées :

    • réécriture des variables shnum, shentsize, shstrndx pour restaurer la cohérence ELF en l’absence effective de headers de section. En pratique, celles-ci ont été mises à 0.
  3. Analyse après correction :

    • identification des routines équivalentes à strlen, génération d’une transformation sur l’entrée (endianness inversion, shifts) ;
    • présence de syscalls non déterministes (arguments dépendants du flot d’exécution).

Restauration d'un binaire ELF

La réparation du format ELF est délicate et documentée dans le cas présent pour garder la traçabilité. Toujours conserver une copie du binaire original.

Owl

Format ELF corrompu

Le binaire reste exécutable mais n’est pas déboguable sans correction des headers.

Observations.

  • On constate dans imhex qu’il y a trois headers de section, le premier étant complètement nul.
  • Le header ELF décrit mal les headers de section.

Restauration ELF.

  • Suppression du header nul et mise à jour de shnum à 2.
  • Fix shentsize = 0x40 (64 octets équivalant à la taille d’un header de section) dont la valeur était initialement nulle.
  • Fix shstrndx = 1.

Exploitation.

  • La portion de code contiguë au point d’entrée se termine par jmp rbx.
  • rbx contient une adresse calculée à partir du nombre d’arguments fournis à owl.
    • Point d’arrêt utile : jmp rbx pour capturer l’adresse de saut et suivre le flot calculé.
    • Il faut un seul argument pour que l’instruction jmp accède à des instructions valides (sinon segfault).
  • En déboguant le programme et en explorant l’exhaustivité des branchements conditionnels par forçage de la valeur des registres via set $<register>, on constate que la chaîne doit être de taille 0x25.
  • On observe qu’une nouvelle chaîne est construite à partir de la chaîne fournie en argument. Cette dernière représente le code ASCII en hexadécimal de la chaîne originale.
  • Ultimement, on somme toutes les valeurs numériques de la chaîne originale, puis de celle produite. Ensuite, on multiplie le résultat obtenu pour la chaîne originale par 2 en faisant un shift left 1. Enfin, on compare les résultats : l’égalité permet d’afficher OK et de terminer le programme normalement.

WARNING

La construction de la chaîne produite implique qu’elle soit deux fois plus longue ; nous avons donc l’intuition d’un point fixe par la transformation appliquée à la chaîne originale. Et en effet, après un parcours de la table ASCII, le caractère 3 est un point fixe — car son code ASCII est 33.

Résultat. Mot de passe : 3333333333333333333333333333333333333.