Introduction
Chaque année les organisateurs de la conférence du SSTIC à Rennes proposent un challenge informatique. Ce défi consiste à retrouver une adresse mail au travers d’une série d'épreuves.
Cette année, le challenge change légèrement puisque l’annonce fait état d’un jeu de rôle et de multiples adresses mails, chacune permettant d’informer les organisateurs de la progression dans le challenge. Le challenge n’est plus linéaire car il suffit d’obtenir 2 points pour valider une étape. Certains challenges valent 1 point, d’autres 2.
Le chemin que j’ai emprunté pour parvenir à la solution est décrit dans ce document. Les codes sources développés sont donnés en annexe. J’ai choisi de délivrer la solution en suivant l’ordre dans lequel j’ai progressé, avec les fausses pistes éventuelles que j’ai pu emprunter, trouvant intéressant de permettre à l’auteur(e) du challenge de voir les pièges dans lesquels un challengeur peut tomber.
Découverte du monde féérique…
Le fichier fourni est une capture réseau au format pcap. Il
s’agit d’une capture d’un échange HTTP contenant le
téléchargement d’un fichier: /challenge2016/challenge.zip
.
Son extraction ne pose aucun problème,
et son contenu nous présente une arborescence et un fichier index.html
.
L’ouverture de ce fichier dans un navigateur web nous permet
de découvrir un monde médiéval fantasstic.
Le jeu démarre dans une maison, et un PNJ (personnage non joueur) explique comment se déplacer et donne la manière de progresser dans le jeu en trouvant des clés sous forme hexadécimales.
…et de quelques trolls
Dans la pièce de départ nous pouvons regarder dragon ball à la télévision (ou pas), interagir avec des personnages PNJ qui donnent des challenges (situés au nord de la carte), se promener dans ce monde mais nous notons que la fouille conscienceuse du linge qui sèche au dessus du Cactus Bar ne nous permet pas de trouver de clé dans les poches.
Ce Cactus Bar est un lieu bien connu à Rennes, et il serait dommage ne pas y faire un tour. La programmation musicale du juke-box est particulièrement réjouissante :) En dehors du juke box, le Cactus Bar nous permet de croiser quelques personnages connus comme News0ft qui veut faire travailler un stagiaire, Ange Albertini qui nous parle de ses fichiers polyglottes, un petit poussin qui fait "miasm", un participant qui a perdu ses lunettes et son portable (nous y reviendrons), et d’autres trolleurs que l’on retrouve chaque année au SSTIC. Un des personnages (sans doute l’auteur des bulletins CERT-FR de l’ANSSI) propose un mot croisé bilingue. Les définitions sont les mots anglais, les solutions l'équivalent en français. Que cet auteur ne soit pas offensé, ce write-up est truffé de mot anglais.
Stage 1
Trois challenges nous sont proposés:
- radio (2 points)
-
ce challenge contient un fichier
rtl2832-f9.452000e+08-s1.000000e+06.bin.lzma
. Le nom de ce fichier permet d’intuiter que son contenu est relatif à de la SDR (Software Defined Radio). - calc (1 point)
-
ce challenge contient un fichier
SSTIC16.8xp
dont l’extension permet de déterminer qu’il s’agit d’un programme pour calculatrice TI-83+ - SOS-fant0me (1 point)
-
Ce challenge contient une capture réseau au format pcap.
La résolution du challenge SOS-fant0me n’ayant pas été très long, je me suis tourné vers l’autre challenge à 1 point (TI-83+) pour passer ce niveau.
SOS-fant0me
Ouverture du pcap
L’ouverture du pcap dans wireshark montre que tous les échanges réseaux sont préfixés des caractères "Gh0st". Pour avoir travaillé sur quelques RAT (Remote Access Tool), cela fait penser au RAT appelé Gh0st. Une recherche google permet également d’aboutir rapidement à cette conclusion.
Gh0st permet de réaliser de la capture clavier, d’exécuter des commandes, et d’uploader ou downloader des fichiers sur le poste victime.
Nous pouvons supposer que le concepteur de l'épreuve aime bien le dessin animé le Roi Lion car les adresses MAC des machines forment le mot HAKUNA MATATA, même si cette information n’aide nullement à résoudre le challenge.
chopshop
chopshop est un outil écrit en python qui permet de déchiffrer les échanges de plusieurs RAT, dont Gh0st. La documentation et les sources de chopshop sont disponibles sur github: https://github.com/MITRECND/chopshop
L’utilisation de chopshop sur la capture pcap nous affiche le résultat fourni en Annexe 1. Les informations nous permettent de voir que la victime a zippé des fichiers avec le mot de passe suivant:
Salut ! Comme pis voici la clef pour le stage 1 ! Le mot de passe de l'archive reste ceui convenu ensemble. [2016/02/27 - 23:15] sstic2016-stage1-solution.zip - Saisir mot de passe Cyb3rSSTIC_2016
Les fautes de typos sont d’origine.
L’extraction des fichiers est possible avec chopshop. Toutefois, l’option d’extraction de chopshop semble ineffective. Un patch des sources pour forcer systématiquement l’extraction permet de les obtenir. Le git diff est fourni en annexe. Les fichiers sont les suivants:
-
how_to_rule_the_world.txt : (non fourni en annexe) est un fichier contenant des symboles franc-maçonniques (pyramide, oeil) le mot SSTIC en majuscule est présent ainsi qu’une référence à un bananier en latin. La raison de la présence de ce fichier m'échappe encore.
-
solution.txt : contient le passe de validation 368BE8C1CC7CC70C2245030934301C15
-
visio_stage2.mp4 : n’est pas du tout une vidéo du stage 2, mais un rickroll. Well played, guys.
Important
|
La clé 368BE8C1CC7CC70C2245030934301C15 permet d’obtenir un point. |
TI-83+
Le fichier SSTIC16.8xp
est un programme pour TI-83+. La TI-83+ est une
calculatrice programmable. La page
wikipedia en donne les caractéristiques
principales.
Le format 8xp
est une version compressée du code source d’un programme de la
TI-83+, une sorte de Basic. Des sites webs proposent la conversion 8xp
vers
une version littérale du Basic de la TI-83+. Le fichier complet est fourni
en annexe.
Recherche de l'émulateur
Une recherche google permet de trouver tilem. Son installation ne pose aucun problème particulier.
Fonctionnement du programme
Le langage de programmation de la TI-83+ n’est pas un langage à fonction
et nous observons de fait un grand nombre de Labels/Goto.
Il est
relativement rapide de transformer ce programme TI-83+ en langage
python en s’aidant d’un site de référence du tibasic comme
http://tibasicdev.wikidot.com/sub.
Cette réécriture m’a permis de comprendre les
étapes principales du programme. Nous avons des fonctions de
bit shifting
, de transformation int
vers binary
et XOR
.
Ce programme est alors simplifié et réécrit en python (fourni en annexe). Nous nous rendons compte qu'écrire les nombres en binaire permet d’obtenir une vue simplifiée des opérations effectuées:
mitsurugi@dojo:~/chall/SSTIC/stage1/calc$ ./break-ti83+.py 456789
Key 00000000-00000110-11111000-01010101
i=0
key_part 01010101
Seed: 11111111-11111111-11111111-11111111
indice 170 (key_part XOR Seed&0xFF)
>>8 Seed: 00000000-11111111-11111111-11111111
L1[170] = 00110110-00000011-01001010-11110110
New Seed: 00110110-11111100-10110101-00001001
i=1
key_part 11111000
Seed: 00110110-11111100-10110101-00001001
indice 241 (key_part XOR Seed&0xFF)
>>8 Seed: 00000000-00110110-11111100-10110101
L1[241] = 11001010-10111010-11000010-10001010
New Seed: 11001010-10001100-00111110-00111111
i=2
key_part 00000110
Seed: 11001010-10001100-00111110-00111111
indice 57 (key_part XOR Seed&0xFF)
>>8 Seed: 00000000-11001010-10001100-00111110
L1[57] = 01011111-00000101-10001000-00001000
New Seed: 01011111-11001111-00000100-00110110
i=3
key_part 00000000
Seed: 01011111-11001111-00000100-00110110
indice 54 (key_part XOR Seed&0xFF)
>>8 Seed: 00000000-01011111-11001111-00000100
L1[54] = 11001111-10111010-10010101-10011001
New Seed: 11001111-11100101-01011010-10011101
807052642
mitsurugi@dojo:~/chall/SSTIC/stage1/calc$
Le programme prend la clef fournie en entrée et effectue 4 fois les mêmes opérations:
-
Un XOR du iième octet de la clé avec le dernier octet de la Seed (initialisée à 232-1) fournit l’indice du tableau de valeurs.
-
Un shift de 8 bits vers la droite pour la Seed
-
La Seed est XOR-ée avec la valeur donnée par le tableau L1
-
Et retour à la première étape
La valeur finale de la Seed est alors XORée une dernière fois avec 232-1 et comparée avec la valeur 3298472535.
Reverse
Le reverse porte bien son nom. Il suffit de répéter ces étapes dans le sens inverse pour obtenir la solution.
Nous observons qu’il est possible de retrouver chacun des indices
du tableau L1 sans se soucier de la clé. En effet il suffit de trouver
quel élément de tableau XOR-é avec la Seed produit 8 zéros en bits
de poids fort et de noter son indice. Un programme
reverse_calc.py
fourni en annexe retrouve chacun des 4 indices
du tableau en partant de la fin. Le shift vers la gauche nous fait
perdre 8 bits d’information, mais nous ne somme pas impactés car
l’opération n’est répétée que 4 fois.
mitsurugi@dojo:~/chall/SSTIC/stage1/calc$ ./reverse_calc.py
New Seed: 00111011-01100101-01001101-10101000
LI[32] = 00111011-01101110-00100000-11001000
_Seed: 00001011-01101101-01100000-00000000 (after <<8)
LI[207] = 00001011-11011011-11011111-00100001
_Seed: 10110110-10111111-00100001-00000000 (after <<8)
LI[63] = 10110110-01100110-00101101-00111101
_Seed: 11011001-00001100-00111101-00000000 (after <<8)
LI[233] = 11011001-11010110-01011010-11011100
_Seed: 11011010-01100111-11011100-00000000 (after <<8)
mitsurugi@dojo:~/chall/SSTIC/stage1/calc$
Les 4 indices du tableau L1 sont dans l’ordre: 233, 63, 67, 32
Nous savons que dans le programme original, l’indice du tableau est calculé comme un XOR entre les 8 bits faible de la Seed et de la clé. Connaissant les indices, nous pouvons déduire la clé en redéroulant le programme, après l’avoir très légèrement modifié:
mitsurugi@dojo:~/chall/SSTIC/stage1/calc$ ./give_key-ti83+.py
i=0
Seed: 11111111-11111111-11111111-11111111
key_part 22
>>8 Seed: 00000000-11111111-11111111-11111111
L1[233] = 11011001110101100101101011011100
New Seed: 11011001-00101001-10100101-00100011
i=1
Seed: 11011001-00101001-10100101-00100011
key_part 28
>>8 Seed: 00000000-11011001-00101001-10100101
L1[63] = 10110110011001100010110100111101
New Seed: 10110110-10111111-00000100-10011000
i=2
Seed: 10110110-10111111-00000100-10011000
key_part 87
>>8 Seed: 00000000-10110110-10111111-00000100
L1[207] = 00001011110110111101111100100001
New Seed: 00001011-01101101-01100000-00100101
i=3
Seed: 00001011-01101101-01100000-00100101
key_part 5
>>8 Seed: 00000000-00001011-01101101-01100000
L1[32] = 00111011011011100010000011001000
New Seed: 00111011-01100101-01001101-10101000
3298472535
Winning key= 89594902
mitsurugi@dojo:~/chall/SSTIC/stage1/calc$
Pour obtenir la clé de validation, il suffit de reprendre l'émulateur TI-83+, et de lui donner ce chiffre. Après quelques secondes, nous voyons s’afficher à l'écran:
Important
|
La clé 57D9F82B49C1EB3993CB82D26E37F69C nous donne le second point et permet de passer le garde. |
Stage 2
Le héros du jeu de rôle arrive dans la neige.
Une fois l’email envoyé à l’organisation du SSTIC pour valider le stage1, il est temps de parcourir ce monde. Une exploration nous permet de découvrir une caverne dans laquelle se trouve une paire de lunettes ainsi qu’un téléphone portable (voir annexe). Il est possible de ramener ces affaires à la personne qui les a perdues dans le cactus Bar, mais cela ne permet pas d’obtenir un point supplémentaire :) Un autre PNJ invite le joueur à porter afl ( http://lcamtuf.coredump.cx/afl/ ) sous windows.
La suite de l’aventure se déroule au chaud, dans l’auberge.
Un PNJ parle d’une mexicaine, d’une société et de questions juridiques mais le sens profond du troll m'échappe. Sans doute que certains initiés comprennent, l’aide de Captain Obvious serait appréciée.
Trois challenges sont proposés:
- loader (1 point)
-
Ce challenge contient un binaire 32 bits pour Windows (GUI)
- Huge (1 point)
-
Ce challenge contient un fichier huge.tar dont l’extraction n’est pas triviale
- foo (1 point)
-
Ce challenge contient un fichier foo.efi qui est une application EFI (Extensible Firmware Interface)
Le fichier huge a attiré mon attention car il est peu fréquent de manipuler des fichiers de 12To (surtout lorsqu’ils ne font que 109ko dans une archive non compressée):
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ ls -l huge.tar
-rw-r--r-- 1 mitsurugi mitsurugi 109568 janv. 1 1980 huge.tar
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ tar tvf huge.tar
-rwxr-xr-x 0/0 128574140715008 1970-01-01 01:00 Huge
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$
J’ai ensuite choisi de reverser le fichier foo.efi car je n’avais pas eu l’occasion de m’intéresser à l’EFI jusqu'à présent.
Huge
Comme nous venons de le voir, le fichier Huge fait 12 Teraoctets. Nous soupçonnons que le binaire ne va pas prendre tout cet espace sur le disque en raison de la petite taille de son archive tar. Nous allons étudier ce fichier tar pour savoir s’il s’agit rééllement de 12To de données, ou si c’est une archive plus petite qui aurait été modifiée à l'éditeur hexadécimal afin d’afficher une taille différente de la taille réelle:
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ strings huge.tar | head -15
PaxHeader/Huge
000755
000000
000000
00000000277 00000000000 011762
ustar
000000
000000
24 size=128574140715008
30 ctime=1456948527.914039536
30 atime=1456948548.569742108
22 GNU.sparse.major=1
22 GNU.sparse.minor=0
24 GNU.sparse.name=Huge
39 GNU.sparse.realsize=128574140715008
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$
La signification de sparse est très bien expliquée par wikipedia: In computer science, a sparse file is a type of computer file that attempts to use file system space more efficiently when the file itself is mostly empty. This is achieved by writing brief information (metadata) representing the empty blocks to disk instead of the actual "empty" space which makes up the block, using less disk space.
Les filesystems linux supportent à peu près tous les sparse file, l’extraction du fichier tar ne devrait poser aucun problème.
Décompactage du binaire
L’extraction du fichier n’est pas possible de manière directe:
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ tar xvf huge.tar
Huge
tar: Huge : Positionnement à 25297854992384 impossible: Argument invalide
tar: Arrêt avec code d'échec à cause des erreurs précédentes
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ mount | grep root
/dev/mapper/dojo--vg-root on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$
Le filesystem ext4 supporte des fichiers de 12To, seulement si des options particulières ont été choisies, qui semblent absentes sur ce disque. Le plus simple consiste à utiliser un fichier en mode loop, et de le formatter en btrfs qui accepte des fichiers de 12To avec les options par défaut de formatage.
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ dd if=/dev/zero of=disk.btrfs bs=1024k count=100
100+0 enregistrements lus
100+0 enregistrements écrits
104857600 octets (105 MB) copiés, 0,047601 s, 2,2 GB/s
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ sudo mkfs.btrfs disk.btrfs
SMALL VOLUME: forcing mixed metadata/data groups
Btrfs v3.17
See http://btrfs.wiki.kernel.org for more information.
Turning ON incompat feature 'mixed-bg': mixed data and metadata block groups
Turning ON incompat feature 'extref': increased hardlink limit per file to 65536
Created a data/metadata chunk of size 8388608
ERROR: device scan failed 'disk.btrfs' - Block device required
fs created label (null) on disk.btrfs
nodesize 4096 leafsize 4096 sectorsize 4096 size 100.00MiB
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ sudo mount -t btrfs -o loop disk.btrfs /mnt/hd/
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ sudo chown mitsurugi.mitsurugi /mnt/hd/
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ cp huge.tar /mnt/hd/
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ cd /mnt/hd/
mitsurugi@dojo:/mnt/hd$ tar xvf huge.tar
Huge
mitsurugi@dojo:/mnt/hd$ ls -l
total 212
-rwxr-xr-x 1 mitsurugi mitsurugi 128574140715008 janv. 1 1970 Huge
-rw-r--r-- 1 mitsurugi mitsurugi 109568 mai 17 17:59 huge.tar
mitsurugi@dojo:/mnt/hd$ df -h .
Sys. de fichiers Taille Utilisé Dispo Uti% Monté sur
/dev/loop0 100M 32K 96M 1% /mnt/hd
mitsurugi@dojo:/mnt/hd$
Il devient dès lors possible de lancer ce programme ou d’utiliser gdb pour le debugger. Toutefois, ce programme semble être bloqué quelque part:
mitsurugi@dojo:/mnt/hd$ ./Huge
Please enter the password: 00112233445566778899AABBCCDDEEFF
(... plus rien)
Un debugger attaché depuis un autre terminal permet de comprendre l'état du programme:
mitsurugi@dojo:~$ ps ax | grep Huge
3682 pts/2 R+ 0:04 ./Huge
mitsurugi@dojo:~$ gdb -q
gdb$ attach 3682
Attaching to process 3682
Reading symbols from /mnt/hd/Huge...(no debugging symbols found)...done.
gdb$ x/10i $rip
=> 0x43b1b65e3097: add BYTE PTR [rax],al
0x43b1b65e3099: add BYTE PTR [rax],al
0x43b1b65e309b: add BYTE PTR [rax],al
0x43b1b65e309d: add BYTE PTR [rax],al
0x43b1b65e309f: add BYTE PTR [rax],al
0x43b1b65e30a1: add BYTE PTR [rax],al
0x43b1b65e30a3: add BYTE PTR [rax],al
0x43b1b65e30a5: add BYTE PTR [rax],al
0x43b1b65e30a7: add BYTE PTR [rax],al
0x43b1b65e30a9: add BYTE PTR [rax],al
gdb$
Et en assembleur x86_64, l’instruction add BYTE PTR [rax],al
s'écrit avec des 0x00
.
Nous sommes donc en face d’un binaire de 12To massivement rempli de zéros,
et dont le flot d’exécution arrive dans une série de zéros. Le programme n’est
pas réellement "bloqué", il est juste en train de parcourir une (très très) longue
suite d’instructions ne faisant rien (al
valant dans le cas présent 0).
Ecriture d’un slider de zéros
Pour reverser ce binaire, il faut être capable d’analyser ses instructions
pertinentes. Je décide alors d'écrire un "slider" qui saurait dire à quelle
adresse se situe la prochaine instruction autre que add BYTE PTR [rax],al
.
Avec ce slider, il sera possible de sauter directement via gdb à cette
instruction.
Pour écrire un tel slider, nous devons savoir comment est construit l’exécutable sur disque, et comment il est mappé en mémoire.
Son mapping en mémoire se fait en trois parties:
mitsurugi@dojo:/mnt/hd$ readelf -l Huge -W
Type de fichier ELF est EXEC (fichier exécutable)
Point d'entrée 0x51466a42e705
Il y a 3 en-têtes de programme, débutant à l'adresse de décalage64
En-têtes de programme :
Type Décalage Adr. vir. Adr.phys. T.Fich. T.Mém. Fan Alignement
LOAD 0x001000 0x00002b0000000000 0x00002b0000000000 0x1ef000000000 0x1ef000000000 R E 0x1000
LOAD 0x2afffffe1000 0x000049f000000000 0x000049f000000000 0x161000000000 0x161000000000 R E 0x1000
LOAD 0x49effffe1000 0x0000000000020000 0x0000000000020000 0x2afffffe0000 0x2afffffe0000 R E 0x1000
mitsurugi@dojo:/mnt/hd$
Pour connaitre les zones ou sont situés des données sur le disque, nous pouvons étudier son archive. La sauvegarde d’un fichier sparse par tar numérote simplement les blocs contenant des données, et stocke les blocs en fin de fichiers. Le début de fichier fournit la taille des zones remplies de zéros suivies par les blocs. La sauvegarde tar de Huge donne les valeurs suivantes:
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ strings huge.tar
(... snip ...)
4096
1627791495168
4096
1627791519744
4096
11168083808256
4096
(... etc...)
4096
128019591507968
4096
128574140710912
4096
J’ai décidé de prendre chacune des valeurs données ici, et de parcourir
8096 octets de données en affichant la première adresse différente de zéro
(8096 car un bloc fait cette taille, les autres sont de 4096).
Le programme seek.py
fourni en annexe réalise cette tache.
Ce programme
permet de connaitre tous les offsets de Huge contenant du code actif
et non pas des 0x00
. En s’appuyant sur ces offsets et la conversion
adresse du programme en mémoire → offset du programme sur disque
et inversement, nous pouvons dire ou se situe la prochaine instruction
utile lorsque le flot d’instruction du programme est située dans un
océan de 0. Le programme slide.py
réalisant cette tache est fourni
en annexe. Exemple:
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$ ./slide.py 0x43b1b65e3097
zone 2
Next data block can be found at: 0x49e7e5410000
mitsurugi@dojo:~/chall/SSTIC/stage2/huge$
Résolution
La résolution de ce challenge est rendu possible avec slide.py et gdb.
Ce programme ne semble pas obfusqué ni contenir de mécanisme de défense anti-analyse. Le début se lit ainsi:
Adresse du point d'entrée: 0x51466a42e705
=> 0x51466a42e705: sub rsp,0x1000
0x51466a42e70c: and rsp,0xfffffffffffffc00
0x51466a42e713: mov rbp,rsp
0x51466a42e716: xor rax,rax
0x51466a42e719: inc rax
0x51466a42e71c: xor rdi,rdi
0x51466a42e71f: inc rdi
0x51466a42e722: movabs rsi,0x5a48156509b7 // Please enter the password:
0x51466a42e72c: mov edx,0x1b
0x51466a42e731: syscall //puts
0x51466a42e733: xor rax,rax
0x51466a42e736: xor rdi,rdi
0x51466a42e739: mov rsi,rbp
0x51466a42e73c: mov edx,0x400
0x51466a42e741: syscall //gets
0x51466a42e743: mov rax,rsp
0x51466a42e746: mov rdi,rsp
0x51466a42e749: mov rsi,rsp
0x51466a42e74c: movabs r15,0x10f338cf000c //check password scheme (32 hex chars)
0x51466a42e756: call r15
0x51466a42e759: movabs rbx,0x43abdb4a000c //check password
0x51466a42e763: jmp rbx
La fonction vérifiant que le password entré fait bien 32 caractères hexdécimaux n’est pas analysée ici, n’apportant aucune information.
L’analyse débute à 0x43abdb4a000c
, le mot de passe est sur la stack à rsp
ou rsp+8
selon son usage.
Chaque jump et/ou call pointant systématiquement sur des 0x00
, il suffit
d’utiliser le programme slide.py
pour modifier rip
et continuer
d’analyser le programme. En résumé, nous
avons ces suites d’instructions:
0x43abdb4a0aee: nop
0x43abdb4a0aef: movabs $0x43abdb4a0b0b,%rbx
0x43abdb4a0af9: cmpb $0x29,(%rsp) //check si pass[0]==0x29
0x43abdb4a0afd: jne 0x43abdb4a0b09
0x43abdb4a0aff: movabs $0x4a170682000c,%rbx
0x43abdb4a0b09: jmpq *%rbx
(...)
0x4a170682ede1: cmp WORD PTR [rsp+0x2],0xd17e //check si pass+0x2==0xd17e
0x4a170682ede8: movabs rbx,0x6f4b0e0000c
0x4a170682edf2: movabs r14,0x4a170682ee02
0x4a170682edfc: cmovne rbx,r14
0x4a170682ee00: jmp rbx
(...)
0x6f4b0e0f370: cmp BYTE PTR [rsp+0xb],0x8c //pass+0xb == 0x8c
0x6f4b0e0f375: movabs rbx,0x6f4b0e0f38f
0x6f4b0e0f37f: movabs r14,0x49e7e541000c
0x6f4b0e0f389: cmove rbx,r14
0x6f4b0e0f38d: jmp rbx
(...)
0x49e7e541be19: push rax
0x49e7e541be1a: lea rax,[rsp+0x10b]
0x49e7e541be22: movzx rbx,BYTE PTR [rsp+0x9] //1 char du mdp
0x49e7e541be28: mov BYTE PTR [rax],0x0
0x49e7e541be2b: lea rcx,[rip+0x6] # 0x49e7e541be38
0x49e7e541be32: lea rcx,[rcx+rbx*2]
0x49e7e541be36: jmp rcx
(...)
0x49e7e541be38: add BYTE PTR [rax],al
(...uniquement des add BYTE PTR[rax],al...)
0x49e7e541c036: add BYTE PTR [rax],al
0x49e7e541c038: cmp BYTE PTR [rax],0x65
0x49e7e541c03b: movabs rbx,0x352845ab000c
0x49e7e541c045: movabs r14,0x49e7e541c056
0x49e7e541c04f: cmovne rbx,r14
0x49e7e541c053: pop rax
0x49e7e541c054: jmp rbx
Cette dernière construction demande un peu d’explications. Le caractère du
password est transformé en une distance sur laquelle il faut sauter.
Ce saut emmène dans une zone de 0x00
dans laquelle la valeur de al
n’est pas nulle. Dit autrement, la valeur pointée par une adresse
sera modifiée selon un des caractères du mot de passe. Cette valeur est
alors comparée à une référence. Une fois le chemin effectué à rebours
nous obtenons comme caractère pour le mot de passe: 0x89
. Le
programme continue:
0x352845ab3bce: lea rbx,[rip+0x0] # 0x352845ab3bd5
0x352845ab3bd5: xor ebx,DWORD PTR [rsp+0xc]
0x352845ab3bd9: cmp ebx,0xa9b00f5c //check si 45ab3bd5 XOR 0xa9b00f5c = EC1B3489 = pass+0xc
0x352845ab3bdf: movabs rbx,0x352845ab3bf9
0x352845ab3be9: movabs r14,0x59cb440c000c
0x352845ab3bf3: cmove rbx,r14
0x352845ab3bf7: jmp rbx
(...)
0x59cb440c4524: movabs rbx,0x59cbc8cc0b83
0x59cb440c452e: mov rcx,QWORD PTR [rip+0x19] // 0x59cb440c454e
0x59cb440c4535: push rax //
0x59cb440c4536: mov eax,DWORD PTR [rsp+0x10] //2e partie du mdp
0x59cb440c453a: xor rax,rbx
0x59cb440c453d: movabs rbx,0x2a7ee24a000c
0x59cb440c4547: cmpxchg rcx,rbx //il faut que rax=rcx
0x59cb440c454b: pop rax //donc rax=hex((0x59cbc8cc0b83 ^ 0x59cb440c4556))=0x8cc04ed5
0x59cb440c454c: jmp rcx
(...)
0x2a7ee24aae24: nop
0x2a7ee24aae25: push rax
0x2a7ee24aae26: lea rdi,[rsp+0x18] //2e partie du mot de passe
0x2a7ee24aae2b: lea rsi,[rip+0x42]
0x2a7ee24aae32: mov ecx,0xa
0x2a7ee24aae37: rep movs BYTE PTR es:[rdi],BYTE PTR ds:[rsi]
0x2a7ee24aae39: mov ebx,DWORD PTR [rsp+0xc] //password+4 : ddccbbaa
0x2a7ee24aae3d: xor DWORD PTR [rsp+0x1c],ebx //XOR avec 0x24b87838
0x2a7ee24aae41: fclex
0x2a7ee24aae44: fld TBYTE PTR [rsp+0x18] //on load les 10 octets 4 fixe + 4 du xor + 2 fixes
0x2a7ee24aae48: fld st(0) //on duplique l'info
0x2a7ee24aae4a: fcos //on fait un cosinus
0x2a7ee24aae4c: fcompp //on compare les 2 valeurs
0x2a7ee24aae4e: fstsw ax
0x2a7ee24aae51: and ax,0xffdf
0x2a7ee24aae55: cmp ax,0x4000 //ax doit valoir: 0x4000 ou 0x4020
0x2a7ee24aae59: movabs rbx,0x99a3805000c
0x2a7ee24aae63: movabs r14,0x2a7ee24aae7e
0x2a7ee24aae6d: cmovne rbx,r14
0x2a7ee24aae71: pop rax
0x2a7ee24aae72: jmp rbx
Cette dernière suite d’instructions un peu compliquée au premier abord peut se simplifier.
Nous prenons 4 octets du mot de passe, ce qui nous permet de construire un
flottant (les 6 autres octets sont en dur dans le programme). Ensuite la
comparaison cos(x)==x
est testée.
x tel que cos(x)=x est une valeur connue (voir http://oeis.org/A003957). La
littérature autour de ce chiffre explique que cosinus est un attracteur: il
suffit de répéter x=cos(x) suffisemment de fois pour obtenir ce chiffre.
J’ai alors écrit un simple script gdb-python:
#! /usr/bin/python
import gdb
for i in range(250):
gdb.execute('set $rip=0x2a7ee24aae4a')
gdb.execute('nexti')
data=gdb.execute('p $st0', to_string=True)
print "DATA : " + data
et le lancement du script à l’adresse 0x2a7ee24aae4a
fait calculer au CPU
automatiquement la bonne valeur. Après XOR nous obtenons les 4 octets
de la clé: 998CD6D4
Une fois tous ces octets remis dans le bon sens (big endian/little endian)
nous obtenons une clé: 29897ed1d4d68c99d54ec08c89341bec
. Cette valeur n’est
pas la clé qui valide le point de niveau. Le programme Huge effectue
plusieurs opérations ensuite avant d’afficher la clé. Plutôt que de les reverser,
j’ai préféré faire travailler le programme Huge lui-même. Un script gdb
est mis en place, script plaçant des breaks au bon endroit, et jumpant
aux bonnes adresses (script fourni
en annexe):
mitsurugi@dojo:/mnt/hd$ gdb -q Huge
Reading symbols from Huge...(no debugging symbols found)...done.
gdb$ source break_and_slide
Breakpoint 1 at 0x10f338cf000c
Breakpoint 2 at 0x43abdb4a000c
Breakpoint 3 at 0x43abdb4a0aee
Breakpoint 4 at 0x4a170682000c
Breakpoint 5 at 0x49e7e541be18
Breakpoint 6 at 0x352845ab000c
Breakpoint 7 at 0x59cb440c000c
Breakpoint 8 at 0x2a7ee24a000c
Breakpoint 9 at 0x99a3805000c
gdb$ run
Starting program: /mnt/hd/Huge
Please enter the password: 29897ed1d4d68c99d54ec08c89341bec
Breakpoint 1, 0x000010f338cf000c in ?? ()
--Checking password scheme
Breakpoint 2, 0x000043abdb4a000c in ?? ()
--Going to check pass
Breakpoint 3, 0x000043abdb4a0aee in ?? ()
--checking password[0]==0x29
Breakpoint 4, 0x00004a170682000c in ?? ()
--checking that pass[2:3]==0x7ed1
Breakpoint 5, 0x000049e7e541be18 in ?? ()
--checking that pass[1]=0x89
Breakpoint 6, 0x0000352845ab000c in ?? ()
--check that last 4 chars of pass==89341bec
Breakpoint 7, 0x000059cb440c000c in ?? ()
--check passwords[0x8:0xc]==d54ec08c
Breakpoint 8, 0x00002a7ee24a000c in ?? ()
--check pass[4:8]==d4d68c99
Breakpoint 9, 0x0000099a3805000c in ?? ()
--All done..
The key is: E574B514667F6AB2D83047BB871A54F5
[Inferior 1 (process 4512) exited normally]
gdb$
Important
|
La clé vaut: E574B514667F6AB2D83047BB871A54F5 |
Nous pouvons désormais nous intéresser à la deuxième partie de
l'épreuve, le fichier foo.efi
.
foo.efi
Le fichier foo.efi
est un programme pour EFI (Extensible Firmware
Interface):
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$ file foo.efi
foo.efi: PE32 executable (DLL) (EFI application) EFI byte code, for MS Windows
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$
Avant le challenge, je ne connaissais pas ce concept d’EFI application. La lecture de plusieurs documentations (non citées ici) m’a permis de comprendre que l’UEFI peut être vu comme un environnement d’exécution. J’ai choisi une approche dynamique + statique pour analyser ce programme.
Installation d’un émulateur et debugger
Quelques recherches internet mènent relativement rapidement à http://www.tianocore.org/ovmf/ qui est un émulateur UEFI pour qemu. La documentation associée est assez claire, et pour accéder à des fichiers depuis l’UEFI il suffit de créer un pseudo disque formatté en vfat. Le premier lancement de foo.efi se fait de cette manière:
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$ mkdir hda
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$ cp foo.efi hda
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$ qemu-system-x86_64 -cpu qemu64 -bios OVMF.fd -hda fat:hda/ -nographic
(...)
UEFI Interactive Shell v2.0
EDK II
UEFI v2.40 (EDK II, 0x00010000)
Mapping table
FS0: Alias(s):HD7a1:;BLK3:
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
BLK2: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
BLK4: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
BLK0: Alias(s):
PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)
BLK1: Alias(s):
PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x1)
Press ESC in 1 seconds to skip startup.nsh or any other key to continue.
Shell> FS0:
FS0:\> foo
UEFI checker
Missing key ?
Error reported: Invalid Parameter
FS0:\>
Qemu nous donne accès au shell EFI. Il suffit alors de changer de disque
avec la commande FS0:
et de démarrer le programme foo
.
Un environnement d’exécution n’est pas suffisant, il faut également
un debugger pour étudier le fonctionnement interne du programme.
D’autres recherches sur internet mènent vers le debugger EBCDebugger
dont la documentation se trouve : http://jelezo.com.ua/image/EBC-Debugger/EBC_Debugger_User_Manual.pdf
Nous retrouvons toutes les fonctions classiques d’un debugger:
affichage des registres, fonctionnement pas à pas, inspection
de la mémoire, de la stack et possibilité d’ajout ou suppression de
breakpoints. Je note deux options particulièrement intéressantes:
BOC |
Break On Call, permet de rompre le flot d’exécution à chaque appel de fonction |
BOR |
Break On Ret, permet de rompre le flot d’exéction à chaque retour de fonction |
Il est important de noter que d’un lancement à l’autre, les adresses
du programme foo.efi
diffèrent. Si vous rejouez les commandes
citées ici, vous risquez d’avoir d’autres adresses.
Pour résoudre ce challenge, il suffit de savoir que le debugger présente 8 registres R0 → R7, que R0 est l’adresse de la stack et R7 contient le code retour des fonctions.
Pour activer le debugger, il faut dézipper l’ensemble des fichiers
de EDBDebugger
dans le répertoire hda/
et appeler le debugger depuis le shell EFI:
Shell> FS0:
FS0:\> load EBCDebugger/EbcDebugger/X64/EbcDebugger.efi
(... ok)
FS0:\> foo 1234567890abcdef1234567890abcdef
Break on Entrypoint
EDB >
EDB > est le prompt interactif du debugger.
Résolution
J’ai utilisé les options BOC
et BOR
pour obtenir une vue
d’avion du programme. Les commandes g
et k
permettent de démarrer le
programme et d’afficher la pile d’appel (callstack), je donne en format
brut les commandes permettant de progresser
depuis l’invite de commande de EDB.
1/ On lance foo en BOC et BOR sans argument: on cherche la fonction de détection de l'argument:
EDB > g
Break on CALL
000005B93ED0: 83 10 9A FD FF
000005B93ED5: FF CALL R0(0xFFFFFD9A)
EDB > g
UEFI checker
Break on CALL
000005B93D0E: 83 10 A8 FC FF
000005B93D13: FF CALL R0(0xFFFFFCA8)
EDB > k
Call-Stack (TOP):
Caller Callee Name
================== ================== ========
0x0000000005B93ED0 0x0000000005B93C70 Fonction qui écrit UEFI checker?
0xFFFFFFFFFFFFFFFF 0x0000000005B93DA0 Le main, sans doute.
EDB > g
Missing key ?
Break on RET
000005B93C0E: 04 00 RET
EDB > k
Call-Stack (TOP):
Caller Callee Name
================== ================== ========
0x0000000005B93D0E 0x0000000005B939BC
0x0000000005B93ED0 0x0000000005B93C70
0xFFFFFFFFFFFFFFFF 0x0000000005B93DA0
Puis on fait trois g (pour les trois RET), et on sort
2/ On lance avec un argument, mais mal formaté: (attention aux adresses qui changent)
FS0:\> foo 123456
EDB > g
Break on CALL
0000064F4ED0: 83 10 9A FD FF
0000064F4ED5: FF CALL R0(0xFFFFFD9A)
EDB > g
UEFI checker //il écrit bien UEFI checker
Break on CALL
0000064F4D0E: 83 10 A8 FC FF
0000064F4D13: FF CALL R0(0xFFFFFCA8)
EDB > g
Key must be exactly 32 characters <-- Ok
Break on RET
0000064F4C0E: 04 00 RET
EDB > k
Call-Stack (TOP):
Caller Callee Name
================== ================== ========
0x00000000064F4D0E 0x00000000064F49BC Donc c'est la fonction de check de format
0x00000000064F4ED0 0x00000000064F4C70
0xFFFFFFFFFFFFFFFF 0x00000000064F4DA0
Et une suite de G permet de sortir
3/ Avec une clé d'un bon format:
FS0:\> foo 1234567890abcdef1234567890abcdef
Break on Entrypoint
EDB > g
Break on CALL
0000064F4ED0: 83 10 9A FD FF
0000064F4ED5: FF CALL R0(0xFFFFFD9A)
EDB > g
UEFI checker
Break on CALL
0000064F4D0E: 83 10 A8 FC FF
0000064F4D13: FF CALL R0(0xFFFFFCA8)
EDB > g
Break on RET //on est passé sans aucun message
0000064F4C6E: 04 00 RET
EDB > k
Call-Stack (TOP):
Caller Callee Name
================== ================== ========
0x00000000064F4D0E 0x00000000064F49BC
0x00000000064F4ED0 0x00000000064F4C70
0xFFFFFFFFFFFFFFFF 0x00000000064F4DA0
EDB > g
Break on CALL
0000064F4D2A: 83 10 00 F8 FF
0000064F4D2F: FF CALL R0(0xFFFFF800)
EDB > g
Break on CALL
0000064F4936: 83 10 C4 FA FF
0000064F493B: FF CALL R0(0xFFFFFAC4)
On steppe un coup, et:
EDB > k
Call-Stack (TOP):
Caller Callee Name
================== ================== ========
0x00000000064F4936 0x00000000064F4400 // ??
0x00000000064F4D2A 0x00000000064F4530 // ??
0x00000000064F4ED0 0x00000000064F4C70 // doit être utile, mais à quoi?
0xFFFFFFFFFFFFFFFF 0x00000000064F4DA0 // Le main
EDB > g
Break on RET
0000064F444E: 04 00 RET
EDB > g
Break on CALL
0000064F4936: 83 10 C4 FA FF
0000064F493B: FF CALL R0(0xFFFFFAC4) //Retour dans la même fonction
(...)
0000064F493B: FF CALL R0(0xFFFFFAC4) //Et encore,
(...)
0000064F493B: FF CALL R0(0xFFFFFAC4) //Et encore,
(... cet appel à cette fonction se répète 16 fois)
EDB > g
Break on RET
0000064F444E: 04 00 RET
EDB > g
Break on RET
0000064F49BA: 04 00 RET
Nous avons donc utilisé 16x la fonction R0(0xFFFFFAC4)
EDB > k
Call-Stack (TOP):
Caller Callee Name
================== ================== ========
0x00000000064F4D2A 0x00000000064F4530 // sert à écrire Sorry
0x00000000064F4ED0 0x00000000064F4C70
0xFFFFFFFFFFFFFFFF 0x00000000064F4DA0
EDB > g
Sorry :(
Et ensuite on sort.
En résumé:
Caller Callee Name
================== ================== ========
0x00000000064F4936 0x00000000064F4400 // va être appelée 16x, donc validation du pass
0x00000000064F4D2A 0x00000000064F4530 // Ecrit Sorry :( si on échoue la validation du pass
0x00000000064F4ED0 0x00000000064F4C70 // Ecrit UEFI Checker
0xFFFFFFFFFFFFFFFF 0x00000000064F4DA0 // Le main
Nous avons observé que la fonction 0x00000000064F4400
est appelée 16 fois.
Nous en déduisons que le mot de passe est analysé ou transformé par cette
fonction. Une analyse plus poussée de celle-ci doit donc être faite, sans que
nous nous intéressions plus aux fonctions précédentes.
EDB > l 50
List Number: 50
0000064F4400: 60 00 10 80 MOVqw R0, R0(-0,-16)
0000064F4404: 5D 88 20 00 MOVbw @R0, @R0(+0,+32) // on a un caractère du pass
0000064F4408: 77 68 04 00 08
0000064F440D: 00 MOVIdw @R0(+0,+4), 8 // on met 8 dans @R0+4
0000064F440E: 5F 87 81 10 MOVdw R7, @R0(+1,+32) // vaut le rang du char du pass
0000064F4412: 5F 84 04 00 MOVdw R4, @R0(+0,+4) // R4 vaut 8
0000064F4416: 12 47 MOD R7, R4 // R7=R7 modulo 8
0000064F4418: 9F 78 81 10 MOVdw @R0(+1,+32), R7 // R7 est remis dans R0+1+32
0000064F441C: 5D 87 20 00 MOVbw R7, @R0(+0,+32) // 1 char du pass
0000064F4420: 5F 84 81 10 MOVdw R4, @R0(+1,+32) // le résultat précédent
0000064F4424: 19 47 ASHR R7, R4 // shift right
0000064F4426: 9D 78 20 00 MOVbw @R0(+0,+32), R7 // Et on le remet à R0+32
0000064F442A: 1D 87 MOVbw R7, @R0 // on sauvegarde @R0 dans R7
0000064F442C: 5F 84 81 10 MOVdw R4, @R0(+1,+32) // et on reprend le @R0+1+32 rang du char
0000064F4430: 0B 44 NEG R4, R4 // Negation
0000064F4432: 5F 85 04 00 MOVdw R5, @R0(+0,+4) // R5 contient 8
0000064F4436: 0C 45 ADD R5, R4 // OK on a le modulo 8
0000064F4438: 17 57 SHL R7, R5 // on SHL du résultat
0000064F443A: 1D 78 MOVbw @R0, R7 // OK, une fois shifté, on prend la val
0000064F443C: 5D 87 20 00 MOVbw R7, @R0(+0,+32) // le char du pass dans R7
0000064F4440: 1D 84 MOVbw R4, @R0 // ok pour R4
0000064F4442: 15 47 OR R7, R4 // un OR (pas XOR)
0000064F4444: 0A 77 NOT R7, R7 // le NOT
0000064F4446: 21 77 MOVbd R7, R7 // ne conserve qu'un seul byte
0000064F4448: 23 77 MOVdd R7, R7 // sans doute inutile?
0000064F444A: 60 00 10 00 MOVqw R0, R0(+0,+16) // on rebaisse la stack
0000064F444E: 04 00 RET
Une observation empirique montre que les caractères fournis en argument au programme foo.efi sont ceux retrouvés en début de cette fonction.
Cette fonction s'écrit en python:
#! /usr/bin/python
import sys
if len(sys.argv) < 3:
print "Give an hex value and a rank"
exit (1)
key=int(sys.argv[1],16)
rank=(int(sys.argv[2]))%8
shl=key >> rank
shr=(key << (8-rank))&0xff
output=shl+shr
print "out is" + hex((~output)&0xff)
Nous notons que cette fonction est reversible. Nous fournissons un second fichier qui permet de reverser une chaine de caractère:
#! /usr/bin/python
import sys
def unmouling(key,rank):
shl=(key << rank)&0xff
shr=(key >> (8-rank))
output=shl+shr
return hex((~output)&0xff)
if len(sys.argv) == 2:
long_key=sys.argv[1]
out_key=[]
for i in range(0,len(long_key),2):
key=int(long_key[i:i+2],16)
rank=(i/2)%8
out=unmouling(key,rank)[2:]
if len(out)==1:
out="0"+out
out_key.append(out)
print "Key unmouling is: " + ''.join(out_key)
Après avoir reversé cette fonction, nous décidons de nous intéresser à la partie de code qui appelle cette fonction afin de comprendre ce qui est fait du résultat.
000005B9391A: 63 87 84 85 00
000005B9391F: 00 MOVdd R7, @R0(+0,+34180) //Vaut 0 au premier tour
000005B93920: 5C 77 EXTNDD R7, R7
000005B93922: 73 84 01 5F 08
000005B93927: 10 MOVnd R4, @R0(+1,+137152) //on récupère R4
000005B93928: 4C 74 ADD R4, R7 //on avance du rang du mot de passe
000005B9392A: 1D C8 MOVbw @R0, @R4 //l'adresse est sauvée sur la stack
000005B9392C: E3 88 01 00 00
000005B93931: 10 84 85 00 00 MOVdd @R0(+1,+0), @R0(+0,+34180) //et le rang est sauvé juste après
000005B93936: 83 10 C4 FA FF
000005B9393B: FF CALL R0(0xFFFFFAC4) <-- la fonction reversée juste au dessus
000005B9393C: 61 84 B4 85 00
000005B93941: 00 MOVbd R4, @R0(+0,+34228) //vaut 0 et va évoluer ensuite
000005B93942: 64 05 94 85 00
000005B93947: 00 MOVqd R5, R0(+0,+34196) //c'est une adresse (5B91EB4)
000005B93948: 63 81 84 85 00
000005B9394D: 00 MOVdd R1, @R0(+0,+34180) //vaut aussi 0 (à priori le rang)
000005B9394E: 5C 11 EXTNDD R1, R1
000005B93950: 4C 15 ADD R5, R1 //on avance de ce rang
000005B93952: 1D D5 MOVbw R5, @R5 //Vaut CB --> on y reviendra
000005B93954: 16 57 XOR R7, R5 //ok
000005B93956: 15 74 OR R4, R7 //ok
000005B93958: A1 48 B4 85 00
000005B9395D: 00 MOVbd @R0(+0,+34228), R4 //on sauve le résultat -> pour tour 2 etc..
000005B9395E: 63 87 84 85 00
000005B93963: 00 MOVdd R7, @R0(+0,+34180) //on prend La valeur
000005B93964: 60 77 01 00 MOVqw R7, R7(+0,+1) //on incrémente
000005B93968: A3 78 84 85 00
000005B9396D: 00 MOVdd @R0(+0,+34180), R7 //on sauve
000005B9396E: 63 87 84 85 00
000005B93973: 00 MOVdd R7, @R0(+0,+34180) //on la reprend
000005B93974: 2F 07 10 00 CMPIwgte R7, 16 //on compare si < 16
000005B93978: 82 D0 JMP8cc 0xD0 //et on jump pour recommencer
Le code se lit assez bien. Nous prenons chaque caractère du mot de passe, lui effectuons une transformation à l’aide de la fonction vue précédemment et XOR-ons le résultat caractère après caractère à une chaine statique.
Le résultat de ce XOR est alors OR-é tour après tour. Nous comprenons alors que cette valeur ne peut valoir 0 que si l’ensemble des XOR précédents valent eux aussi 0. Avoir 0 en résultat est également une valeur intéressante car elle permet d’avoir une seule chaine en entrée qui produise 0. Toute autre valeur en sortie aurait plusieurs possibilités de chaine entrée.
Le debugger permet d’extraire la chaine placée en R5
:
EDB > dd 5B91EB4 4 (l'adress R5 avec laquelle on XOR)
000005B91EB4: B1DC41CB 704697D8 98E97F5A E7CC1AF1
En retournant les octets (big endian/little endian encore) nous obtenons la valeur de CB41DCB1D89746705A7FE998F11ACCE7. Nous décidons alors de connaitre la chaine à fournir à foo pour obtenir cette valeur. La sortie de la fonction effectuant ces opérations vaudra 0.
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$ ./unmouling.py CB41DCB1D89746705A7FE998F11ACCE7
Key unmouling is: 347d8c72720d6ec7a501583be0bccc0c
mitsurugi@dojo:~/chall/SSTIC/stage2/foo$
Un simple test sous UEFI nous indique que le challenge est terminé (!), la clé étant valide. N’ayant pas reversé le reste du code, je m’attendais à d’autres opérations avant l’affichage de succès ou échec.
UEFI Interactive Shell v2.0
EDK II
UEFI v2.40 (EDK II, 0x00010000)
Mapping table
FS0: Alias(s):HD7a1:;BLK3:
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
BLK2: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
BLK4: Alias(s):
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
BLK0: Alias(s):
PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)
BLK1: Alias(s):
PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x1)
Press ESC in 1 seconds to skip startup.nsh or any other key to continue.
Shell> FS0:
FS0:\> foo 347d8c72720d6ec7a501583be0bccc0c
UEFI checker
Success!
FS0:\>
Important
|
la clé de validation vaut 347D8C72720D6EC7A501583BE0BCCC0C |
L’utilisation de la clé donnée par Huge et de foo.efi permet d’amadouer le garde du niveau 2.
Stage 3
Une fois le garde du niveau 2 passé, nous pouvons entrer dans le sous-sol de l’auberge.
Ce sous-sol nous permet de discuter avec quatre personnes nous fournissant des challenges, un PNJ "docteur ingénieur" qui sait faire des boîtes de dialogues javascript contenant XSS (PH3AR!!!) et un autre qui s’est ruiné dans l’achat de disques durs de grandes tailles pour stocker le fichier Huge vu à l'étape précédente. Les challenges sont:
- Ring (2 points)
-
Ce challenge contient un unique fichier, ring.exe qui est un binaire windows x86_64 (console).
- USB (1 point)
-
Ce challenge fournit deux fichiers, un fichier compressé ainsi qu’un binaire windows x86_64 (GUI).
- Video (1 point)
-
Ce challenge est accompagné d’un texte, d’une vidéo et d’un binaire windows appelé screensaver. Le texte nous explique que ce screensaver serait à l’origine d’un vol de clé confidentielle chez la compagnie Thabus.
- Strange (2 points)
-
Ce challenge est constitué de deux fichiers. Le premier (a.out) est un fichier ELF pour IA-64, le second (196) ne contient rien de trivialement connu.
L’architecture IA-64 m'étant inconnue, j’opte pour ce challenge. C’est de plus un challenge à 2 points, sa résolution me permettrait donc de passer directement au stage suivant.
IA-64
L’explication de la résolution de ce challenge va suivre le cheminement que j’ai utilisé. Il n’est pas toujours linéaire, contient des hypothèses fausses, mais laissera entrevoir à l’auteur(e) du challenge les pérégrinations qui peuvent mener à la victoire et le temps que l’on peut perdre dans certaines fausses pistes. Je pense que certaines étaient volontaires de la part du concepteur(-trice) , d’autres beaucoup moins, mais ne faisant pas moins perdre de temps. Ceci dit, place à l’analyse.
Les fichiers du challenge sont au nombre de deux:
- a.out
-
un binaire compilé pour IA-64, relativement gros 5Mo, ce qui est peu courant dans les challenges de type crackme. La commande
strings
nous donne un peu d’informations:mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$ strings -10 a.out /lib/ld-linux-ia64.so.2 libm.so.6.1 __deregister_frame_info __register_frame_info __gmon_start__ libc.so.6.1 __libc_start_main __register_frame_info_aux GCC: (GNU) 2.96 20000731 (Debian GNU/Linux IA64 experimental) GCC: (GNU) 2.96 20000731 (Debian GNU/Linux IA64 experimental) (...) GCC: (GNU) 2.96 20000731 (Debian GNU/Linux IA64 experimental) GCC: (GNU) 2.96 20000731 (Debian GNU/Linux IA64 experimental) .note.ABI-tag .gnu.version .gnu.version_r .rela.IA_64.pltoff .IA_64.unwind_info .IA_64.unwind mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$
Sans décompileur ou émulateur, il est difficile d’en apprendre plus sur ce binaire. Nous constatons l’appel à la libc et la libm.
- 196
-
Ce fichier contient des données binaires sans réélles significations. Le nom de ce fichier ne renseigne en rien sur son contenu ou son usage. Un calcul d’entropie montre que les données sont relativement éparpillées de manière constante. On suppose que les données sont compressées ou chiffrées. Une inspection visuelle contredit cette supposition, puisque tous les 8 octets nous observons
0x3F
ou0x3E
, avec une majorité de0x3F
.mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$ hexdump -C 196 | head 00000000 21 ea c8 a3 8d b4 49 3f 54 ed 76 38 e7 80 22 3f |!.....I?T.v8.."?| 00000010 95 6a a1 bf 81 3e 42 3f 68 0a e0 1e 3e dc 43 3f |.j...>B?h...>.C?| 00000020 60 8d 70 33 16 ad 3e 3f 39 15 c0 58 3e 50 36 3f |`.p3..>?9..X>P6?| 00000030 23 85 84 64 3b 34 e5 3e f9 2e d2 bc 63 13 43 3f |#..d;4.>....c.C?| 00000040 33 af 29 a8 c0 08 4d 3f 3f 17 40 cc 0b f3 4b 3f |3.)...M??.@...K?| 00000050 7b a3 22 db 98 4b 50 3f de 53 77 20 53 e5 3a 3f |{."..KP?.Sw S.:?| 00000060 7f a7 65 f5 48 15 33 3f 4c 6c ad b5 13 33 46 3f |..e.H.3?Ll...3F?| 00000070 e7 1d 28 7d 3f 46 0d 3f 8d c7 c3 ef af 0a e3 3e |..(}?F.?.......>| 00000080 4f b6 65 16 42 0a 50 3f 52 34 34 94 bc 32 3e 3f |O.e.B.P?R44..2>?| 00000090 0b 47 1c d1 64 73 2b 3f e5 d6 4c 31 b6 f6 42 3f |.G..ds+?..L1..B?| mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$
Sans autre informations sur le fichier 196
, nous devons retourner à
l’analyse de a.out
, et donc de s’informer sur l’architecture IA-64.
Documentation sur l’architecture IA-64
Selon wikipedia, le CPU IA-64 est une architecture de processeurs Intel. Les processeurs Itanium et Itanium 2 qui intègrent cette architecture ont été conçus par Intel en coopération avec différents constructeurs informatiques (exemple : HP, Bull, etc.), pour l’intégration dans leur offre serveurs. Ces processeurs sont aujourd’hui présents presque uniquement dans des serveurs d’applications et des serveurs de base de données d’entreprise de forte capacité et traitant des volumes importants de données ou avec forte demande de calculs.
Trouver de la documentation digeste sur le CPU IA-64 n’est pas une tâche aisée. Il semblerait que ce CPU soit resté dans un marché de niche au début des années 2000 et n’ait jamais vraiment percé. En googlant, il est possible de trouver quelques documentations (quelque fois contradictoires) sur l’assembleur IA-64, et les liens vers les documents de référence chez Intel ou HP terminent souvent en 404. Le MSDN contient une excellente source de documentation sur l’assembleur de l’IA-64 qui m’a bien aidé, malgré le fait que ce soit un challenge de binaire ELF/Linux. Seul un lien du MSDN est fourni, mais l’ensemble des articles IA-64 du MSDN reste un excellent moyen d’apprendre comment fonctionne ce CPU peu commun. Je fournis quelque liens qui m’ont aidé à mieux comprendre le fonctionnement de ce CPU
Recherche d’un émulateur et debugger
Lorsque l’on effectue le reverse d’un binaire d’un CPU inconnu, deux options s’ouvrent: le reverser statiquement à l’aide d’outils comme IDA ou utiliser un émulateur de CPU pour coupler une analyse statique à une analyse dynamique. Comme seule la version d’IDA permettant de lire les fichier ELF/IA-64 est payante, je suis parti à la recherche d’un émulateur open-source gratuit.
L'émulateur de CPU le plus connu reste qemu. Malheureusement, qemu n'émule pas l’IA-64. Des recherches google mènent à un fork: https://github.com/jmickeyd/qemu-ia64/ Ce fork ne contient qu’un seul commit dont le contenu est éloquent:
De plus amples recherches google mènent vers un émulateur appelé ski. http://ski.sourceforge.net : This project provides ski, an ia64 instruction set simulator. Ski was originally developed by Hewlett-Packard and has been relicensed under the GNU Public License v2. Ski fournit trois programmes ski, bski et xski et tourne sur CPU x86.
ski |
est un debugger en console texte de programme compilés pour IA-64 |
bski |
est un émulateur de CPU IA-64 |
xski |
est un debugger graphique de programme compilés pour IA-64 |
Il ne semble pas possible de compiler/d’installer ski sur une distribution
linux récente (problèmes de headers). Pour résoudre ce problème le plus simple est
de prendre une distribution d'époque (par exemple une slackware 10),
l’installer dans une VM et compiler
puis installer ski dedans. Il est conseillé de configurer le logiciel
avec ./configure --with-x11
pour bénéficier d’un environnement
de debug graphique.
Des mailing-lists
de ski parlent d’un filesystem complet appelé NUE (pour N…? User Environment?)
simulant une distribution linux entière. Malheureusement ces noms
sont mal choisis, car chercher "ski nue"
sur google renvoie vers des skieuses qui ont manifestement très
chaud, mais pas sur un package installable.
L'émulation d’un système complet étant de fait impossible, il faut
se rabattre sur l'émulation d’un binaire uniquement.
Pour démarrer le programme a.out
, il faut alors trouver une libc
IA-64, la désarchiver dans une arborescence, et lancer bski
.
Le site http://rpmfind.net permet de trouver des fichiers rpm
de la libc compilés pour l’architecture IA-64.
Une fois la libc désarchivée dans le dossier ~/ia64/
, nous pouvons
lancer le programme a.out
.
root@slack10:~/ia64/# bski -simroot . -noconsole a.out
fail
root@slack10:~/ia64/#
Le debugger graphique xski
contient 5 fenêtres:
MAIN |
sert à taper les commandes |
REGISTERS |
affiche les registres du CPU IA-64 |
DATA |
affiche des zones de mémoire |
PROGRAM |
affiche le code décompilé en cours d’exécution |
CONSOLE |
entrée/sortie du programme (non montrée sur l’image) |
Le debugger permet (entre autre) d’inspecter des zones mémoire, de modifier des valeurs en mémoire ou des registres, et de poser ou enlever des breakpoints.
Premiers pas dans le binaire
La première question à laquelle un reverser de crackme est tenté de
répondre est: où est la clé qui va se faire valider par le binaire?
Cette réponse n’est pas évidente dans le cas présent. L’utilisation
ou le renommage du fichier 196 n’aide pas, ni l’ouverture d’un
fichier contenant une clé. Après plusieurs essais/erreurs en ligne
de commande à l’aide de bski
quelques points sont constatés:
-
l’exit value change selon les cas, et plus l’exit value augmente plus il semble que l’on progresse dans le programme.
-
le nom de l’argument ne semble avoir aucune importance, c’est à dire que le programme ne semble pas attendre qu’on lui fournisse une clé en argument..
-
deux fichiers doivent exister:
196
sous ce nom dans le même dossier quea.out
et le fichier donné en argument au programmea.out
.
Exemple de tests réalisés. L’option -stats
est ajoutée car bski ajoute
directement l’exit value lors de la fin d’exécution du programme:
root@linux:~/ia64# bski -stats -noconsole -simroot . a.out
fail
program exited with status 1
378879 insts, 0.11 sec, 3353624 i/s, 129793 cycles, 2.92 ipc
root@linux:~/ia64# bski -stats -noconsole -simroot . a.out inexistent_file
fail
program exited with status 4
392885 insts, 0.11 sec, 3694959 i/s, 133867 cycles, 2.93 ipc
root@linux:~/ia64# mv 196 _196
root@linux:~/ia64# bski -stats -noconsole -simroot . a.out inexistent_file
fail
program exited with status 2
389359 insts, 0.11 sec, 3497373 i/s, 132885 cycles, 2.93 ipc
root@linux:~/ia64# mv _196 196
root@linux:~/ia64# touch existent_file
root@linux:~/ia64# bski -stats -noconsole -simroot . a.out existent_file
fail
program exited with status 5
392701 insts, 0.13 sec, 2942572 i/s, 133697 cycles, 2.94 ipc
root@linux:~/ia64#
Pour avancer dans l’analyse, il a été décidé d’utiliser l'émulateur pour
installer le programme objdump
afin d’avoir un listing complet du
programme décompilé.
root@linux:~/ia64# bski -noconsole -simroot . usr/bin/objdump -d a.out > disass_a.out
root@linux:~/ia64# wc -l disass_a.out
984317 disass_a.out
root@linux:~/ia64# ls -l disass_a.out
433 -rw-r--r-- 1 root root 62900536 mai 9 17:03 disass_a.out
root@linux:~/ia64#
Le listing est très volumineux, 900000 lignes, 60 Mo de données. Néanmoins, il reste très pratique pour pouvoir noter et annoter l’analyse en cours.
Le point d’entrée dans le binaire est 0x4000000000000940
. Un binaire
ELF démarre normalement par mettre en place diverses valeurs, puis
appelle __libc_start_main
qui prépare son environnement d’exécution
et enfin appelle la fonction main
. Comme il est fréquent de le voir
dans les challenges informatiques, le binaire a ses symboles strippés
et ne donne donc pas l’adresse de sa fonction main
:
mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$ file a.out
a.out: ELF 64-bit LSB executable, IA-64, version 1 (SYSV), dynamically
linked, interpreter /lib/ld-linux-ia64.so.2, for GNU/Linux 2.4.0, *stripped*
mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$
Débute alors un travail d’analyse à la recherche de cette fonction main
.
Nous savons que le binaire a.out est lié dynamiquement. Le moyen employé
fût de placer des breakpoints
dans toutes les fonctions à résoudre de la plt
après avoir exporté la variable d’environnement LD_BIND_NOW
avec une
valeur de 1.
Le debugger graphique xski fournit le nom de la fonction à résoudre dans
la fenêtre des registres. Les fonctions utilisées de la libc sont alors
résolues:
-
0x40000000000008c0 : _IO_fopen
-
0x4000000000000800 : malloc
-
0x4000000000000840 : _IO_fgets
-
0x4000000000000820 : memcmp
-
0x40000000000008e0 : _IO_sscanf
-
0x40000000000008a0 : printf
En nous basant sur les appels à _IO_fopen
et après quelques breakpoints et
vérification, nous définissons le point d’entrée de main à l’adresse
0x400000000019c700
Avancer step par step à partir de ce point permet de comprendre une bonne partie du challenge:
-
régulièrement le registre r40 est augmenté de 1, et ce registre est utilisé en exit value, ce qui est en accord avec les observations précédentes
-
deux fichiers sont ouverts. Celui donné en argument et le fichier
196
-
à chaque échec, le programme part vers un printf qui affiche
"fail\n"
-
nous constatons une série de vérifications et comparaisons, et vraisemblablement le programme reste assez linéaire: quelques boucles, mais pas de code spaghetti, d’obfuscation ou de machine virtuelle.
Arrivée à ce stade, nous avons une assez bonne prise en main du programme.
Le debugger permet de naviguer dans le binaire et de valider ou non
des hypothèses. La question importante qui se pose désormais est de
savoir où est la clé de validation, et comment elle est validée.
Le second problème concerne la taille gigantesque du binaire, puisqu’il
est hors de question de reverser chacune des lignes de la sortie d'objdump
.
Schéma "vue d’avion" du binaire a.out
Nous proposons une vue d’avion en pseudo code du binaire. Pour des raisons de clarté, l’incrémentation de l’exit value n’est pas indiquée.
si argc != 2 => goto fail
fopen() du fichier "196"
malloc()
fopen() du fichier donné en argument
fgets() de sa première ligne
si la ligne != "P2\n" => goto fail
fgets()
si le premier caractère de la ligne != "#" => goto fail
fgets()
si la ligne ne contient pas 2 int et que leur multiplication diffère de 12800 => goto fail
fgets()
si la ligne ne contient pas un int qui vaut 255 => goto fail
Boucle n°1 lue tant qu'il y a des lignes dans le fichier
fgets()
si la ligne contient autre chose qu'un int qui vaut 0 ou 255 => goto fail
les données sont copiées en mémoire (en suivant un algorithme particulier)
Boucle n°2 effectuée 32 fois
printf ".\n"
fread() de 526880 octets du fichier 196
copie de données du fichier donné en argument
Appel fonction 1, si exit val != 0 ==> goto fail
Appel fonction 2 (contenant 161 sous fonctions!!)
Comparaison de deux variables => goto fail ou continue selon le résultat
Comparaison de deux variables => goto fail ou continue selon le résultat
Comparaison de deux variables => goto fail ou continue selon le résultat
Comparaison de deux variables => goto fail ou continue selon le résultat
printf "pass\n"
Nous savons que la clé de validation fait 32 caractères. Le fait que la dernière boucle fasse 32 itérations est une bonne indication sur la manière dont sera vérifiée la clé testée.
A titre d’exemple, le fichier suivant permet au programme de passer toute les étapes jusqu'à la boucle n°2:
P2
#
12800 1
255
255
root@linux:~/ia64# bski -simroot . a.out numbers
root@linux:~/ia64# echo $?
13
root@linux:~/ia64#
Nous avons également pu observer que le programme utilise beaucoup les
flottants. L’algorithme de copie des lignes du fichier donné en argument
en mémoire transforme les 255 en 0 de type float et les
0 en 1 de type float.
Certaines constantes reviennent également souvent: 20, 640, 32, 20x20.
De manière empirique, nous constatons que 20x640=12800 et 32x400=12800.
Nous observons aussi que le fichier 196
contient un nombre d’octets multiples de 32. Toutes ces informations
glanées en cours d’analyse vont nous servir pour la résolution finale
bien que leur importance ne saute pas aux yeux à cet instant.
A ce stade l’analyse de la boucle n°2 est laissé en suspens tant que nous n’avons pas compris que signifie la première étape.
Première étape de résolution
La meilleure idée à ce point est d’essayer de comprendre quel est ce fichier qui démarre par "P2":
mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$ file input_file
input_file: Netpbm PGM image text, size = 12800 x 1, ASCII text
mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$
Netpbm est un format d’image qui s'écrit avec du texte. La page wikipedia de Netpbm donne toute l’information nécessaire pour comprendre et manipuler ce format.
Beaucoup de choses peuvent s'éclaircir. Le fichier input est donc une image. L’image fait donc 12800 pixels (largeur x hauteur) et ne contient que du noir et blanc (couleurs 0 et 255). Nous supposons que l’image contient du texte, texte qui n’est autre que la clé qui nous permettra de passer le niveau.
La vérification de l’image se fait en 32 passes. Il nous reste donc deux tailles d’images plausibles:
-
400 x 32: Chaque ligne est vérifiée l’une après l’autre
-
640 x 20: L’image est découpée en 32 blocs de 20x20 pixels, chacun représentant un caractère.
L’algorithme de copie des données du fichier en mémoire, ainsi que la manière dont la boucle n°2 est conçue laisse comprendre que l’image fait 640 x 20 caractères. L’algorithme de copie alloue et utilise un tableau de 640x20 caractères, la boucle n°2 prend les caractères un par un par bloc de 20x20 pixels.
Maintenant que le challenge est compris, nous pouvons avancer dans sa résolution. La fonction 1 dans la boucle n°2 teste quelques caractéristiques du tableau 20x20:
-
La première ligne doit être composée de pixels blancs
-
la deuxième ligne et la dernière ligne doivent avoir au moins un pixel noir
-
le pixel noir le plus à gauche du centre doit être à égale distance du pixel noir le plus à droite du centre
La fonction 2 est rapidement observée, mais laissée de côté:
-
la fonction 2 est composée de 161 fonctions
-
les quelques fonctions regardées font entre 5000 et 8000 lignes désassemblées
-
les fonctions additionnent et multiplient des valeurs provenant du fichier 196 et du fichier fourni en argument. Le fichier
196
semble être rempli defloat
. Un parseur de fonction a été écrit afin d’observer le suivi des variables, mais rien de cohérent n’en ressort (fx représente le registre float numéro x):mitsurugi@dojo:~/chall/SSTIC/stage3/strange_2pt$ ./func_4000000000000b30.py f7=f7xf6+f8 ==> f7=pixel[0] x _196_[3] + f8 f8=f8xf6+f7 ==> f8=pixel[1] x _196_[4] + f7 f7=f7xf6+f8 ==> f7=pixel[2] x _196_[5] + f8 f8=f8xf6+f7 ==> f8=pixel[3] x _196_[6] + f7 f7=f7xf6+f8 ==> f7=pixel[4] x _196_[7] + f8 f8=f8xf6+f7 ==> f8=pixel[5] x _196_[8] + f7 (au début cela semble suivre une suite logique, mais) (...) f7=f7xf6+f8 ==> f7=pixel[74] x _196_[77] + f8 f8=f8xf6+f7 ==> f8=pixel[75] x _196_[78] + f7 f7=f7xf6+f8 ==> f7=pixel[76] x _196_[79] + f8 f8=f8xf6+f7 ==> f8=_196_[199] x pixel[128] + f7 f7=f7xf6+f8 ==> f7=pixel[199] x pixel[132] + f8 f8=f8xf6+f7 ==> f8=pixel[201] x _196_[204] + f7 f7=f7xf6+f8 ==> f7=_196_[206] x pixel[204] + f8 f8=f8xf6+f7 ==> f8=pixel[143] x pixel[206] + f7 f7=f7xf6+f8 ==> f7=pixel[82] x _196_[85] + f8 f8=f8xf6+f7 ==> f8=pixel[151] x pixel[211] + f7 f7=f7xf6+f8 ==> f7=_196_[216] x pixel[214] + f8 f8=f8xf6+f7 ==> f8=pixel[216] x _196_[219] + f7 (...) f7=f7xf6+f8 ==> f7=_196_[30] x _196_[31] + f8 f8=f8xf6+f7 ==> f8=_196_[32] x _196_[33] + f7 f7=f7xf6+f8 ==> f7=_196_[34] x _196_[35] + f8 f8=f8xf6+f7 ==> f8=_196_[36] x _196_[37] + f7 f7=f7xf6+f8 ==> f7=_196_[38] x _196_[39] + f8 f8=f8xf6+f7 ==> f8=_196_[40] x _196_[41] + f7 f7=f7xf6+f8 ==> f7=pixel[383] x _196_[42] + f8 f8=f8xf6+f7 ==> f8=_196_[43] x _196_[44] + f7 f7=f7xf6+f8 ==> f7=_196_[45] x _196_[46] + f8 f8=f8xf6+f7 ==> f8=_196_[47] x _196_[48] + f7 f7=f7xf6+f8 ==> f7=_196_[49] x _196_[50] + f8 (...)
La sophistication de ce parseur pourrait être améliorée afin d'être capable d’extraire les données pertinentes puis de la reverser afin d'être capable de vérifier quels sont les input d’entrées qui vont valider les 4 comparaisons finales.
J’ai décidé d’explorer une autre voie, plus pragmatique.
La calligraphie est un art
Les informations récoltées jusqu'à ce point nous permettent de connaitre beaucoup de choses sur ce challenge.
Nous savons que a.out
va valider les caractères les un après les autres
en analysant les pixels d’une image.
Nous savons que cette vérification finale se fait à l’aide de 4
comparaisons.
Une première hypothèse consiste à imaginer une police de caractère assez
simple. Nous dessinons alors une police à la manière des calculatrices à
cristaux liquides, et testons chacun des caractères
à l’aide du programme a.out lui-même. Comme la largeur de la police n’est
pas connue, l’opération est répétée pour différentes valeurs de celle-ci.
Le premier caractère qui passe le test est un "2". Le second caractère est un "3".
L'émulateur étant lent, et chaque caractère à valider demandant beaucoup
d’opérations, nous décidons d’accélérer les choses. Chaque caractère
est validé par un bloc de 526880 octets du fichier 196
. Pour optimiser
la durée de traitement il suffit de couper le fichier 196
en 32 parties
égales, et de donner le bloc comme étant le fichier 196
. Il validera
alors le premier caractère. Pour valider le 3e caractère, nous pouvons donc
faire:
root@linux:~/ia64# split -b $((16860160/32)) 196
root@linux:~/ia64# mv 196 196_ori
root@linux:~/ia64# ln -s xac 196
root@linux:~/ia64# bski -noconsole -simroot . a.out input_file
(...)
Après quelques modifications mineures de la police de caractères fournie
en référence, nous obtenons une clé qui débute par: 2348503a
Un oeil attentif reconnaît immédiatement des caractères ASCII imprimables.
De plus, ces caractères forment la chaine #HP:
. Nous savons que l’image
NetPBM peut contenir une ligne de commentaire débutant par le symbole
dièse, et que HP est un des constructeurs du CPU IA-64. La clé ne
serait-elle donc que le commentaire de l’image NetPBM, commentaire
faisant référence au(x) constructeurs(s) du CPU?
L’hypothèse est en effet très séduisante, mais elle se révèle finalement totalement fausse.
Car en modifiant très légèrement la police de caractères, j’obtiens rapidement quelques centaines de chaines passant la vérification du programme a.out. Certaines permettent de reconstruire une chaine ascii classique, mais sans aucune signification. Il n’est pas possible de savoir si le concepteur du challenge avait volontairement posé ce piège, mais il se révéla extrêmement chronophage à ce stade de résolution du challenge.
Résolution finale
Les qualités de concepteur en typographie ne semblant pas suffisante pour résoudre ce challenge, il est nécessaire de revenir aux basiques et de reprendre l’analyse..
Nous savons que le fichier 196 contient des listes de flottants. Ces flottants ont été analysés et une caractérique intéressante s’en dégage: Le fichier contient soit des flottants très petits, de l’ordre de 10-3 à 10-8, soit des nombres très proches de 1. Un affichage plus précis permet d’observer une grande majorité de nombre proches de 1.
L’expérience acquise au chapitre précédente s’avère déterminante car elle permet de reconnaître le dessin d’une police de caractère. Un court programme python permet d’extraire les 400 premiers flottants des 32 blocs de données. Nous obtenons en résultat:
(les autres blocs répètent les mêmes chiffres). Nous n’avons que des chiffres, aucune lettre. Le haïku fourni lors du download de ce challenge disait:
Note
|
Ce matin rien n’a changé Reflétant le soleil, Les chiffres me regardent |
Cela pourrait en effet être une piste signifiant que la clé ne contient que des chiffres. Hypothèse difficile à réfuter ou non sans autres bribes d’informations.
Un dernier test permet de valider l’ensemble des caractères de la clé:
-
Le fichier 196 est découpé en 32 parties égales
-
La police de caractères donnée plus haut est produite en 10 images NetPbm appelées img_0, img_1, img_2 etc.. de 640x20 pixels
-
Nous lançons a.out afin qu’il valide que le caractère dessinée soit acceptée en regardant le code de retour
-
Nous testons l’ensemble des 10 nombres pour s’assurer qu’il n’y ait pas de doublons.
-
Un one-liner (découpé pour plus de visibilité) est écrit en bash pour achever ce challenge:
root@linux:~/ia64# for bloc in split_196/x* ;\
do ln -sf $bloc 196 ; \
for f in img/* ; \
do bski -noconsole -simroot . a.out $f 2> /dev/null 1> /dev/null; \
RES=$? ; \
[ $RES -gt 14 ] && echo $f; \
done ; \
done
img/img_2
img/img_3
img/img_4
img/img_2
img/img_5
img/img_0
img/img_3
img/img_8
img/img_4
img/img_7
img/img_2
img/img_5
img/img_0
img/img_8
img/img_2
img/img_8
img/img_7
img/img_3
img/img_3
img/img_5
img/img_7
img/img_7
img/img_2
img/img_0
img/img_8
img/img_5
img/img_5
img/img_4
img/img_4
img/img_0
img/img_3
img/img_5
root@linux:~/ia64#
Important
|
En remettant en forme les valeurs nous obtenons la clé de validation. 23425038472508287335772085544035 |
Stage 4
Le passage du dernier garde à l’aide de cette clé permet d’entrer dans la
salle du trône ou le roi semble nous attendre.
Après s'être approché d’icelui, un fichier mystérieux est donné:
final.txt
. Ce fichier ne contient pas d’adresse mail du domaine sstic.org.
Il
faut se résoudre à l'évidence, le challenge SSTIC n’est pas terminé, une
dernière épreuve barre la route.
rotationnel 13
Le début du fichier final.txt
reste compréhensible, mais la fin
est illisible…
Coucou !
Tu as presque réussi le challenge !
I01p1 y'4qe3553 z41y : 8Y6d5j9Vy88HUGHfGSKsJvqA@ffgvp.bet
Le courageux lecteur qui serait parvenu jusqu’a ce point du write-up devrait être capable de savoir comment trouver l’adresse mail qui permet de résoudre le challenge SSTIC 2016 (un indice habilement dissimulé en titre de paragraphe devrait l’aider).
Conclusion
Ce challenge SSTIC 2016 a été très intéressant à résoudre. Il m’a permis de me plonger dans les arcanes de construction des binaires ELF, comment fonctionne la GOT/PLT, comment est transposé en mémoire un fichier du disque. La partie EFI fût vraiment une nouveauté pour moi, n’ayant jamais eu à manipuler ce firmware autrement que pour démarrer un PC, j’ignorais même qu’on puisse s’en servir pour exécuter des programmes.
Dans la partie regret, je souhaite préciser que j’ai testé le konami code dans à peu près tous les lieux traversés par le héros du challenge. Aucun easter egg n’est apparu.
Annexes
Les codes sources personnels livrés ici ne sont pas à voir comme des codes éprouvés et respectueux des standards, il s’agit uniquement de bout de codes permettant de valider/invalider une hypothèse rapidement et doivent être vus comme tels.
Stage 1 - SOS fant0me - Gh0st retranscription
TOKEN: LOGIN: SSTIC-PC1: Windows 7 No service pack - Build: 7600 - Clock: 2500 Mhz - IP: 10.100.96.21 Webcam: yes
COMMAND: ACTIVED
COMMAND: SYSTEM
TOKEN: Shutting Down Modules ...
Shutting Down gh0st_decode
Module Shutdown Complete ...
ChopShop Complete
em32\csrss.exe
352 SearchIndexer.exe C:\Windows\system32\SearchIndexer.exe
392 winit.exe winit.exe
404 winlogon.exe winlogon.exe
492 services.exe C:\Windows\system32\services.exe
500 lsass.exe C:\Windows\system32\lsass.exe
508 lsm.exe C:\Windows\system32\lsm.exe
608 svchost.exe C:\Windows\system32\svchost.exe -k DcomLaunch
724 svchost.exe C:\Windows\system32\svchost.exe -k RPCSS
816 svchost.exe C:\Windows\system32\svchost.exe -k LocalServiceNetworkRestricted
860 svchost.exe C:\Windows\system32\svchost.exe -k LocalSystemNetworkRestricted
884 svchost.exe C:\Windows\system32\svchost.exe -k netsvcs
1048 svchost.exe C:\Windows\system32\svchost.exe -k LocalService
1152 svchost.exe C:\Windows\system32\svchost.exe -k NetworkService
1296 spoolsv.exe spoolsv.exe
1536 dwm.exe C:\Windows\system32\Dwm.exe
1552 explorer.exe C:\Windows\Explorer.EXE
1641 conhost.exe C:\Windows\system32\conhost.exe
1664 brasseur.exe C:\tmp\brasseur.exe
1702 vlc.exe C:\Program Files\vlc\vlc.exe
1778 iexplore.exe iexplore.exe
1785 thunderbird.exe C:\Program Files\Mozilla\Thunderbird\thunderbird.exe
1852 notepad.exe notepad.exe
COMMAND: KEYBOARD
TOKEN: KEYBOARD START (OFFLINE)
COMMAND: NEXT
TOKEN: KEYBOARD DATA
[2016/02/27 - 23:14] New message: [SSTIC 2016/Challenge] Stage 1
TOKEN: KEYBOARD DATA
Sal
TOKEN: KEYBOARD DATA
ut ! C
TOKEN: KEYBOARD DATA
omme p
TOKEN: KEYBOARD DATA
is voic
TOKEN: KEYBOARD DATA
i la
TOKEN: KEYBOARD DATA
clef p
TOKEN: KEYBOARD DATA
ou
TOKEN: KEYBOARD DATA
r l
TOKEN: KEYBOARD DATA
e sta
TOKEN: KEYBOARD DATA
ge 1
TOKEN: KEYBOARD DATA
! L
TOKEN: KEYBOARD DATA
e mo
TOKEN: KEYBOARD DATA
t de p
TOKEN: KEYBOARD DATA
ass
TOKEN: KEYBOARD DATA
e de
TOKEN: KEYBOARD DATA
l'a
TOKEN: KEYBOARD DATA
rch
TOKEN: KEYBOARD DATA
ive res
TOKEN: KEYBOARD DATA
te ce
TOKEN: KEYBOARD DATA
ui conv
TOKEN: KEYBOARD DATA
enu en
TOKEN: KEYBOARD DATA
sem
TOKEN: KEYBOARD DATA
ble.
TOKEN: KEYBOARD DATA
[2016/02/27 - 23:15] sstic2016-stage1-solution.zip - Saisir mot de passe
TOKEN: KEYBOARD DATA
C
TOKEN: KEYBOARD DATA
y
TOKEN: KEYBOARD DATA
b
TOKEN: KEYBOARD DATA
3
TOKEN: KEYBOARD DATA
r
TOKEN: KEYBOARD DATA
S
TOKEN: KEYBOARD DATA
S
TOKEN: KEYBOARD DATA
T
TOKEN: KEYBOARD DATA
I
TOKEN: KEYBOARD DATA
C
TOKEN: KEYBOARD DATA
_
TOKEN: KEYBOARD DATA
2
TOKEN: KEYBOARD DATA
0
TOKEN: KEYBOARD DATA
1
TOKEN: KEYBOARD DATA
6
COMMAND: LIST DRIVE
TOKEN: DRIVE LIST
DRIVE TOTAL FREE FILESYSTEM DESCRIPTION
C 10228 8171 NTFS Local Disk
D 0 0 CD Drive
COMMAND: LIST FILES (C:\)
TOKEN: FILE LIST
TYPE NAME SIZE WRITE TIME
FILE autoexec.bat 0 1438353600
FILE config.sys 0 1438353600
DIR PerfLogs 0 1438353600
DIR Program Files 0 1438353600
DIR Users 0 1438353600
DIR Windows 0 1438353600
COMMAND: LIST FILES (C:\Users\)
TOKEN: FILE LIST
TYPE NAME SIZE WRITE TIME
DIR Public 0 1438353600
DIR sstic 0 1438353600
COMMAND: LIST FILES (C:\Users\sstic\)
TOKEN: FILE LIST
TYPE NAME SIZE WRITE TIME
FILE Contacts 0 1438353600
FILE Desktop 0 1438353600
FILE Documents 0 1438353600
FILE Downloads 0 1438353600
FILE Favorites 0 1438353600
FILE Links 0 1438353600
FILE Music 0 1438353600
DIR Pictures 0 1438353600
DIR Saved Games 0 1438353600
DIR Searches 0 1438353600
DIR Videos 0 1438353600
COMMAND: LIST FILES (C:\Users\sstic\Documents\)
TOKEN: FILE LIST
TYPE NAME SIZE WRITE TIME
FILE Challenge SSTIC 2016 0 1438353600
COMMAND: LIST FILES (C:\Users\sstic\Documents\Challenge SSTIC 2016\)
TOKEN: FILE LIST
TYPE NAME SIZE WRITE TIME
FILE how_to_rule_the_world.txt 2759 1451606340
DIR Stage 1 0 1453797600
DIR visio_stage2.mp4 1374020 1454960220
COMMAND: DOWN FILES (C:\Users\sstic\Documents\Challenge SSTIC 2016\how_to_rule_the_world.txt)
TOKEN: FILE SIZE (C:\Users\sstic\Documents\Challenge SSTIC 2016\how_to_rule_the_world.txt: 2759)
COMMAND: CONTINUE
COMMAND: FILE DATA (2759)
COMMAND: CONTINUE
COMMAND: DOWN FILES (C:\Users\sstic\Documents\Challenge SSTIC 2016\visio_stage2.mp4)
TOKEN: FILE SIZE (C:\Users\sstic\Documents\Challenge SSTIC 2016\visio_stage2.mp4: 193968)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (8183)
COMMAND: CONTINUE
COMMAND: FILE DATA (5759)
COMMAND: CONTINUE
COMMAND: LIST FILES (C:\Users\sstic\Documents\Challenge SSTIC 2016\Stage 1\)
TOKEN: FILE LIST
TYPE NAME SIZE WRITE TIME
DIR sstic2016-stage1-solution.zip 207 1456614900
COMMAND: DOWN FILES (C:\Users\sstic\Documents\Challenge SSTIC 2016\Stage 1\sstic2016-stage1-solution.zip)
TOKEN: FILE SIZE (C:\Users\sstic\Documents\Challenge SSTIC 2016\Stage 1\sstic2016-stage1-solution.zip: 234)
COMMAND: CONTINUE
COMMAND: FILE DATA (234)
COMMAND: CONTINUE
COMMAND: LIST DRIVE
Stage 1 - SOS fant0me - git diff chopshop
mitsurugi@dojo:~/chall/SSTIC/stage1/SOS-Fant0me/chopshop/modules$ git diff
diff --git a/modules/gh0st_decode.py b/modules/gh0st_decode.py
index 2dd6a35..2640189 100644
--- a/modules/gh0st_decode.py
+++ b/modules/gh0st_decode.py
@@ -45,7 +45,7 @@ moduleName = "gh0st_decode"
def init(module_data):
parser = OptionParser()
parser.add_option("-s", "--savefiles", action="store_true",
- dest="savefiles", default=False, help="save carved files")
+ dest="savefiles", default=True, help="save carved files")
parser.add_option("-w", "--wsize", action="store", dest="wsize",
default=20, help="window size")
parser.add_option("-v", "--verbose", action="store_true",
mitsurugi@dojo:~/chall/SSTIC/stage1/SOS-Fant0me/chopshop/modules$
Stage 1 - TI-83+ - code source
ClrHome
Goto 1
Label 0
If S=5:Goto 5
If S=6:Goto 6
If S=8:Goto 8
If S=9:Goto 9
If S=10:Goto 10
If S=11:Goto 11
If S=13:Goto 13
If S=14:Goto 14
If S=16:Goto 16
If S=17:Goto 17
If S=18:Goto 18
If S=19:Goto 19
If S=21:Goto 21
If S=23:Goto 23
Disp "DISPATCH ERROR"
Stop
Label 3
sub(STR1,length(STR1)-((A+1)*8)+1,8)->STR1
Goto 0
Label 2
" "->STR1
Repeat not(A
A/2->A
sub("01",1+not(not(fPart(Ans))),1)+STR1->STR1
iPart(A->A
End
sub(STR1,1,length(STR1)-1)->STR1
While length(STR1)<32
"0"+STR1->STR1
End
Goto 0
Label 4
" "->STR3
For(I,1,length(STR1)
If "1"=sub(STR1,length(STR1)-I+1,1)~"1"=sub(STR2,length(STR1)-I+1,1):Then
"1"+STR3->STR3
Else
"0"+STR3->STR3
End
End
sub(STR3,1,length(STR3)-1)->STR3
Goto 0
Label 7
0->A
For(I,1,length(STR1)
If "1"=sub(STR1,length(STR1)-I+1,1):Then
A+2^(I-1->A
End
End
Goto 0
Label 15
"00000000"+sub(STR1,1,24)->STR1
Goto 0
Label 22
" "->STR1
0->C
While A
iPart(A/16)->C
fPart(A/16)*16->B
sub("0123456789ABCDEF",B+1,1)+STR1->STR1
C->A
End
sub(STR1,1,length(STR1)-1)->STR1
If length(STR1)<2
"0"+STR1->STR1
Goto 0
Label 1
Input "Enpv(npv(npv(npv(npv( npv(npv( npv(npv(npv(npv( : ",Z
4294967295->C
0->N
{0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918000,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117}->L1
Z->A
5->S
Goto 2
Label 5
STR1->STR5
C->A
11->S
Goto 2
Label 11
STR1->STR6
Goto 12
Label 12
STR6->STR1
0->A
13->S
Goto 3
Label 13
STR1->STR7
STR5->STR1
N->A
14->S
Goto 3
Label 14
STR1->STR1
STR7->STR2
6->S
Goto 4
Label 6
STR3->STR1
8->S
Goto 7
Label 8
L1(A+1)->A
9->S
Goto 2
Label 9
STR1->STR8
STR6->STR1
10->S
Goto 15
Label 10
STR8->STR2
16->S
Goto 4
Label 16
STR3->STR1
11->S
N+1->N
If N=4
Goto 17
Goto 0
Label 17
"11111111111111111111111111111111"->STR2
18->S
Goto 4
Label 18
STR3->STR1
19->S
Goto 7
Label 19
ClrHome
If A=3298472535:Then
Z->rand
21->S
1->X
4->Y
Output(3,7,"KEY:")
Goto 21
Else
Output(3,6,"PERDU")
End
Goto X
Label 21
iPart(rand*256)->A
23->S
Goto 22
Label 23
If X=17:Then
If Y=5:Then
Goto X
End
1->X
5->Y
End
Output(Y,X,STR1)
X+2->X
Goto 21
Label X
Stop
Stage 1 - TI-83+ - break-ti83+.py
#! /usr/bin/python
import sys
#exporting from .tib file gave me strings...
L1=['0','1996959894','3993919788','2567524794','124634137','1886057615','3915621685','2657392035','249268274','2044508324','3772115230','2547177864','162941995','2125561021','3887607047','2428444049','498536548','1789927666','4089016648','2227061214','450548861','1843258603','4107580753','2211677639','325883990','1684777152','4251122042','2321926636','335633487','1661365465','4195302755','2366115317','997073096','1281953886','3579855332','2724688242','1006888145','1258607687','3524101629','2768942443','901097722','1119000684','3686517206','2898065728','853044451','1172266101','3705015759','2882616665','651767980','1373503546','3369554304','3218104598','565507253','1454621731','3485111705','3099436303','671266974','1594198024','3322730930','2970347812','795835527','1483230225','3244367275','3060149565','1994146192','31158534','2563907772','4023717930','1907459465','112637215','2680153253','3904427059','2013776290','251722036','2517215374','3775830040','2137656763','141376813','2439277719','3865271297','1802195444','476864866','2238001368','4066508878','1812370925','453092731','2181625025','4111451223','1706088902','314042704','2344532202','4240017532','1658658271','366619977','2362670323','4224994405','1303535960','984961486','2747007092','3569037538','1256170817','1037604311','2765210733','3554079995','1131014506','879679996','2909243462','3663771856','1141124467','855842277','2852801631','3708648649','1342533948','654459306','3188396048','3373015174','1466479909','544179635','3110523913','3462522015','1591671054','702138776','2966460450','3352799412','1504918807','783551873','3082640443','3233442989','3988292384','2596254646','62317068','1957810842','3939845945','2647816111','81470997','1943803523','3814918930','2489596804','225274430','2053790376','3826175755','2466906013','167816743','2097651377','4027552580','2265490386','503444072','1762050814','4150417245','2154129355','426522225','1852507879','4275313526','2312317920','282753626','1742555852','4189708143','2394877945','397917763','1622183637','3604390888','2714866558','953729732','1340076626','3518719985','2797360999','1068828381','1219638859','3624741850','2936675148','906185462','1090812512','3747672003','2825379669','829329135','1181335161','3412177804','3160834842','628085408','1382605366','3423369109','3138078467','570562233','1426400815','3317316542','2998733608','733239954','1555261956','3268935591','3050360625','752459403','1541320221','2607071920','3965973030','1969922972','40735498','2617837225','3943577151','1913087877','83908371','2512341634','3803740692','2075208622','213261112','2463272603','3855990285','2094854071','198958881','2262029012','4057260610','1759359992','534414190','2176718541','4139329115','1873836001','414664567','2282248934','4279200368','1711684554','285281116','2405801727','4167216745','1634467795','376229701','2685067896','3608007406','1308918612','956543938','2808555105','3495958263','1231636301','1047427035','2932959818','3654703836','1088359270','936918000','2847714899','3736837829','1202900863','817233897','3183342108','3401237130','1404277552','615818150','3134207493','3453421203','1423857449','601450431','3009837614','3294710456','1567103746','711928724','3020668471','3272380065','1510334235','755167117']
def pb(data):
b=bin(data)[2:].zfill(32)
return b[0:8]+'-'+b[8:16]+'-'+b[16:24]+'-'+b[24:32]
clef=int(sys.argv[1])
print "Key %s" % pb(int(clef))
Seed=2**32-1
pb(Seed)
for i in range(4):
print "\ni=%s" % i
#We slide and keep only 8 bit of the key
key_part=(clef >> (i*8))&0xFF
print "key_part %s" % (bin(key_part)[2:].zfill(8))
print " Seed: %s" % pb(Seed)
indice=key_part^(Seed&0xFF)
print "indice %s (key_part XOR Seed&0xFF)" % indice
Seed=Seed>>8
print ">>8 Seed: %s" % pb(Seed)
print "L1[%s] = %s" % (indice,pb(int(L1[indice])))
Seed=Seed^int(L1[indice])
print "New Seed: %s" % pb(Seed)
print Seed^(2**32-1)
Stage 2 - La caverne (secrète?)
Nous constatons à ce sujet que des arbres poussent sous la terre dans la caverne et qu’il semble y neiger régulièrement.
Stage 2 - Huge - seek.py
#! /usr/bin/python
import sys
datas=[1627791495168, 1627791519744, 11168083808256, 11168083820544, 25297854992384, 25297855041536, 27126397538304, 33981332525056, 33981332570112, 47446612774912, 47446612832256, 55346731216896, 64713413758976, 64713413775360, 65249501974528, 88943149977600, 88943150039040, 91853110185984, 91853110214656, 99931956908032, 99931956936704, 128019591467008, 128019591507968, 128574140710912]
def get_data(seek):
f=open("mnt/Huge","rb")
f.seek(seek)
data=f.read(8196)
f.close()
return data
def print_data(data):
for i in range(32):
print hex(ord(data[i])),
if (i+1)%16==0:
print '\n',
def slide(datas,s):
data_to_print=0
for i in range(len(datas)):
if data_to_print==0 and ord(datas[i])!=0x00:
print 'data!!!!'
print 's+i = ' + str(s+i)
data_to_print=1
if data_to_print==1 and ord(datas[i])==0x00:
data_to_print=0
for s in datas:
data=get_data(s)
print "########## Value "+str(s)
print_data(data)
slide(data,s)
Stage 2 - Huge - slide.py
#! /usr/bin/python
import sys
#How to get this sparse_map where there is code:
#$ ./seek.py | grep 's+i' | cut -d '=' -f2 | tr '\n' ','
sparse_map=[1627791495168, 1627791495172, 1627791495178, 1627791520428, 1627791520464, 1627791520503, 1627791520508, 1627791520515, 1627791520532, 11168083808256, 11168083808260, 11168083808266, 11168083823566, 11168083823573, 11168083823593, 11168083823597, 11168083823603, 25297854992384, 25297854992388, 25297854992394, 25297855043410, 27126397538304, 27126397538308, 27126397538314, 27126397541102, 27126397541113, 27126397541123, 27126397541129, 33981332525056, 33981332525060, 33981332525066, 33981332573720, 33981332573730, 33981332573739, 33981332573746, 33981332574264, 33981332574271, 33981332574277, 33981332574287, 47446612774912, 47446612774916, 47446612774922, 47446612835808, 47446612835820, 47446612835826, 47446612835836, 55346731218681, 55346731218685, 55346731218691, 55346731218697, 55346731218700, 55346731218704, 55346731218732, 55346731218737, 55346731218750, 55346731218753, 55346731218768, 55346731218774, 55346731218781, 55346731218787, 64713413758976, 64713413758980, 64713413758986, 64713413776676, 64713413776686, 64713413776693, 64713413776705, 64713413776711, 65249501977003, 65249501977007, 65249501977013, 88943149977600, 88943149977604, 88943149977610, 88943150039920, 88943150039935, 88943150039939, 88943150039945, 91853110185984, 91853110185988, 91853110185994, 91853110216102, 91853110216116, 91853110216127, 91853110216133, 91853110216157, 91853110216162, 91853110216179, 91853110216196, 91853110216203, 91853110216224, 99931956908032, 99931956908036, 99931956908042, 99931956938056, 99931956938061, 128019591467008, 128019591467012, 128019591467018, 128019591511588, 128019591511602, 128019591511607, 128019591511640, 128019591511645, 128019591511651, 128019591511661]
if len(sys.argv) < 2:
print "Give address!"
exit (1)
address=int(sys.argv[1],16)
if address >= 0x20000 and address < 0x2b0000000000:
print "zone 1"
file_address=address-0x20000+0x49effffe1000
i=0
while sparse_map[i]<file_address:
i+=1
next_address=sparse_map[i]
if next_address>0x74effffc1000:
print "We are outside of our zone"
print "Problem ahead, exiting"
exit(1)
#convert back to binary
slide=next_address-0x49effffe1000+0x20000
if address >= 0x00002b0000000000 and address < 0x000049f000000000:
print "zone 2"
file_address=address-0x00002b0000000000+0x0000000000001000
i=0
while sparse_map[i]<file_address:
i+=1
next_address=sparse_map[i]
if next_address>0x00001ef000001000:
print "We are outside of our zone"
print "Problem ahead, exiting"
exit(1)
#convert back to binary
slide=next_address-0x0000000000001000+0x00002b0000000000
if address >= 0x000049f000000000 and address < 0x000074effffc1000:
print "zone 3"
file_address=address-0x000049f000000000+0x00002afffffe1000
i=0
while sparse_map[i]<file_address:
i+=1
next_address=sparse_map[i]
if next_address>0x0000410ffffe1000:
print "We are outside of our zone"
print "Problem ahead, exiting"
exit(1)
#convert back to binary
slide=next_address-0x00002afffffe1000+0x000049f000000000
print 'Next data block can be found at: ' + hex(slide)
Stage 2 - Huge - break_and_slide
#b * 0x000051466a42e705
b * 0x000010f338cf000c
commands
echo " --Checking password scheme\n"
set $rip=0x10f338cf7548
c
end
b * 0x000043abdb4a000c
commands
echo Going to check pass
set $rip=0x43abdb4a0aee
c
end
b * 0x000043abdb4a0aee
commands
echo " --checking password[0]==0x26\n"
c
end
b * 0x4a170682000c
commands
set $rip=0x4a170682ede0
echo " --checking that pass[2:3]==0x7ed1\n"
c
end
b * 0x49e7e541be18
commands
echo " --checking that pass[1]=0x89\n"
c
end
b * 0x352845ab000c
commands
set $rip=0x352845ab3bce
echo " --check that last 4 chars of pass==89341bec\n"
c
end
b * 0x000059CB440C000C
commands
set $rip=0x59cb440c4524
echo " --check passwords[0x8:0xc]==d54ec08c\n"
c
end
b * 0x2a7ee24a000c
commands
set $rip=0x2a7ee24aae24
echo " --check pass[4:8]==d4d68c99\n"
c
end
b * 0x99a3805000c
commands
set $rip=0x99a380575a6
echo " --All done..\n"
c
end