Descompilar binários python compilados (exe, elf) - Recuperar de .pyc
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Do binário compilado para .pyc
A partir de um binário compilado ELF você pode obter o .pyc com:
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
Em um python exe binary compilado você pode obter o .pyc executando:
python pyinstxtractor.py executable.exe
De .pyc para python code
Para os dados .pyc (“compilado” python) você deve começar tentando extrair o original python code:
uncompyle6 binary.pyc > decompiled.py
Certifique-se de que o binário tem a extensão “.pyc” (se não, uncompyle6 não vai funcionar)
Ao executar uncompyle6 você pode encontrar os seguintes erros:
Erro: Número mágico desconhecido 227
/kali/.local/bin/uncompyle6 /tmp/binary.pyc
Unknown magic number 227 in /tmp/binary.pyc
Para corrigir isso você precisa adicionar o número mágico correto no início do arquivo gerado.
Números mágicos variam conforme a versão do python, para obter o número mágico do python 3.8 você precisará abrir um terminal do python 3.8 e executar:
>> import imp
>> imp.get_magic().hex()
'550d0d0a'
O número mágico neste caso para python3.8 é 0x550d0d0a, então, para corrigir esse erro você precisará adicionar no início do arquivo .pyc os seguintes bytes: 0x0d550a0d000000000000000000000000
Uma vez que você tenha adicionado esse cabeçalho mágico, o erro deve ser corrigido.
É assim que um cabeçalho mágico .pyc python3.8 corretamente adicionado ficará:
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
Erro: Erros genéricos de decompilação
Outros erros como: class 'AssertionError'>; co_code should be one of the types (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); is type <class 'NoneType'> podem aparecer.
Isso provavelmente significa que você não adicionou corretamente o magic number ou que você não usou o magic number correto, então certifique-se de usar o correto (ou tente um novo).
Verifique a documentação de erros anterior.
Ferramenta Automática
O python-exe-unpacker tool funciona como uma combinação de várias ferramentas disponíveis na comunidade projetadas para ajudar pesquisadores a desempacotar e decompilar executáveis escritos em Python, especificamente aqueles criados com py2exe e pyinstaller. Inclui regras YARA para identificar se um executável é baseado em Python e confirmar a ferramenta de criação.
ImportError: File name: ‘unpacked/malware_3.exe/pycache/archive.cpython-35.pyc’ doesn’t exist
Um problema comum encontrado envolve um arquivo de bytecode Python incompleto resultante do processo de desempacotamento com unpy2exe ou pyinstxtractor, que então não é reconhecido pelo uncompyle6 devido à falta do número da versão do bytecode Python. Para resolver isso, uma opção prepend foi adicionada, que anexa o número de versão necessário do bytecode Python, facilitando o processo de decompilação.
Exemplo do 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.
Analisando python assembly
Se você não conseguiu extrair o código “original” de python seguindo os passos anteriores, então você pode tentar extrair a assembly (mas isso não é muito descritivo, então tente extrair novamente o código original). Em here encontrei um código bem simples para disassemble o binário .pyc (boa sorte entendendo o fluxo do código). Se o .pyc for do python2, use 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
- Extrair blobs de marshal embutidos:
pyi-archive_viewer sample.exe e exporte os objetos brutos (por exemplo, um arquivo chamado vvs). PyInstaller armazena streams de marshal nus que começam com 0xe3 (TYPE_CODE com FLAG_REF) em vez de arquivos .pyc completos. Prepend o header .pyc de 16 bytes correto (magic para a versão do interpretador embutido + timestamp/tamanho zerados) para que decompiladores o aceitem. Para Python 3.11.5 você pode obter o magic via imp.get_magic().hex() e patchar com dd/printf antes do payload marshal.
- Decompilar com ferramentas que respeitem a versão:
pycdc -c -v 3.11.5 vvs.pyc > vvs.py ou PyLingual. Se apenas código parcial for necessário, você pode percorrer o AST (por exemplo, ast.NodeVisitor) para extrair argumentos/constantes específicas.
- Fazer parse do header do Pyarmor v9 para recuperar parâmetros criptográficos: assinatura
PY<license> em 0x00, major/minor do Python em 0x09/0x0a, tipo de proteção 0x09 quando BCC está habilitado (0x08 caso contrário), offsets de início/fim do ELF em 0x1c/0x38, e o nonce AES-CTR de 12 bytes dividido entre 0x24..0x27 e 0x2c..0x33. O mesmo padrão se repete após o ELF embutido.
- Levar em conta objetos de código modificados pelo Pyarmor:
co_flags tem o bit 0x20000000 setado e um campo adicional prefixado com comprimento. Desative deopt_code() do CPython durante o parse para evitar falhas de decriptação.
- Identificar regiões de código criptografadas: o bytecode é encapsulado por
LOAD_CONST __pyarmor_enter_*__ … LOAD_CONST __pyarmor_exit_*__. Descriptografe o blob entre eles com AES-128-CTR usando a chave em runtime (por exemplo, 273b1b1373cf25e054a61e2cb8a947b8). Derive o nonce por região fazendo XOR da chave XOR específica do payload (12 bytes, do runtime do Pyarmor) com os 12 bytes no marcador __pyarmor_exit_*__. Após a decriptação, você também pode ver __pyarmor_assert_*__ (strings criptografadas) e __pyarmor_bcc_*__ (alvos compilados de dispatch).
- Descriptografar strings “mixed” do Pyarmor: constantes prefixadas com
0x81 estão criptografadas com AES-128-CTR (o plaintext usa 0x01). Use a mesma chave e o nonce de string derivado em runtime (por exemplo, 692e767673e95c45a1e6876d) para recuperar constantes de string longas.
- Tratar modo BCC: Pyarmor
--enable-bcc compila muitas funções para um ELF acompanhante e deixa stubs Python que chamam __pyarmor_bcc_*__. Mapeie essas constantes para símbolos do ELF com ferramentas como bcc_info.py, então decompile/análise o ELF nos offsets reportados (por exemplo, __pyarmor_bcc_58580__ → bcc_180 no offset 0x4e70).
Python to Executable
Para começar, vamos mostrar como payloads podem ser compilados em py2exe e PyInstaller.
To create a payload using py2exe:
- Instale o pacote py2exe a partir de http://www.py2exe.org/
- Para o payload (neste caso, vamos chamá-lo de hello.py), use um script como o da Figura 1. A opção “bundle_files” com o valor de 1 empacotará tudo, incluindo o intérprete Python, em um único exe.
- Quando o script estiver pronto, executamos o comando “python setup.py py2exe”. Isso criará o executável, como na 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 criar um payload usando PyInstaller:
- Instale PyInstaller usando pip (pip install pyinstaller).
- Depois disso, iremos emitir o comando “pyinstaller –onefile hello.py” (uma lembrança de que ‘hello.py’ é nosso payload). Isso irá agrupar tudo em um único executável.
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.
Referências
- https://blog.f-secure.com/how-to-decompile-any-python-binary/
- VVS Discord Stealer Usando Pyarmor para Obfuscation and Detection Evasion
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.