Decompilare binari python compilati (exe, elf) - Recuperare da .pyc
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Dal binario compilato al .pyc
Da un binario compilato ELF puoi ottenere il .pyc con:
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
In un python exe binary compilato puoi ottenere il .pyc eseguendo:
python pyinstxtractor.py executable.exe
Da .pyc a python code
Per i dati .pyc (“compilato” python) dovresti iniziare provando a estrarre il python code originale:
uncompyle6 binary.pyc > decompiled.py
Assicurati che il binario abbia l’estensione “.pyc” (altrimenti uncompyle6 non funzionerà)
Durante l’esecuzione di uncompyle6 potresti incontrare i seguenti errori:
Errore: Numero magico sconosciuto 227
/kali/.local/bin/uncompyle6 /tmp/binary.pyc
Unknown magic number 227 in /tmp/binary.pyc
Per risolvere questo devi aggiungere il numero magico corretto all’inizio del file generato.
I numeri magici variano in base alla versione di python, per ottenere il numero magico di python 3.8 dovrai aprire un terminale python 3.8 ed eseguire:
>> import imp
>> imp.get_magic().hex()
'550d0d0a'
Il numero magico in questo caso per python3.8 è 0x550d0d0a, quindi, per correggere questo errore dovrai aggiungere all’inizio del .pyc file i seguenti byte: 0x0d550a0d000000000000000000000000
Una volta che hai aggiunto quell’intestazione magica, l’errore dovrebbe essere risolto.
Ecco come apparirà la intestazione magica .pyc python3.8 correttamente aggiunta:
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
Errore: Decompilazione - errori generici
Altri errori come: class 'AssertionError'>; co_code should be one of the types (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); is type <class 'NoneType'> possono apparire.
Questo probabilmente significa che non hai aggiunto correttamente il magic number oppure che non hai usato il magic number corretto, quindi assicurati di usare quello giusto (o prova con uno nuovo).
Consulta la documentazione degli errori precedente.
Strumento automatico
The python-exe-unpacker tool funge da combinazione di diversi strumenti disponibili nella community progettati per assistere i ricercatori nell’unpacking e nel decompiling di eseguibili scritti in Python, specificamente quelli creati con py2exe e pyinstaller. Include regole YARA per identificare se un eseguibile è basato su Python e per confermare lo strumento di creazione.
ImportError: File name: ‘unpacked/malware_3.exe/pycache/archive.cpython-35.pyc’ doesn’t exist
Un problema comune riscontrato riguarda un file di bytecode Python incompleto risultante dal unpacking process with unpy2exe or pyinstxtractor, che poi non viene riconosciuto da uncompyle6 a causa di un numero di versione del bytecode Python mancante. Per risolvere questo, è stata aggiunta un’opzione prepend, che aggiunge il numero di versione del bytecode Python necessario, facilitando il processo di decompiling.
Esempio del problema:
# 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.
Analisi dell’assembly di python
Se non sei riuscito a estrarre il codice “originale” di python seguendo i passaggi precedenti, puoi provare a estrarre l’assembly (ma non è molto descrittivo, quindi prova di nuovo a estrarre il codice originale). In here ho trovato un codice molto semplice per disassemblare il binario .pyc (buona fortuna a capire il flusso del codice). Se lo .pyc è di python2, usa python2:
Disassemblare 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(' 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
```
Flusso di lavoro per l’unpack statico di PyInstaller raw marshal & Pyarmor v9
- 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).
Da Python a eseguibile
To start, we’re going to show you how payloads can be compiled in py2exe and PyInstaller.
Per creare un payload usando py2exe:
- Install the py2exe package from http://www.py2exe.org/
- 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.
- 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
Per creare un payload usando PyInstaller:
- Installa PyInstaller usando pip (pip install pyinstaller).
- Successivamente eseguiremo il comando “pyinstaller –onefile hello.py” (ricordando che ‘hello.py’ è il nostro payload). Questo impacchetterà tutto in un unico eseguibile.
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.
Riferimenti
- https://blog.f-secure.com/how-to-decompile-any-python-binary/
- VVS Discord Stealer Using Pyarmor for Obfuscation and Detection Evasion
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
HackTricks