์ปดํŒŒ์ผ๋œ 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 ์ง€์›ํ•˜๊ธฐ

์ปดํŒŒ์ผ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ์—์„œ .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์—์„œ ๋‹ค์Œ์„ ์‹คํ–‰ํ•˜๋ฉด get the .pyc๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

python pyinstxtractor.py executable.exe

.pyc์—์„œ python code๋กœ

.pyc ๋ฐ์ดํ„ฐ(โ€œcompiledโ€ python)์˜ ๊ฒฝ์šฐ original python code๋ฅผ extractํ•˜๋ ค๊ณ  ๋จผ์ € ์‹œ๋„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

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 file์˜ beginning์— ๋‹ค์Œ ๋ฐ”์ดํŠธ๋ฅผ addํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: 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

์˜ค๋ฅ˜: ์ผ๋ฐ˜์ ์ธ ๋””์ปดํŒŒ์ผ ์˜ค๋ฅ˜

Other errors like: class 'AssertionError'>; co_code should be one of the types (<class 'str'>, <class 'bytes'>, <class 'list'>, <class 'tuple'>); is type <class 'NoneType'> may appear.

์ด๋Š” ์•„๋งˆ๋„ magic number๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ์ •ํ™•ํ•œ magic number๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์„ ์‚ฌ์šฉํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š” (๋˜๋Š” ์ƒˆ ๊ฐ’์„ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”).

์ด์ „ ์˜ค๋ฅ˜ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

์ž๋™ ๋„๊ตฌ

The python-exe-unpacker tool serves as a combination of several community-available tools designed to assist researchers in unpacking and decompiling executables written in Python, specifically those created with py2exe and pyinstaller. It includes YARA rules to identify if an executable is Python-based and confirms the creation tool.

ImportError: File name: โ€˜unpacked/malware_3.exe/pycache/archive.cpython-35.pycโ€™ doesnโ€™t exist

์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋Š” unpy2exe๋‚˜ pyinstxtractor๋กœ ์ˆ˜ํ–‰ํ•œ ์–ธํŒฉ ๊ณผ์ •์—์„œ Python ๋ฐ”์ดํŠธ์ฝ”๋“œ ํŒŒ์ผ์ด ๋ถˆ์™„์ „ํ•˜๊ฒŒ ์ƒ์„ฑ๋˜์–ด, ์ดํ›„ Python ๋ฐ”์ดํŠธ์ฝ”๋“œ ๋ฒ„์ „ ๋ฒˆํ˜ธ๊ฐ€ ์—†์–ด uncompyle6์—์„œ ์ธ์‹๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ Python ๋ฐ”์ดํŠธ์ฝ”๋“œ ๋ฒ„์ „ ๋ฒˆํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” prepend ์˜ต์…˜์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ์จ ๋””์ปดํŒŒ์ผ ๊ณผ์ •์ด ์šฉ์ดํ•ด์ง‘๋‹ˆ๋‹ค.

๋ฌธ์ œ ์˜ˆ์‹œ:

