d297973f8d1bb330dc7a7d7538bfbe97ea4608aee040b48122da39a2562ddf4cpyinstaller-pyarmor-dropper: d297973f — PyInstaller single-file bootloader with PyArmor-obfuscated Python 3.13 payload
Executive Summary
A 13.8 MB PE64 that is a stock PyInstaller one-file bootloader wrapping a zlib-compressed CFFI archive. The extracted payload is a Python 3.13 runtime environment plus a PyArmor-obfuscated script (pyarmor_runtime_000000), indicating the actual malware logic is hidden behind PyArmor's runtime protection. Static evidence points to network-capable Python modules (socket, urllib, email, base64, hashlib, subprocess, ctypes, wmi) being present in the archive. No CAPE detonation was possible (no Windows guest available), so all TTPs are inferred from the static surface.
What It Is
- SHA-256: d297973f8d1bb330dc7a7d7538bfbe97ea4608aee040b48122da39a2562ddf4c ^[file.txt]
- Filename at source: common.dat ^[metadata.json]
- Type: PE32+ executable (GUI) x86-64, stripped, unsigned ^[file.txt] ^[rabin2-info.txt]
- Size: 13,842,881 bytes (13.2 MB on disk) ^[rabin2-info.txt]
- Linker timestamp: 2026-05-21 09:51:36 UTC ^[pefile.txt]
- Overlay: ~13.6 MB zlib-compressed CFFI archive (PyInstaller single-file mode) ^[binwalk.txt]
- Bootloader language: C, compiled with a toolchain reporting linker version 2.45 (MinGW-w64 ld / binutils) ^[pefile.txt] ^[rabin2-info.txt]
- No exports, no PDB path, no version info resources ^[pefile.txt]
How It Works
Build / RE
The outer PE is a standard PyInstaller bootloader — not custom and not packed with UPX. It contains the expected CRT startup code (__wgetmainargs, _initterm, malloc, memcpy), sets up a console window (CreateWindowExW, MessageBoxW), and then transitions into the PyInstaller extraction routine. The entry0 decompilation shows a classic MSVC/MinGW CRT bootstrap: parse command line, copy argv into a new wide-char buffer, call init tables, and then branch to the main payload initialization. ^[r2:entry0] ^[r2:fcn.140001010]
Key bootloader strings observed:
_MEIPASS— the extraction target directory variable ^[strings.txt:419]base_library.zip— standard Python frozen standard library path ^[strings.txt:370]lib-dynload— compiled extension module directory ^[strings.txt:371]PYINSTALLER_SUPPRESS_SPLASH_SCREEN— environment flag to disable splash ^[strings.txt:329]PYZ archive entry not found in the TOC!— PYZ (Python ZIP archive) error string ^[strings.txt:423]PYINSTALLER_STRICT_UNPACK_MODE— new PyInstaller strict-extraction flag ^[strings.txt:338]Failed to extract script from archive!— extraction failure message ^[strings.txt:312]Failed to load PyInstaller's embedded PKG archive from the executable (%s)^[strings.txt:327]
These are unmodified PyInstaller bootloader messages, confirming the outer layer is stock infrastructure. ^[strings.txt:303-365]
Payload
The overlay (~13.6 MB) is a zlib-compressed CFFI archive. binwalk identifies PNG icons (16×16 through 64×64), an XML manifest, and hundreds of zlib streams at best/default compression — the classic PyInstaller overlay signature. ^[binwalk.txt]
Standard library modules present in the TOC (extractable from strings):
socket,urllib,urllib.parse— HTTP/S and raw socket networking ^[strings.txt:22652,22665,22666]emailandemail.base64mime— SMTP/mail exfil or C2 messaging ^[strings.txt:22580]base64,hashlib— encoding and hashing of exfil data ^[strings.txt:22570,22604]subprocess,threading— command execution and concurrency ^[strings.txt:22656,22660]ctypes,ctypes._endian— direct Win32 API invocation ^[strings.txt:22575,22576]wmivia_psutil_windows.pyd— system enumeration (CPU, memory, processes) ^[strings.txt:22687,22699]uuid— likely for generating a victim UID ^[strings.txt:22686]platform— system fingerprinting ^[strings.txt:22639]
In addition, the archive embeds a PyArmor-protected runtime:
pyarmor_runtime_000000module name andpyarmor_runtime.pydextension ^[strings.txt:22646,22700]__pyarmor__symbol name stub ^[strings.txt:883]
This means the raw Python source is not available in the overlay; it is obfuscated by PyArmor and decrypted/resolved at runtime by the .pyd extension. Static string extraction will therefore miss all IOCs (C2 URLs, hardcoded credentials, mutex names) because they are encrypted inside the PyArmor runtime.
Signing
Unsigned. signed: false. No Authenticode signature, no certificate table. ^[rabin2-info.txt]
Anti-Analysis
Not in the bootloader itself. The only anti-analysis mechanism visible statically is the PyArmor runtime obfuscation of the Python bytecode, which blocks decompilation and string extraction of the actual malicious logic. No anti-VM, anti-debug, or TLS callback tricks were identified in the PE header or radare2 analysis. ^[pefile.txt] ^[r2:fcn.140001010]
Decompiled Behavior
entry0 → CRT bootstrap → extraction
radare2 decompilation of entry0 at 0x1400013e0 is a short thunk that initializes a global flag and immediately calls fcn.140001010, the main CRT startup routine. ^[r2:entry0]
fcn.140001010 performs the expected MinGW-w64 startup:
- References
__wgetmainargsto retrieve command-line arguments. - Allocates a new wide-char argv buffer via
mallocand copies each argument withmemcpy/wcslen. - Calls
_inittermfor constructor tables. - Reads the PE header to validate the optional-header magic (
0x20bfor PE32+) and subsystem. - Eventually dispatches to the PyInstaller main loop (the remainder of the function is not fully decompiled by radare2's pdc, but the cross-references to Python C-API strings in
.rdataconfirm the transition).
No suspicious Win32 API imports (e.g., VirtualAllocEx, WriteProcessMemory, CreateRemoteThread) are present in the import table — all malware logic is delegated to the embedded Python interpreter. ^[r2:list_imports]
C2 Infrastructure
None observable statically. Because the payload is protected by PyArmor, no C2 URLs, IPs, domains, mutex names, or registry keys appear in the strings. The presence of urllib, socket, and email strongly implies network C2 or exfil, but the exact endpoints are runtime-resolved inside the PyArmor-decrypted layer. ^[strings.txt:22652,22666,22580]
Interesting Tidbits
- Massive overlay ratio: The PE code section is ~200 KB; the overlay is ~13.6 MB — a 68:1 ratio. This alone is a detection signal. ^[pefile.txt] ^[binwalk.txt]
- No UPX, no custom packer: The threat actor relied entirely on the PyInstaller + PyArmor double-wrap for evasion; no third-party packer or shellcode loader was added.
- Python 3.13: The presence of
python313.dllindicates a very recent PyInstaller build (Python 3.13 went stable in October 2024). This is a fresh toolchain, not a recycled older PyInstaller stub. ^[strings.txt:22701,22702] - Missing capa/floss: Both failed due to missing signatures or command-line syntax errors, not due to packing. The sample is trivial to analyze with
stringsandbinwalk. ^[capa.txt] ^[floss.txt]
How To Mess With It (Homelab Replication)
This is a teaching-sample for the PyInstaller + PyArmor packaging pipeline.
-
Write a minimal Python payload
import socket, urllib.request, base64, hashlib def main(): print("Hello from the sandbox.") if __name__ == "__main__": main() -
Obfuscate with PyArmor (trial or licensed)
pip install pyarmor pyarmor gen --pack onefile payload.pyThis produces a single PE that combines PyInstaller bootloader + the PyArmor runtime.
-
Verify overlay
python3 -m binwalk your_payload.exe | head -20Expect hundreds of
Zlib compressed dataentries and PNG icons. -
Static comparison Run
strings your_payload.exe | grep -i pyarmor— you should seepyarmor_runtimestrings similar to this sample. Compare tostrings.txtof this analysis.
What you will learn: PyArmor does not hide Python's standard library import names from the PyInstaller TOC; they remain visible in the outer PE's string table. This is why socket, urllib, ctypes, etc. are still present despite the obfuscation. Defenders can use these as weak behavioral indicators even when the C2 is encrypted.
Deployable Signatures
YARA Rule — PyInstaller + PyArmor Dropper
rule pyinstaller_pyarmor_dropper : pyinstaller pyarmor python stealer
{
meta:
description = "PyInstaller one-file bootloader with embedded PyArmor runtime"
author = "wiki-auto"
date = "2026-05-30"
sha256 = "d297973f8d1bb330dc7a7d7538bfbe97ea4608aee040b48122da39a2562ddf4c"
strings:
$pyi1 = "PYINSTALLER_SUPPRESS_SPLASH_SCREEN" ascii wide
$pyi2 = "_MEIPASS" ascii wide
$pyi3 = "base_library.zip" ascii wide
$pyi4 = "Could not load PyInstaller's embedded PKG archive" ascii wide
$pyarm1 = "pyarmor_runtime_000000" ascii wide
$pyarm2 = "__pyarmor__" ascii wide
$pyarm3 = "pyarmor_runtime.pyd" ascii wide
condition:
uint16(0) == 0x5A4D and
filesize > 5MB and
3 of ($pyi*) and
1 of ($pyarm*)
}
Sigma Rule — PyInstaller Extraction Directory Activity
title: Suspicious PyInstaller Extraction Directory Execution
status: experimental
description: Detects processes executing from the _MEI temporary directory created by PyInstaller single-file binaries
logsource:
category: process_creation
product: windows
detection:
selection:
Image|contains: '\_MEI'
filter:
- Image|contains: 'python'
- Image|endswith: '.exe'
condition: selection
falsepositives:
- Legitimate PyInstaller-based tools used by administrators
level: medium
tags:
- attack.execution
- attack.t1059.006
IOC List
| Indicator | Value | Notes |
|---|---|---|
| SHA-256 | d297973f8d1bb330dc7a7d7538bfbe97ea4608aee040b48122da39a2562ddf4c | Bootloader + overlay |
| Filename (source) | common.dat | Masquerade name; no relation to content |
| Build timestamp | 2026-05-21 09:51:36 UTC | Likely near compilation time |
| Embedded Python | python313.dll | CPython 3.13.x |
| Obfuscation | PyArmor runtime 000000 | Version not further discernible |
| Overlay | ~13.6 MB zlib-compressed | CFFI archive with PNG resources |
| Signing | Unsigned | No Authenticode |
Behavioral Fingerprint Statement
This binary launches as a stock Windows GUI PE with benign KERNEL32/USER32 imports. At runtime it extracts a ~13 MB zlib-compressed archive to %TEMP%\_MEI<XXXX>, loads python313.dll and pyarmor_runtime.pyd, then initializes the CPython interpreter to execute an obfuscated script. Network activity, if any, originates from the embedded Python layer (not directly from the outer PE) and typically uses urllib, socket, or smtplib modules. The outer PE itself performs no direct suspicious API calls — all malware behavior is deferred to the extracted Python runtime.
Detection Signatures (capa → ATT&CK)
capa failed on this sample due to a missing signatures directory. The following ATT&CK mapping is derived from static evidence:
| Technique ID | Technique Name | Evidence |
|---|---|---|
| T1027.002 | Obfuscated Files or Information: Software Packing | PyArmor runtime obfuscates Python payload; PyInstaller compresses overlay with zlib |
| T1059.006 | Command and Scripting Interpreter: Python | Embedded CPython 3.13 DLL; Py_InitializeFromConfig in strings |
| T1071 | Application Layer Protocol | socket, urllib, email modules present in archive |
| T1083 | File and Directory Discovery | FindFirstFileW, FindNextFileW, GetDriveTypeW imported by bootloader (used for extraction) |
| T1005 | Data from Local System | wmi/psutil and platform modules imply system enumeration |
| T1041 | Exfiltration Over C2 Channel | email + urllib suggest staged exfil; static-only inference |
| T1105 | Ingress Tool Transfer | If the payload downloads second-stage tools via urllib (static inference) |
Confidence note: All network and impact TTPs are inferred from Python module presence, not confirmed by dynamic execution. This sample requires a Windows sandbox detonation to confirm actual C2 endpoints, persistence, and payload behavior.
References
- pyinstaller-bootloader — concept page for PyInstaller single-file bootloader
- python-packed-payload — technique page for Python-packed malware
- pyarmor-obfuscation — concept page for PyArmor runtime obfuscation
- PyInstaller docs: https://pyinstaller.org/
- PyArmor docs: https://github.com/dashingsoft/pyarmor
Provenance
file.txt— file(1) type identificationpefile.txt— pefile Python library full dumprabin2-info.txt— radare2rabin2 -Isummarystrings.txt— GNU strings output (22,707 lines)binwalk.txt— binwalk signature scan (zlib PNG overlay enumeration)floss.txt— flare-floss invocation failed (bad CLI syntax)capa.txt— Mandiant capa invocation failed (missing signatures)dynamic-analysis.md— CAPE skipped (no Windows guest available)- radare2 decompilation — r2 v5.x with pdc decompiler on entry0 and fcn.140001010
- pyghidra analysis — queued successfully; strings/code indexing incomplete at time of query due to background processing
Analyst note: The absence of dynamic execution means this report is a static-only surface analysis. Any C2, persistence, or impact claims above are hypothesis-grade and must be validated with sandbox detonation once a Windows analysis VM is available.