34C3 Junior - NOTHYP1

Du 27 au 30 Décembre se tient le 34C3 à Leipzig (ville natale de Rammstein+ au passage). N’étant pas sur place, j’ai pu néanmoins participer au CTF junior. Il se déroulait de mercredi 21h UTC+1 à aujourd’hui même heure soit 48h de pwn.

Je n’ai pas pu y consacrer tout le temps que j’aurais aimé avec les evénements de fin d’année mais j’ai quand même réussis à pwn 3 challs. Ce CTF était dit débutant mais pour quelques un d’entre nous, il nous apparu assez musclé.

Voici la write up pour le chall de reverse : nohtyp1

0x1. Désobfuscation

 

Voici le contenu du fichier, du python un peu obfusqué

La première étape est donc de le rendre aussi compréhensible que possible. Pour commencer il est préférable de remplacer tout les underscores par leurs valeurs si elles existent ou par un nom plus explicite. On pourra également remplacer les chaines de caractères héxadécimales par leur représentation human readable.

On remplace maintenant toutes les constantes puis on arrange notre code.

not not not 21 and not not 21 => False
not not 21 and not not 21 => True

One est donc un dictionnaire avec deux clés: True et False chacune des deux clés a pour valeur une fonction lambda qui affiche “vrai” ou “faux”. A la ligne n°8 on voit se dessiner une opération logique :

liste_un[::-1] == liste_deux and 'mo4r' in user_data and '34C3_' in user_data and user_data.split('_')[3] == 'tzzzz'

Cela nous donne des indications sur le contenu du flag : Premièrement le résultat de cette opération est True ou False, ainsi en fonction de ce résultat le dictionnaire one nous indique si on a entré le bon flag ou pas

if boolean_return: print(‘Correct!’) else: print(‘Almost!!’) Je rappel que user_data est l’entrée utilisateur, on va alors déterminer plusieurs choses :

  1. Il doit contenir ‘mo4r’ (‘mo4r’ in user_data)
  2. Il doit également contenir ‘34C3’ (‘34C3’ in user_data)
  3. Lorsqu’on split userdata à chaque “” et qu’on récupère le 4ème morceau il doit contenir “tzzzz” ( userdata.split(’’)[3] == ‘tzzzz’)
  4. Grâce à la déduction précédente on en déduit qu’il y a 3 “_” dans le flag :
  • 34C3_
  • _tzzzz
  • ??

Il en reste un à trouver, ça nous sera utile pour plus tard On continu de simplifier notre code: je remplace la liste en intention pour créer une boucle for plus lisible. Je supprime l’appel de la fonction lamda et je le remplace par son contenu : x + (y ^ 21). J’ajoute chaque élément dans une nouvelle liste : liste_1. Le contenu de cette liste est inversé, inversé via le [::-1] et est comparé à une autre liste. Ainsi je reverse l’autre liste (ça revient à la même chose) et la stock dans liste_2

0x2.Analyse

 

On va maintenant analyser :

zip( list(map(ord,user_data)) , list(map(ord,user_data))[::-1] )

Malgré le code légèrment barbare le fonctionnement est très simple, premièrement list(map(ord, user_data)) permet d’obtenir une liste contenant chaque représentation ASCII décimal de l’entrée utilisateur (ex: a = 97).

Zip retourne donc deux listes contenant l’entrée utilisateur à l’exception près que la deuxième est inversé via [::-1] Pour résumer, la valeur contenue dans liste_un est : user_data[x] + (user_data[::-1][x] ^ 21) pour x allant de 0 à 23 (taille du flag) ce qui joue en notre faveur car en connaissant un des deux char on obtient l’autre !

C’est parti : on sait que le flag commence par 34C3_ car c’est le type de flag pour le CTF junior ! Ainsi : 162 = ord(“3”) + (y ^ 21) => 162 - 51 = y ^ 21 => 111 ^ 21 => y = 122, qui est le code ASCII de z (chr(122) en python) On sait maintenant que la dernière lettre du flag est z, puis on répéte l’opération avec 34C_ : 34C3_XXXXXXXXXXXXXXtzzz

Tiens ça me rappel quelque chose ahah, cela implique donc 34C3_XXXXXXXXXXXXX_tzzz, ce nouveau char nous permet de calculer celui-ci 34C3_XXXXXXXXXXXXX_tzzz

183 = x + (ord(”_“) ^ 21) => 183 = x + 74 => chr(183 - 74) = x => “m” 34C3_mXXXXXXXXXXXX_tzzz on se rappel alors que mo4r doit être dans le flag : 34C3_mo4rXXXXXXXX_tzzz

(ça aurait très bien pu ne pas être ça mais on peut le vérifier via des calculs ce que j’ai fait avant d’aller plus loin) o4r nous permettent de calculer 34C3_mo4rXXXXXXkestzzz toujours avec la méthode précédente. On se rappel maintenant qu’il y a unquelque par.

Pourquoi pas ici : 34C3_mo4r_XXXXXkes_tzzz ?

128 = ord(””) + (y ^ 21) => y = 4 126 = ord(“4”) + (x ^ 21) => x = ““ Ca fonctionne ! : 34C3_mo4r_XXXX4kes_tzzz Nous n’avons maintenant plus aucun indice mise à part que les lettres sont en couples et sont liées par une opération mathématique : 4C3_mo4r_ABBA4kes_tzzz

0x3 Scripting

J’ai donc codé un script qui ma permis d’obtenir le couple A en testant les combinaisons possible qui valides l’équation

4C3_mo4r_sBBn4kes_tzzz Petite paranthèse qui m’a valu un bon faceplam : pour générer toutes les combinaisons possibles j’utilise itertools avec la fonction product or avant j’utilisais combinaison qui ne retourne pas TOUTES les combinaisons (ref : https://docs.python.org/2/library/itertools.htm) Bref, pour le dernier couple le script me retournait trop de possibilité :

Une des possiblité était le bruteforce dans le contexte du script mais un hint est sorti quelques minutes plus tôt : cat flag | md5sum : 5a76c600c2ca0f179b643a4fcd4bc7ac. J’ai donc adapté mon code :

FLAG : 34C3_mo4r_schn4kes_tzzzz