Decompile compiled python binaries (exe, elf) - Retreive from .pyc

Wsparcie dla HackTricks

Wskazówka dotycząca bug bounty: zarejestruj się w Intigriti, premium platformie bug bounty stworzonej przez hackerów, dla hackerów! Dołącz do nas na https://go.intigriti.com/hacktricks już dziś i zacznij zarabiać nagrody do 100 000 USD!

Z skompilowanego binarnego do .pyc

Z ELF skompilowanego binarnego możesz uzyskać .pyc za pomocą:

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

W skompilowanym python exe binary możesz uzyskać .pyc uruchamiając:

python pyinstxtractor.py executable.exe

From .pyc to python code

Dla danych .pyc ("skompilowany" python) powinieneś zacząć próbować wyodrębnić oryginalny kod python:

uncompyle6 binary.pyc  > decompiled.py

Upewnij się, że plik binarny ma rozszerzenie ".pyc" (jeśli nie, uncompyle6 nie zadziała)

Podczas wykonywania uncompyle6 możesz napotkać następujące błędy:

Błąd: Nieznana liczba magiczna 227

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

Aby to naprawić, musisz dodać odpowiedni magic number na początku wygenerowanego pliku.

Magic number różni się w zależności od wersji pythona, aby uzyskać magic number dla python 3.8, musisz otworzyć terminal python 3.8 i wykonać:

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

Liczba magiczna w tym przypadku dla python3.8 to 0x550d0d0a, następnie, aby naprawić ten błąd, musisz dodać na początku .pyc file następujące bajty: 0x0d550a0d000000000000000000000000

Gdy dodasz ten nagłówek magiczny, błąd powinien być naprawiony.

Tak będzie wyglądał poprawnie dodany .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

Błąd: Dekompilacja błędów ogólnych

Inne błędy takie jak: class 'AssertionError'>; co_code powinien być jednym z typów (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); jest typu <class 'NoneType'> mogą się pojawić.

To prawdopodobnie oznacza, że nie dodałeś poprawnie magicznego numeru lub że nie użyłeś poprawnego magicznego numeru, więc upewnij się, że używasz poprawnego (lub spróbuj nowego).

Sprawdź dokumentację wcześniejszych błędów.

Narzędzie automatyczne

Narzędzie python-exe-unpacker służy jako połączenie kilku dostępnych w społeczności narzędzi zaprojektowanych w celu pomocy badaczom w rozpakowywaniu i dekompilacji plików wykonywalnych napisanych w Pythonie, szczególnie tych stworzonych za pomocą py2exe i pyinstaller. Zawiera zasady YARA do identyfikacji, czy plik wykonywalny jest oparty na Pythonie i potwierdza narzędzie do jego stworzenia.

ImportError: Nazwa pliku: 'unpacked/malware_3.exe/pycache/archive.cpython-35.pyc' nie istnieje

Powszechnym problemem jest niekompletny plik bajtowy Pythona wynikający z procesu rozpakowywania za pomocą unpy2exe lub pyinstxtractor, który następnie nie jest rozpoznawany przez uncompyle6 z powodu brakującego numeru wersji bajtowego Pythona. Aby to naprawić, dodano opcję prepend, która dołącza niezbędny numer wersji bajtowego Pythona, ułatwiając proces dekompilacji.

Przykład problemu:

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

Analiza assemblera Pythona

Jeśli nie udało Ci się wyodrębnić "oryginalnego" kodu Pythona, postępując zgodnie z poprzednimi krokami, możesz spróbować wyodrębnić assembler (ale nie jest to zbyt opisowe, więc spróbuj ponownie wyodrębnić oryginalny kod). W tutaj znalazłem bardzo prosty kod do deasemblacji binarnego pliku .pyc (powodzenia w zrozumieniu przepływu kodu). Jeśli .pyc pochodzi z python2, użyj python2:

>>> 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('<H', magic[:2])
>>> timestamp = struct.unpack('<I', timestamp)
>>> code = marshal.loads(code)
>>> magic, timestamp, code
((62211,), (1425911959,), <code object <module> at 0x7fd54f90d5b0, file "hello.py", line 1>)
>>>
>>> # Verify if the magic number corresponds with the current python version
>>> struct.unpack('<H', imp.get_magic()[:2]) == magic
True
>>>
>>> # Disassemble the code object
>>> dis.disassemble(code)
1           0 LOAD_CONST               0 (<code object hello_world at 0x7f31b7240eb0, file "hello.py", line 1>)
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

Python na Wykonywalny

Aby zacząć, pokażemy, jak ładunki mogą być kompilowane w py2exe i PyInstaller.

Aby stworzyć ładunek za pomocą py2exe:

  1. Zainstaluj pakiet py2exe z http://www.py2exe.org/

  2. Dla ładunku (w tym przypadku nazwiemy go hello.py), użyj skryptu jak w Rysunku 1. Opcja “bundle_files” z wartością 1 połączy wszystko, w tym interpreter Pythona, w jeden plik exe.

  3. Gdy skrypt będzie gotowy, wydamy polecenie “python setup.py py2exe”. To stworzy plik wykonywalny, tak jak na Rysunku 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

Aby stworzyć ładunek za pomocą PyInstaller:

  1. Zainstaluj PyInstaller za pomocą pip (pip install pyinstaller).

  2. Następnie wydamy polecenie “pyinstaller –onefile hello.py” (przypomnienie, że ‘hello.py’ to nasz ładunek). To połączy wszystko w jeden plik wykonywalny.

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.

References

Bug bounty tip: zarejestruj się w Intigriti, premium platformie bug bounty stworzonej przez hackerów, dla hackerów! Dołącz do nas na https://go.intigriti.com/hacktricks już dziś i zacznij zarabiać nagrody do 100 000 USD!

Support HackTricks

Last updated