Décompiler des binaires python compilés (python, exe, elf) - Récupérer depuis .pyc

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Du binaire compilé au .pyc

Depuis un binaire compilé ELF vous pouvez obtenir le .pyc avec:

pyi-archive_viewer <binary>
# The list of python modules will be given here:
[(0, 230, 311, 1, 'm', 'struct'),
(230, 1061, 1792, 1, 'm', 'pyimod01_os_path'),
(1291, 4071, 8907, 1, 'm', 'pyimod02_archive'),
(5362, 5609, 13152, 1, 'm', 'pyimod03_importers'),
(10971, 1473, 3468, 1, 'm', 'pyimod04_ctypes'),
(12444, 816, 1372, 1, 's', 'pyiboot01_bootstrap'),
(13260, 696, 1053, 1, 's', 'pyi_rth_pkgutil'),
(13956, 1134, 2075, 1, 's', 'pyi_rth_multiprocessing'),
(15090, 445, 672, 1, 's', 'pyi_rth_inspect'),
(15535, 2514, 4421, 1, 's', 'binary_name'),
...

? X binary_name
to filename? /tmp/binary.pyc

Dans un python exe binary compilé, vous pouvez obtenir le .pyc en exécutant :

python pyinstxtractor.py executable.exe

De .pyc vers python code

Pour les données .pyc (“compilé” python) vous devriez commencer par essayer d’extraire le python code original :

uncompyle6 binary.pyc  > decompiled.py

Assurez-vous que le binary a l’extension.pyc” (si ce n’est pas le cas, uncompyle6 ne fonctionnera pas)

Lors de l’exécution de uncompyle6 vous pourriez rencontrer les erreurs suivantes:

Error: Unknown magic number 227

/kali/.local/bin/uncompyle6 /tmp/binary.pyc
Unknown magic number 227 in /tmp/binary.pyc

Pour corriger cela, vous devez ajouter le bon nombre magique au début du fichier généré.

Les nombres magiques varient selon la version de python, pour obtenir le nombre magique de python 3.8 vous devrez ouvrir un terminal python 3.8 et exécuter:

>> import imp
>> imp.get_magic().hex()
'550d0d0a'

Le nombre magique dans ce cas pour python3.8 est 0x550d0d0a, donc, pour corriger cette erreur vous devrez ajouter au début du fichier .pyc les octets suivants : 0x0d550a0d000000000000000000000000

Une fois que vous aurez ajouté cet en-tête magique, l’erreur devrait être corrigée.

Voici à quoi ressemblera un en-tête magique .pyc python3.8 correctement ajouté :

hexdump 'binary.pyc' | head
0000000 0d55 0a0d 0000 0000 0000 0000 0000 0000
0000010 00e3 0000 0000 0000 0000 0000 0000 0000
0000020 0700 0000 4000 0000 7300 0132 0000 0064
0000030 0164 006c 005a 0064 0164 016c 015a 0064

Erreur : erreurs générales de décompilation

D’autres erreurs telles que : class 'AssertionError'>; co_code should be one of the types (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); is type <class 'NoneType'> peuvent apparaître.

Cela signifie probablement que vous n’avez pas ajouté correctement le magic number ou que vous n’avez pas utilisé le magic number correct, donc assurez-vous d’utiliser le bon (ou essayez-en un nouveau).

Consultez la documentation des erreurs précédente.

Outil automatique

Le python-exe-unpacker tool sert de combinaison de plusieurs outils disponibles dans la communauté conçus pour aider les chercheurs dans l’unpacking et le decompiling d’exécutables écrits en Python, spécifiquement ceux créés avec py2exe et pyinstaller. Il inclut des règles YARA pour identifier si un exécutable est basé sur Python et confirme l’outil de création.

ImportError: File name: ‘unpacked/malware_3.exe/pycache/archive.cpython-35.pyc’ doesn’t exist

Un problème courant rencontré implique un fichier bytecode Python incomplet résultant du unpacking process with unpy2exe or pyinstxtractor, qui ensuite fails to be recognized by uncompyle6 due to a missing Python bytecode version number. Pour y remédier, une option prepend a été ajoutée, qui appends le numéro de version de bytecode Python nécessaire, facilitant le decompiling process.

Exemple du problème :

# Error when attempting to decompile without the prepend option
test@test: uncompyle6 unpacked/malware_3.exe/archive.py
Traceback (most recent call last):
...
ImportError: File name: 'unpacked/malware_3.exe/__pycache__/archive.cpython-35.pyc' doesn't exist
# Successful decompilation after using the prepend option
test@test:python python_exe_unpack.py -p unpacked/malware_3.exe/archive
[*] On Python 2.7
[+] Magic bytes are already appended.

# Successfully decompiled file
[+] Successfully decompiled.

Analyse de l’assembleur Python

Si vous n’avez pas pu extraire le code “original” Python en suivant les étapes précédentes, vous pouvez essayer d’extraire l’assembleur (mais il n’est pas très descriptif, donc essayez d’extraire à nouveau le code original). Dans here j’ai trouvé un code très simple pour désassembler le binaire .pyc (bonne chance pour comprendre le flux du code). Si le .pyc provient de python2, utilisez python2 :

Désassembler un .pyc ```python >>> import dis >>> import marshal >>> import struct >>> import imp >>> >>> with open('hello.pyc', 'r') as f: # Read the binary file ... magic = f.read(4) ... timestamp = f.read(4) ... code = f.read() ... >>> >>> # Unpack the structured content and un-marshal the code >>> magic = struct.unpack('>> timestamp = struct.unpack('>> code = marshal.loads(code) >>> magic, timestamp, code ((62211,), (1425911959,), at 0x7fd54f90d5b0, file "hello.py", line 1>) >>> >>> # Verify if the magic number corresponds with the current python version >>> struct.unpack('>> >>> # Disassemble the code object >>> dis.disassemble(code) 1 0 LOAD_CONST 0 () 3 MAKE_FUNCTION 0 6 STORE_NAME 0 (hello_world) 9 LOAD_CONST 1 (None) 12 RETURN_VALUE >>> >>> # Also disassemble that const being loaded (our function) >>> dis.disassemble(code.co_consts[0]) 2 0 LOAD_CONST 1 ('Hello {0}') 3 LOAD_ATTR 0 (format) 6 LOAD_FAST 0 (name) 9 CALL_FUNCTION 1 12 PRINT_ITEM 13 PRINT_NEWLINE 14 LOAD_CONST 0 (None) 17 RETURN_VALUE ```

PyInstaller raw marshal & Pyarmor v9 static unpack workflow

  • Extract embedded marshal blobs: pyi-archive_viewer sample.exe and export raw objects (e.g., a file named vvs). PyInstaller stores bare marshal streams that start with 0xe3 (TYPE_CODE with FLAG_REF) instead of full .pyc files. Prepend the correct 16-byte .pyc header (magic for the embedded interpreter version + zeroed timestamp/size) so decompilers accept it. For Python 3.11.5 you can grab the magic via imp.get_magic().hex() and patch it with dd/printf before the marshal payload.
  • Decompile with version-aware tools: pycdc -c -v 3.11.5 vvs.pyc > vvs.py or PyLingual. If only partial code is needed, you can walk the AST (e.g., ast.NodeVisitor) to pull specific arguments/constants.
  • Parse the Pyarmor v9 header to recover crypto parameters: signature PY<license> at 0x00, Python major/minor at 0x09/0x0a, protection type 0x09 when BCC is enabled (0x08 otherwise), ELF start/end offsets at 0x1c/0x38, and the 12-byte AES-CTR nonce split across 0x24..0x27 and 0x2c..0x33. The same pattern repeats after the embedded ELF.
  • Account for Pyarmor-modified code objects: co_flags has bit 0x20000000 set and an extra length-prefixed field. Disable CPython deopt_code() during parsing to avoid decryption failures.
  • Identify encrypted code regions: bytecode is wrapped by LOAD_CONST __pyarmor_enter_*__LOAD_CONST __pyarmor_exit_*__. Decrypt the enclosed blob with AES-128-CTR using the runtime key (e.g., 273b1b1373cf25e054a61e2cb8a947b8). Derive the per-region nonce by XORing the payload-specific 12-byte XOR key (from the Pyarmor runtime) with the 12 bytes in the __pyarmor_exit_*__ marker. After decryption, you may also see __pyarmor_assert_*__ (encrypted strings) and __pyarmor_bcc_*__ (compiled dispatch targets).
  • Decrypt Pyarmor “mixed” strings: constants prefixed with 0x81 are AES-128-CTR encrypted (plaintext uses 0x01). Use the same key and the runtime-derived string nonce (e.g., 692e767673e95c45a1e6876d) to recover long string constants.
  • Handle BCC mode: Pyarmor --enable-bcc compiles many functions to a companion ELF and leaves Python stubs that call __pyarmor_bcc_*__. Map those constants to ELF symbols with tooling such as bcc_info.py, then decompile/analyze the ELF at the reported offsets (e.g., __pyarmor_bcc_58580__bcc_180 at offset 0x4e70).

Python to Executable

To start, we’re going to show you how payloads can be compiled in py2exe and PyInstaller.

To create a payload using py2exe:

  1. Install the py2exe package from http://www.py2exe.org/
  2. For the payload (in this case, we will name it hello.py), use a script like the one in Figure 1. The option “bundle_files” with the value of 1 will bundle everything including the Python interpreter into one exe.
  3. Once the script is ready, we will issue the command “python setup.py py2exe”. This will create the executable, just like in Figure 2.
from distutils.core import setup
import py2exe, sys, os

sys.argv.append('py2exe')

setup(
options = {'py2exe': {'bundle_files': 1}},
#windows = [{'script': "hello.py"}],
console = [{'script': "hello.py"}],
zipfile = None,
)
C:\Users\test\Desktop\test>python setup.py py2exe
running py2exe
*** searching for required modules ***
*** parsing results ***
*** finding dlls needed ***
*** create binaries ***
*** byte compile python files ***
*** copy extensions ***
*** copy dlls ***
copying C:\Python27\lib\site-packages\py2exe\run.exe -> C:\Users\test\Desktop\test\dist\hello.exe
Adding python27.dll as resource to C:\Users\test\Desktop\test\dist\hello.exe

Pour créer un payload en utilisant PyInstaller:

  1. Installez PyInstaller en utilisant pip (pip install pyinstaller).
  2. Ensuite, nous exécuterons la commande “pyinstaller –onefile hello.py” (rappel que ‘hello.py’ est notre payload). Cela regroupera tout en un seul exécutable.
C:\Users\test\Desktop\test>pyinstaller --onefile hello.py
108 INFO: PyInstaller: 3.3.1
108 INFO: Python: 2.7.14
108 INFO: Platform: Windows-10-10.0.16299
………………………………
5967 INFO: checking EXE
5967 INFO: Building EXE because out00-EXE.toc is non existent
5982 INFO: Building EXE from out00-EXE.toc
5982 INFO: Appending archive to EXE C:\Users\test\Desktop\test\dist\hello.exe
6325 INFO: Building EXE from out00-EXE.toc completed successfully.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks