Volatility 3 recovers encryption keys, C2 configs, and wallet addresses from Python infostealers by targeting CPython heap structures, PyInstaller overlay regions, and RWX memory segments that persist in RAM long after disk artifacts are wiped. PyInstaller --onefile --key obfuscation and marshal bytecode hiding complicate static analysis but leave predictable forensic residue in process memory.
Pithy Security | Cybersecurity FAQs – The Details
Question: In 2026-era Python information stealers, what memory forensics artifacts and Volatility 3 plugin patterns reliably recover dynamically generated encryption keys, C2 configuration dictionaries, wallet addresses, and execution traces when attackers abuse PyInstaller –onefile with –key obfuscation, marshal bytecode hiding inside pycache structures, and ctypes memory relocation to avoid obvious disk writes?
Asked by: Claude Sonnet 4.5
Answered by: Mike D (MrComputerScience) from Pithy Security.
Why PyInstaller –onefile Leaves Recoverable Forensic Residue in RAM
PyInstaller --onefile bundles everything into a self-extracting executable that decompresses to a temp directory at runtime (%TEMP\_MEIxxxxxx). The --key flag AES-encrypts the bundled .pyc files, which sounds protective until you understand the decryption happens in-process before execution. The decrypted bytecode lands in heap memory and stays there for the process lifetime.
The forensic entry point is the MEI temp directory path stored in the process environment block. Volatility 3’s windows.envars plugin recovers _MEIPASS2 pointing to the extraction path even if the directory has been deleted from disk. From there, windows.memmap on the target PID dumps all committed memory regions. Search those dumps for the \x42\x0d\x0d\x0a magic bytes (Python 3.x .pyc header) to locate decrypted bytecode that was never written back to disk after the --key decryption pass.
Marshal bytecode hidden inside legitimate-looking __pycache__ structures follows the same pattern. The bytecode must be deserialized into live code objects to execute. Those code objects persist on the CPython heap as PyCodeObject structs, complete with co_consts tuples that frequently contain hardcoded strings: C2 hostnames, wallet addresses, and API keys that survived the obfuscation layer intact.
Volatility 3 Plugin Patterns That Surface C2 Configs and Encryption Keys
The CPython object heap is the primary artifact surface. Python’s memory allocator (pymalloc) carves fixed-size arenas that Volatility can scan directly. The workflow that produces results against Xillen-style polymorphic variants and OysterLoader-class loaders:
First, windows.pstree and windows.cmdline to identify the target PID and confirm PyInstaller execution (look for single-executable processes with no obvious parent-child relationship to a Python interpreter). Then windows.vadinfo filtered to the target PID to enumerate Virtual Address Descriptors — RWX regions with no backing file on disk are immediate priority targets, because legitimate Python modules map from disk while dynamically generated shellcode and decrypted configs live in anonymous RWX allocations.
windows.dumpfiles on any file-backed VAD regions recovers partially-decrypted .pyc overlays. For heap content, windows.memmap --pid [PID] --dump followed by bulk YARA scanning with rules targeting PyDictObject structures (4-byte refcount + 4-byte ob_type pointer matching PyDict_Type offset) extracts dictionary objects that frequently contain C2 configuration verbatim. Dynamically generated AES and ChaCha20 keys appear as 16 or 32-byte high-entropy byte strings in PyBytesObject heap allocations adjacent to the config dictionaries. Bulk entropy scanning (Volatility’s windows.yarascan with a high-entropy YARA condition) on the heap dump reliably surfaces key material even when variable names have been stripped.
Recovering Wallet Addresses and Execution Traces from ctypes Relocations
ctypes memory relocation moves shellcode and config data into anonymous heap regions allocated via VirtualAlloc, deliberately avoiding file-backed mappings that forensic tools correlate to disk artifacts. These regions show up in windows.vadinfo output as PAGE_EXECUTE_READWRITE or PAGE_READWRITE VADs with VadType: 0 (private, no file backing) and unusually high entropy.
Wallet address recovery is often the easiest win. Bitcoin, Ethereum, and Monero address formats are regex-matchable even inside obfuscated memory. Run strings -el (UTF-16LE) and strings -a against raw memory dumps before any structured analysis. Clipper-style infostealers that swap clipboard wallet addresses keep a target address list in a live Python list or dict object that persists on the CPython heap throughout execution.
For execution traces, windows.python (available in community Volatility plugins) walks the CPython interpreter state to recover the active call stack, loaded module list, and sys.path entries at capture time. If that plugin isn’t available, manually locate the PyInterpreterState struct by scanning for the PyRuntime global exported by python3X.dll or embedded in the PyInstaller bundle’s memory space. The interpreter state chains to PyThreadState, which chains to the active frame (PyFrameObject), giving you function names, file paths (even if synthetic), and local variable bindings at the point of memory capture.
What This Means For You
- Run
windows.vadinfofiltered to anonymous RWX regions on any suspicious Python or PyInstaller process first, because file-less config and key material lives exclusively in those allocations. - Scan raw heap dumps with YARA rules targeting
PyDictObjectandPyBytesObjectstruct signatures to extract C2 dictionaries and high-entropy key material before attempting bytecode reconstruction. - Search all memory dumps with wallet address regex patterns (Base58, EIP-55 checksummed hex, Monero 95-character strings) immediately, since clipboard stealers maintain live target lists in Python heap objects throughout execution.
- Recover
_MEIPASS2fromwindows.envarsoutput to establish PyInstaller extraction paths and correlate decrypted bytecode regions even when the%TEMP%directory has been cleaned post-execution. - Capture memory at the earliest possible point after detection: CPython heap objects containing decrypted configs are garbage-collected once the relevant code objects go out of scope, and that forensic window closes fast.
Related Questions
- 1
- 2
- 3
