反编译已编译的 python 二进制 (exe, elf) - 从 .pyc 检索
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
从已编译二进制到 .pyc
对于一个 ELF 编译的二进制,你可以使用以下方法 获取 .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 数据(“已编译”的 python),你应该开始尝试 提取 原始的 python code:
uncompyle6 binary.pyc > decompiled.py
确保 二进制文件具有 扩展名 “.pyc”(如果没有,uncompyle6 将无法工作)
在执行 uncompyle6 时,你可能会遇到 以下错误:
错误:未知魔数 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'
在这种情况下,python3.8 的 magic number 是 0x550d0d0a。要修复此错误,你需要在 .pyc 文件 的 开头 添加 以下字节: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
错误:反编译通用错误
其他错误,例如: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 结合了若干社区可用工具,旨在帮助研究人员解包和反编译用 Python 编写的可执行文件,特别是那些由 py2exe 和 pyinstaller 创建的。它包含 YARA 规则来识别可执行文件是否基于 Python 并确认创建工具。
ImportError: File name: ‘unpacked/malware_3.exe/pycache/archive.cpython-35.pyc’ doesn’t exist
常见问题是,解包过程中使用 unpy2exe 或 pyinstxtractor 导致生成了不完整的 Python 字节码文件,从而 因缺少 Python 字节码版本号而无法被 uncompyle6 识别。为了解决这个问题,已添加了一个 prepend 选项,用于附加所需的 Python 字节码版本号,从而便于反编译过程。
示例:
# 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 汇编
如果你按照前面的步骤无法提取出 python 的“原始”代码,那么你可以尝试提取****汇编(但 it isn’t very descriptive,所以try再次extract原始代码)。In here 我找到了一段非常简单的代码用于disassemble .pyc 二进制(祝你在理解代码流程时好运)。如果 .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(' 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
首先,我们将展示如何使用 py2exe 和 PyInstaller 将 payload 编译成可执行文件。
To create a payload using py2exe:
- 从 http://www.py2exe.org/ 安装 py2exe 包。
- 对于 payload(在本例中命名为 hello.py),使用类似图 1 中的脚本。选项 “bundle_files” 值为 1 会将所有内容(包括 Python 解释器)打包到一个 exe 中。
- 脚本准备好后,执行命令 “python setup.py py2exe”。这将创建可执行文件,就像图 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
使用 PyInstaller 创建 payload:
- 使用 pip 安装 PyInstaller (pip install pyinstaller).
- 之后,运行命令 “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.
参考资料
- https://blog.f-secure.com/how-to-decompile-any-python-binary/
- VVS Discord Stealer Using Pyarmor for Obfuscation and Detection Evasion
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:
HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。