Descompilar binarios python compilados (exe, elf) - Recuperar desde .pyc
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Desde binario compilado a .pyc
Desde un binario compilado ELF puedes obtener el .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
En un python exe binary compilado puedes obtener el .pyc ejecutando:
python pyinstxtractor.py executable.exe
De .pyc a código Python
Para los datos .pyc (python “compilado”) deberías empezar intentando extraer el original código Python:
uncompyle6 binary.pyc > decompiled.py
Asegúrate de que el binario tenga la extensión “.pyc” (si no, uncompyle6 no va a funcionar)
Mientras ejecutas uncompyle6 puedes encontrar los siguientes errores:
Error: Unknown magic number 227
/kali/.local/bin/uncompyle6 /tmp/binary.pyc
Unknown magic number 227 in /tmp/binary.pyc
Para solucionar esto necesitas añadir el número mágico correcto al inicio del archivo generado.
Los números mágicos varían según la versión de python, para obtener el número mágico de python 3.8 necesitarás abrir una terminal de python 3.8 y ejecutar:
>> import imp
>> imp.get_magic().hex()
'550d0d0a'
El número mágico en este caso para python3.8 es 0x550d0d0a, por lo tanto, para corregir este error necesitarás añadir al inicio del archivo .pyc los siguientes bytes: 0x0d550a0d000000000000000000000000
Una vez que hayas añadido ese encabezado mágico, el error debería solucionarse.
Así es como se verá correctamente añadido el encabezado mágico .pyc python3.8:
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
Error: Decompiling generic errors
Otros errores como: class 'AssertionError'>; co_code should be one of the types (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); is type <class 'NoneType'> pueden aparecer.
Esto probablemente significa que no has añadido correctamente el número mágico o que no has usado el número mágico correcto, así que asegúrate de usar el correcto (o prueba uno nuevo).
Revisa la documentación de errores anterior.
Automatic Tool
The python-exe-unpacker tool sirve como una combinación de varias herramientas disponibles en la comunidad diseñadas para ayudar a los investigadores a desempaquetar y descompilar ejecutables escritos en Python, específicamente aquellos creados con py2exe y pyinstaller. Incluye reglas YARA para identificar si un ejecutable está basado en Python y confirmar la herramienta de creación.
ImportError: File name: ‘unpacked/malware_3.exe/pycache/archive.cpython-35.pyc’ doesn’t exist
Un problema común consiste en un archivo de bytecode de Python incompleto resultante del proceso de unpacking con unpy2exe o pyinstxtractor, que luego no es reconocido por uncompyle6 debido a un número de versión de bytecode de Python faltante. Para solucionarlo, se ha añadido una opción prepend, que antepone el número de versión de bytecode de Python necesario, facilitando el proceso de decompilación.
Ejemplo 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.
Analizando python assembly
Si no pudiste extraer el python “original” code siguiendo los pasos anteriores, entonces puedes intentar extract el assembly (pero no es muy descriptivo, así que try a extraer again el código original). En here encontré un código muy simple para disassemble el binario .pyc (buena suerte entendiendo el flujo del código). Si el .pyc es de python2, usa python2:
Disassemble a .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
```
Flujo de trabajo: marshal crudo de PyInstaller & desempaquetado estático de 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 (número mágico para la versión del intérprete embebido + timestamp/size a cero) para que los decompiladores lo acepten. Para Python 3.11.5 puedes obtener el magic mediante imp.get_magic().hex() y parchearlo con dd/printf antes del payload marshal.
- 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
Para empezar, vamos a mostrar cómo se pueden compilar payloads en py2exe y PyInstaller.
To create a payload using 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 Figura 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 Figura 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
Para crear un payload usando PyInstaller:
- Instala PyInstaller usando pip (pip install pyinstaller).
- Después de eso, ejecutaremos el comando “pyinstaller –onefile hello.py” (un recordatorio de que ‘hello.py’ es nuestro payload). Esto empaquetará todo en un único ejecutable.
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.
Referencias
- https://blog.f-secure.com/how-to-decompile-any-python-binary/
- VVS Discord Stealer Using Pyarmor for Obfuscation and Detection Evasion
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.