Les codes fantastiques : utilisation d’un bytecode Python invalide

Magazine
Marque
GNU/Linux Magazine
Numéro
261
Mois de parution
janvier 2023
Spécialité(s)


Résumé

Continuons cette série sur les codes fantastiques avec un exemple tiré d’une histoire vécue : retrouver les sources d’un plug-in Python obfusqué...


Body

En Python, un fichier avec l’extension .pyc représente un fichier de bytecode, spécifique à une version de Python. Tout code Python passe par cet état (au moins en mémoire) avant d’être interprété. Fournir un fichier dans un tel format au lieu d’un fichier source n’apporte guère de protection en termes d’obfuscation de code : il est plutôt facile de régénérer les sources à partir d’un fichier de bytecode, en utilisant un outil comme uncompyle6, tout juste perd-on les commentaires...

La créativité humaine n’ayant pas de limite, je vous propose cette protection anti-reverse découverte en analysant un plug-in Python qui avait été volontairement obfusqué. Commençons avec un code des plus simple en apparence :

# hello.py
try: print("bye")
except Exception: print("hello")

Son exécution affiche sans surprise bye dans la console. On peut forcer sa compilation en bytecode :

python -m py_compile hello.py

Et inspecter le bytecode associé à l’aide du script suivant :

import dis, marshal
with open('__pycache__/hello.cpython-36.pyc', 'rb') as f:
    f.seek(12) ; dis.dis(marshal.load(f))

Sans rentrer dans les détails du format pyc, disons qu’on saute les 12 premiers octets d’en-tête pour atterrir sur le code Python qu’on charge avec marshal et désassemble avec dis, ce qui nous donne (extrait) :

  1           0 SETUP_EXCEPT            12 (to 14)
 
  2           2 LOAD_NAME                0 (print)
              4 LOAD_CONST               0 ('bye')
              6 CALL_FUNCTION            1
              8 POP_TOP
             10 POP_BLOCK
             12 JUMP_FORWARD            28 (to 42)
 
  3     >>   14 DUP_TOP

Maintenant, remplaçons l’opcode associé à LOAD_CONST par un 0x10, c’est-à-dire changeons l’opcode associé pour un opcode qui n’aurait pas de sens ici, par exemple BINARY_MATRIX_MULTIPLY, que l’on sauve dans hello.pyc :

python -c 'c = open("__pycache__/hello.cpython-36.pyc", "rb").read(); open("hello.pyc", "wb").write(c[:42] + b"\x10\x00" + c[44:])'

Quand on exécute hello.pyc, malgré la séquence invalide, aucune erreur ne ressort, et le programme affiche hello dans la console.

Que s’est-il passé ? BINARY_MATRIX_MULTIPLY attend deux arguments sur la pile, il a pris ce qu’il y trouvait qui n’a pas de sens (dans ce cas, une string et une fonction, pas le top pour un produit matriciel), il lève alors une exception, que l’on attrape. Et uncompyle6 ne parviendra pas à désassembler le bytecode, échouant sur un joli :

Parse error at or near `BINARY_MATRIX_MULTIPLY' instruction at offset 4


Article rédigé par

Par le(s) même(s) auteur(s)

C++ : contrôlez votre espérance de vie

Magazine
Marque
MISC
Numéro
141
Mois de parution
septembre 2025
Spécialité(s)
Résumé

Le langage C est un magnifique outil pédagogique pour enseigner le concept de mémoire, puisqu’il laisse la main au développeur pour la gérer. Mais on le sait, cet attrait n’en est pas un quand on parle de sûreté d’exécution, puisque ce langage est connu pour ne pas aider le développeur pour détecter les usages illégaux liés à la mémoire. Le langage Rust a attaqué le problème en rendant explicite le concept de durée de vie. Le langage Safe C++ tente quant à lui d’introduire ce concept en C++. Il y a plus de vingt ans, splint proposait déjà un concept moins ambitieux pour C. Et maintenant certains fous essaient de le porter vers C++, à travers des extensions de Clang. Quelles sont donc toutes ces approches ?

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Plus de listes de lecture