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.

Stage 1 du challenge
Figure 1. En sortant de la maison

…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:

key calc
Figure 2. Victoire
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.

rpg stage2
Figure 3. Un monde enneigé

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.

rpg stage2 auberge
Figure 4. Une auberge accueillante

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.

rpg stage3
Figure 5. Les cyber sous sols 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 ou 0x3E, avec une majorité de 0x3F.

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:

Unsolved issues/bugs in the ia64 backend

General
-------
- Unimplemented:
  - Everything

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:

xski
Figure 6. debugger xski
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 que a.out et le fichier donné en argument au programme a.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 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 de float. 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.

police
Figure 7. Police de test

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:

liste
Figure 8. Extraction de données du fichier 196

(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

rpg stage4
Figure 9. La salle du trône

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?)

rpg stage2 caverne
Figure 10. Entrée de la caverne
rpg stage2 caverne in
Figure 11. Le portable et les lunettes

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