Decompile compiled python binaries (exe, elf) - Ανάκτηση από .pyc

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Από Compiled Binary σε .pyc

Από ένα ELF compiled binary μπορείτε να αποκτήσετε το .pyc με:

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

Σε ένα μεταγλωττισμένο python exe binary μπορείτε να εξάγετε το .pyc εκτελώντας:

python pyinstxtractor.py executable.exe

Από .pyc σε python code

Για τα .pyc δεδομένα (“compiled” python) θα πρέπει να ξεκινήσετε προσπαθώντας να εξαγάγετε τον πρωτότυπο python code:

uncompyle6 binary.pyc  > decompiled.py

Βεβαιωθείτε ότι το binary έχει την επέκταση.pyc” (αν όχι, το uncompyle6 δεν θα δουλέψει)

Κατά την εκτέλεση του uncompyle6 μπορεί να συναντήσετε τα παρακάτω σφάλματα:

Error: Unknown magic number 227

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

Για να το διορθώσετε χρειάζεται να προσθέσετε τον σωστό μαγικό αριθμό στην αρχή του παραγόμενου αρχείου.

Οι μαγικοί αριθμοί διαφέρουν ανά έκδοση του python· για να βρείτε τον μαγικό αριθμό της python 3.8 θα χρειαστεί να ανοίξετε ένα τερματικό python 3.8 και να εκτελέσετε:

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

Ο magic number σε αυτή την περίπτωση για python3.8 είναι 0x550d0d0a, επομένως, για να διορθώσετε αυτό το σφάλμα θα χρειαστεί να προσθέσετε στην αρχή του .pyc file τα ακόλουθα bytes: 0x0d550a0d000000000000000000000000

Μόλις έχετε προσθέσει εκείνο το magic header, το σφάλμα θα πρέπει να διορθωθεί.

Έτσι θα μοιάζει μια σωστά προστεθείσα .pyc python3.8 magic header:

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

Σφάλμα: Γενικά σφάλματα decompiling

Άλλα σφάλματα όπως: class 'AssertionError'>; co_code should be one of the types (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); is type <class 'NoneType'> μπορεί να εμφανιστούν.

Αυτό πιθανότατα σημαίνει ότι δεν έχετε προσθέσει σωστά τον magic number ή ότι δεν έχετε χρησιμοποιήσει τον σωστό magic number, οπότε φροντίστε να χρησιμοποιήσετε τον σωστό (ή δοκιμάστε έναν καινούργιο).

Ελέγξτε την προηγούμενη τεκμηρίωση σφαλμάτων.

Αυτόματο εργαλείο

Το python-exe-unpacker tool λειτουργεί ως συνδυασμός πολλών εργαλείων διαθέσιμων στην κοινότητα, σχεδιασμένων να βοηθούν τους ερευνητές στο unpacking και decompiling εκτελέσιμων αρχείων γραμμένων σε Python, ειδικά αυτών που δημιουργήθηκαν με py2exe και pyinstaller. Περιλαμβάνει κανόνες YARA για να εντοπίζει αν ένα εκτελέσιμο βασίζεται σε Python και να επιβεβαιώνει το εργαλείο δημιουργίας.

ImportError: Το αρχείο ‘unpacked/malware_3.exe/pycache/archive.cpython-35.pyc’ δεν υπάρχει

Ένα κοινό πρόβλημα που συναντάται αφορά ένα ατελές Python bytecode αρχείο που προκύπτει από την διαδικασία unpacking με unpy2exe ή pyinstxtractor, το οποίο στη συνέχεια δεν αναγνωρίζεται από uncompyle6 λόγω ελλιπούς αριθμού έκδοσης Python bytecode. Για να αντιμετωπιστεί αυτό, έχει προστεθεί μια επιλογή prepend, που προσθέτει τον απαραίτητο αριθμό έκδοσης Python bytecode, διευκολύνοντας τη διαδικασία decompiling.

Παράδειγμα του προβλήματος:

# 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.

Ανάλυση python assembly

Αν δεν καταφέρατε να εξαγάγετε τον αρχικό κώδικα python ακολουθώντας τα προηγούμενα βήματα, τότε μπορείτε να δοκιμάσετε να εξαγάγετε το assembly (αλλά δεν είναι πολύ περιγραφικό, οπότε προσπαθήστε ξανά να εξαγάγετε τον αρχικό κώδικα). Σε here βρήκα έναν πολύ απλό κώδικα για να disassemble το .pyc binary (καλή τύχη στην κατανόηση της ροής του κώδικα). Αν το .pyc είναι από python2, χρησιμοποιήστε 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('>> 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 σε Εκτελέσιμο

Για αρχή, θα σας δείξουμε πώς μπορούν να μεταγλωττιστούν payloads σε py2exe και PyInstaller.

Για να δημιουργήσετε ένα payload χρησιμοποιώντας py2exe:

  1. Εγκαταστήστε το πακέτο py2exe από http://www.py2exe.org/
  2. Για το payload (σε αυτή την περίπτωση, θα το ονομάσουμε hello.py), χρησιμοποιήστε ένα script όπως αυτό στο Figure 1. Η επιλογή “bundle_files” με τιμή 1 θα πακετάρει τα πάντα συμπεριλαμβανομένου του Python interpreter σε ένα exe.
  3. Μόλις το script είναι έτοιμο, θα εκτελέσουμε την εντολή “python setup.py py2exe”. Αυτό θα δημιουργήσει το εκτελέσιμο, όπως στο 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

Για να δημιουργήσετε ένα payload χρησιμοποιώντας PyInstaller:

  1. Εγκαταστήστε το PyInstaller χρησιμοποιώντας pip (pip install pyinstaller).
  2. Μετά από αυτό, θα εκτελέσουμε την εντολή “pyinstaller –onefile hello.py” (υπενθύμιση ότι ‘hello.py’ είναι το payload μας). Αυτό θα δημιουργήσει ένα ενιαίο εκτελέσιμο που περιλαμβάνει τα πάντα.
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.

Αναφορές

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks