Crackme — Python
Le crackme étudié dans cet article provient d’ici.
Cinquième crackme, et cette fois nous allons travailler non pas sur du code machine, mais sur du bytecode Python.
Un fichier Python compilé reste un fichier binaire, donc nous pouvons
commencer par la traditionnelle commande strings
.
Bon rien de bien concluant. Il est maintenant temps de se renseigner sur ce fameux fichier de bytecode, et plus particulièrement sur son format. Après quelques recherches, je suis tombé sur ce fil de la liste de diffusion Python-ideas. La partie intéressante est la suivante :
Currently .pyc files use a very simple format:
- MAGIC number (4 bytes, little-endian)
- last modification time of source file (4 bytes, little-endian)
- code object (marshaled)
Et bien ça c’est une bonne nouvelle :-). Le format de ce fichier est
extrêmement simple. Deux entiers 32-bit et un objet code
sérialisé.
Cela dit, le fait que l’objet code
soit sérialisé via le module
marshal plutôt
que via le module
pickle
implique qu’il va nous falloir utiliser une version de Python relativement
proche de celle qui a généré le bytecode, sinon on risque de ne pas pouvoir
désérialiser l’objet.
Le crackme ayant été publié le 3 juillet 2013, je vais tenter ma
chance avec le Python 3.3 de mon Archlinux.
Essayons donc de lire ce fichier :
Ok, c’est bien du Python 3. À titre de comparaison, voilà ce qui arrive si l’on essaye de lire le fichier avec du Python 2.7.
Ok, on arrive donc bien à désérialiser notre binaire en un
objet code
. Et maintenant, on fait quoi avec cet objet ?
C’est là que l’excellente et très complète bibliothèque standard de Python
intervient. Dans cette bibliothèque on trouve une section
Python Language Services
qui contient le module
dis. Et c’est
exactement ce qu’il nous faut !
Allez, faisons un petit script pour désassembler ce fichier compilé et avoir du bytecode à nous mettre sous la dent.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #!/usr/bin/env python3 import dis import marshal import struct import sys def disassemble(filepath): with open(filepath, 'rb') as pyc: # Read the magic number. _ = struct.unpack('<i', pyc.read(4)) # Read the timestamp _ = struct.unpack('<i', pyc.read(4)) # Read the code. code = marshal.load(pyc) dis.disassemble(code) if __name__ == '__main__': if len(sys.argv) == 2: disassemble(sys.argv[1]) else: print('Error: missing argument', file=sys.stderr) print('Usage: {} file.pyc'.format(sys.argv[0])) |
Et maintenant, appliquons-le à notre crackme.
On constate que le bytecode Python produit par l’interpréteur de référence CPython est :
- simple et lisible
- documenté
- un bytecode pour stack machine
Si l’on ajoute à cela le fait que le script à l’origine de ce bytecode est court, alors il est simple de faire une décompilation à la main, à partir du bytecode, qui nous permet de retrouver le script suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/usr/bin/env python3 if __name__ == '__main__': print('Welcome to the RootMe python crackme') PASS = input('Enter the Flag: ') KEY = 'I know, you love decrypting Byte Code !' I = 5 SOLUCE = [57, 73, 79, 16, 18, 26, 74, 50, 13, 38, 13, 79, 86, 86, 87] KEYOUT = [] for X in PASS: KEYOUT.append(ord(X) + I ^ ord(KEY[I]) % 255) I = I + 1 % len(KEY) if SOLUCE == KEYOUT: print('You Win') else: print('Try Again !') |
Maintenant que l’on connaît le résultat à obtenir et comment la chaîne en entrée est transformée, il nous suffit d’appliquer la transformation inverse (et ici la transformation est simple à inverser) sur le résultat attendu (SOLUCE) afin d’obtenir ce que nous cherchons(PASS). Pour ce faire, j’ai fait un petit script Ruby :
1 2 3 4 5 6 7 | #!/usr/bin/env ruby SOLUCE = [57, 73, 79, 16, 18, 26, 74, 50, 13, 38, 13, 79, 86, 86, 87] KEY = 'I know, you love decrypting Byte Code !'.each_char.map(&:ord).to_a I = 5 psw = SOLUCE.zip(KEY[I..-1]).each_with_index.map { |(x, k), i| (x^k) - (i+I) } puts(psw.map(&:chr).join) |
Et il nous donne :
Si j’avais su ^^
Bon allez, testons cela :
Gagné !
Voilà, ce crackme un peu original est terminé. Il était relativement
simple, mais il aura fallu quelques recherches pour en venir à bout. En tout
cas, ce fut instructif.