Reverse 250 - unVM me

Énoncé :

If I tell you what version of python I used .. where is the fun in that?

On lance le challenge, dans l'enoncé ça parle de python, pas trop surpris de voir : unvm_me.pyc comme fichier.

L'extension est .pyc, c'est du python compilé. Première chose, on essaye de le décompiler pour afficher le code source en clair. Pour ça on peut utiliser cet outil.

Une fois que le .pyc est décompilé on l'ouvre avec notepad et on peut voir le code source en clair :

unvm_me.py
# Embedded file name: unvm_me.py
import md5
md5s = [174282896860968005525213562254350376167L,
 137092044126081477479435678296496849608L,
 126300127609096051658061491018211963916L,
 314989972419727999226545215739316729360L,
 256525866025901597224592941642385934114L,
 115141138810151571209618282728408211053L,
 8705973470942652577929336993839061582L,
 256697681645515528548061291580728800189L,
 39818552652170274340851144295913091599L,
 65313561977812018046200997898904313350L,
 230909080238053318105407334248228870753L,
 196125799557195268866757688147870815374L,
 74874145132345503095307276614727915885L]
print 'Can you turn me back to python ? ...'
flag = raw_input('well as you wish.. what is the flag: ')
if len(flag) > 69:
    print 'nice try'
    exit()
if len(flag) % 5 != 0:
    print 'nice try'
    exit()
for i in range(0, len(flag), 5):
    s = flag[i:i + 5]
    if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 5]:
        print 'nice try'
        exit()
 
print 'Congratz now you have the flag'

A partir de la on essaye de regrouper les informations disponibles pour comprendre comment arriver au flag.

Deux premières conditions sont assez évidentes :

La troisième condition est un peu plus compliquée, on va regarder le code par étape pour comprendre ce qu'il se passe.

Etape 1 :

for i in range(0, len(flag), 5)

Boucle for avec range à trois arguments. On va itérer selon la taille de la chaine de caractères du flag par pas de 5. On comprend mieux pourquoi il fallait un multiple de 5.

Etape 2 :

s = flag[i:i + 5]

Définition d'une variable s La variable s va être égale au 5 caractères en partant de la où se trouve l'itération i (on se rappelle qu'on itère en pas de 5).

Etape 3 :

if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 5]

La dernière condition. On voit qu'ici on va convertir une string de format hexadécimal en int, c'est pour ça qu'on utilise l'argument 16 qui correspond à la base hexa pour la conversion. Cette string qui va être convertie c'est le md5 au format hexadécimal des 5 caractères récupérés dans s juste avant.

Enfin cet int, doit être égal à celui correspondant dans la grosse liste du début avec des nombres de type long.

En résumé :

Notre flag va être découpé en section de 5 caractères et pour chaque section le md5 de ces 5 caractères convertis en int de base 16 doit être égal à celui dans la liste correspondant (md5s).

Chaque long dans la liste md5s correspond donc à 5 caractères du flag.

Exemple pour mieux comprendre :

s = 'abcde'
p = md5.new(s).hexdigest() = 'ab56b4d92b40713acc5af89985d4b786'
int('0x' + p, 16) = 227748192848680293725464448333830731654L

Il faut donc faire la route inverse, on se renseigne sur comment convertir un int en string en python de préférence et on trouve une fonction sympa :

def digit_to_char(digit):
    if digit < 10:
        return str(digit)
    return chr(ord('a') + digit - 10)

def str_base(number,base):
    if number < 0:
        return '-' + str_base(-number, base)
    (d, m) = divmod(number, base)
    if d > 0:
        return str_base(d, base) + digit_to_char(m)
    return digit_to_char(m)

On va donc itérer dans la liste et afficher toutes les strings md5 à partir des longs, ce qui donne :

for n in md5s:
  print str_base(n, 16)

On obtient ça :

831daa3c843ba8b087c895f0ed305ce7
6722f7a07246c6af20662b855846c2c8
5f04850fec81a27ab5fc98befa4eb40c
ecf8dcac7503e63a6a3667c5fb94f610
c0fd15ae2c3931bc1e140523ae934722
569f606fd6da5d612f10cfb95c0bde6d
68cb5a1cf54c078bf0e7e89584c1a4e
c11e2cd82d1f9fbd7e4d6ee9581ff3bd
1df4c637d625313720f45706a48ff20f
3122ef3a001aaecdb8dd9d843c029e06
adb778a0f729293e7e0b19b96a4c5a61
938c747c6a051b3e163eb802a325148e
38543c5e820dd9403b57beff6020596d

On sait qu'un md5 fait forcement 32 caractères, on remarque à la ligne 7 : 68cb5a1cf54c078bf0e7e89584c1a4e qui ne fait que 31 caractères, durant la convertion on a perdu un 0 devant.

068cb5a1cf54c078bf0e7e89584c1a4e correspond au bon md5.

On balance ça sur un site pour casser les md5 et on se retrouve avec : ALEXCTF{dv5d4s2vj8nk43s8d8l6m1n5l67ds9v41n52nv37j481h3d28n4b6v3k}