# 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 โ€œoriginalโ€ ์ฝ”๋“œ๋ฅผ ์ถ”์ถœํ•˜์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด, ๋Œ€์‹  ์–ด์…ˆ๋ธ”๋ฆฌ๋ฅผ ์ถ”์ถœํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(ํ•˜์ง€๋งŒ ๊ทธ๋‹ค์ง€ ์„ค๋ช…์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค, ๋”ฐ๋ผ์„œ ์›๋ณธ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ถ”์ถœํ•ด ๋ณด์„ธ์š”). ์ด here์—์„œ ๋‚˜๋Š” .pyc ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ disassembleํ•˜๋Š” ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค (์ฝ”๋“œ ํ๋ฆ„์„ ์ดํ•ดํ•˜๊ธฐ๋Š” ์–ด๋ ต์Šต๋‹ˆ๋‹ค). ๋งŒ์•ฝ _.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. ์˜ฌ๋ฐ”๋ฅธ 16-๋ฐ”์ดํŠธ .pyc ํ—ค๋”(์ž„๋ฒ ๋””๋“œ ์ธํ„ฐํ”„๋ฆฌํ„ฐ ๋ฒ„์ „์— ๋งž๋Š” magic + 0์œผ๋กœ ์ฑ„์šด timestamp/size)๋ฅผ ์•ž์— ๋ถ™์—ฌ์•ผ decompiler๊ฐ€ ๋ฐ›์•„๋“ค์ž…๋‹ˆ๋‹ค. Python 3.11.5์˜ ๊ฒฝ์šฐ imp.get_magic().hex()๋กœ magic์„ ์–ป๊ณ  marshal ํŽ˜์ด๋กœ๋“œ ์•ž์— dd/printf๋กœ ํŒจ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Decompile with version-aware tools: pycdc -c -v 3.11.5 vvs.pyc > vvs.py or PyLingual. ์ฝ”๋“œ์˜ ์ผ๋ถ€๋งŒ ํ•„์š”ํ•˜๋ฉด AST๋ฅผ ์ˆœํšŒ(์˜ˆ: ast.NodeVisitor)ํ•˜์—ฌ ํŠน์ • ์ธ์ˆ˜/์ƒ์ˆ˜๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • 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. ๋™์ผํ•œ ํŒจํ„ด์ด ์ž„๋ฒ ๋””๋“œ ELF ๋’ค์—๋„ ๋ฐ˜๋ณต๋ฉ๋‹ˆ๋‹ค.
  • Account for Pyarmor-modified code objects: co_flags has bit 0x20000000 set and an extra length-prefixed field. ํŒŒ์‹ฑ ์ค‘์— CPython์˜ deopt_code()๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ ๋ณตํ˜ธํ™” ์‹คํŒจ๋ฅผ ํ”ผํ•˜์„ธ์š”.
  • Identify encrypted code regions: bytecode๋Š” LOAD_CONST __pyarmor_enter_*__ โ€ฆ LOAD_CONST __pyarmor_exit_*__๋กœ ๊ฐ์‹ธ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ํฌํ•จ๋œ ๋ธ”๋กญ์€ ๋Ÿฐํƒ€์ž„ ํ‚ค(์˜ˆ: 273b1b1373cf25e054a61e2cb8a947b8)๋กœ AES-128-CTR ๋ณตํ˜ธํ™”ํ•ฉ๋‹ˆ๋‹ค. ์˜์—ญ๋ณ„ nonce๋Š” payload-ํŠน์ • 12๋ฐ”์ดํŠธ XOR ํ‚ค(ํšก๋‹จ Pyarmor ๋Ÿฐํƒ€์ž„์—์„œ ์–ป์Œ)๋ฅผ __pyarmor_exit_*__ ๋งˆ์ปค์˜ 12๋ฐ”์ดํŠธ์™€ XORํ•˜์—ฌ ๋„์ถœํ•ฉ๋‹ˆ๋‹ค. ๋ณตํ˜ธํ™” ํ›„์—๋Š” __pyarmor_assert_*__(์•”ํ˜ธํ™”๋œ ๋ฌธ์ž์—ด)์™€ __pyarmor_bcc_*__(์ปดํŒŒ์ผ๋œ dispatch ๋Œ€์ƒ)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Decrypt Pyarmor โ€œmixedโ€ strings: ์ ‘๋‘์‚ฌ๊ฐ€ 0x81์ธ ์ƒ์ˆ˜๋Š” AES-128-CTR๋กœ ์•”ํ˜ธํ™”๋˜์–ด ์žˆ๊ณ (ํ‰๋ฌธ์€ 0x01 ์‚ฌ์šฉ) ๋™์ผํ•œ ํ‚ค์™€ ๋Ÿฐํƒ€์ž„ ์œ ๋„ ๋ฌธ์ž์—ด nonce(์˜ˆ: 692e767673e95c45a1e6876d)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธด ๋ฌธ์ž์—ด ์ƒ์ˆ˜๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.
  • Handle BCC mode: Pyarmor --enable-bcc๋Š” ๋งŽ์€ ํ•จ์ˆ˜๋“ค์„ ๋™๋ฐ˜ ELF๋กœ ์ปดํŒŒ์ผํ•˜๊ณ  Python ์Šคํ…์€ __pyarmor_bcc_*__๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋‚จ๊น๋‹ˆ๋‹ค. bcc_info.py ๊ฐ™์€ ๋„๊ตฌ๋กœ ํ•ด๋‹น ์ƒ์ˆ˜๋“ค์„ ELF ์‹ฌ๋ณผ์— ๋งคํ•‘ํ•œ ๋’ค ๋ณด๊ณ ๋œ ์˜คํ”„์…‹์—์„œ ELF๋ฅผ ๋””์ปดํŒŒ์ผ/๋ถ„์„ํ•˜์„ธ์š”(์˜ˆ: __pyarmor_bcc_58580__ โ†’ bcc_180 at offset 0x4e70).

Python to Executable

์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด, py2exe์™€ PyInstaller์—์„œ ํŽ˜์ด๋กœ๋“œ๋ฅผ ์–ด๋–ป๊ฒŒ ์ปดํŒŒ์ผํ•˜๋Š”์ง€ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

To create a payload using py2exe:

  1. Install the py2exe package from http://www.py2exe.org/
  2. For the payload (in this case, we will name it hello.py), use a script like the one in Figure 1. The option โ€œbundle_filesโ€ with the value of 1 will bundle everything including the Python interpreter into one exe.
  3. Once the script is ready, we will issue the command โ€œpython setup.py py2exeโ€. This will create the executable, just like in 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

PyInstaller๋ฅผ ์‚ฌ์šฉํ•ด payload๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด:

  1. pip์œผ๋กœ PyInstaller๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค (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 ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