컴파일된 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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 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에서 다음을 실행하면 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(' 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:
- Install the py2exe package from http://www.py2exe.org/
- 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.
- 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를 생성하려면:
- 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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.