Multi-stage loader chains Kramer-obfuscated Python, Donut shellcode and laZzzy to deliver an embedded .NET DLL
TrustedSec analyzed a multi-stage malware loader that starts as obfuscated Python bytecode (.pyc, CPython 3.13) and ends in an encrypted .NET payload. The chain layers Kramer Python obfuscation, two Donut shellcode layers and a laZzzy shellcode-loader PE, so a signature for any single format misses the others. The author unpacked it with public tooling, modified one tool and wrote a new one, laZzzy_dump, to extract laZzzy payloads statically. The article does not name the final payload's family, any attribution, victims or IOCs.
Detalhes técnicos
- Stage 1: Python bytecode obfuscated with Kramer and protected by a key. The key was brute-forced with kramer_python_deobfuscator.py. The author modified it to read only the small section it needed, cutting recovery from hours to under a minute. The recovered source holds a base64-encoded, RC4-encrypted shellcode blob, which is copied into executable memory and run.
- Stage 2: The first shellcode was generated by Donut (TheWover). It was decoded with Volexity's donut_decryptor, which yields a laZzzy-wrapped executable.
- Stage 3: laZzzy is an open-source loader that injects shellcode into the current or a remote process. It encrypts the payload with AES-CBC plus an extra XOR layer. Its AESDecrypt function is called by MovePayload and is used by all but one of the injection methods. No public static extractor existed, so the author built laZzzy_dump. It finds the decryption routine by its x86-64 byte-pattern signature, resolves RIP-relative LEA/MOV operands to locate the AES key, IV and ciphertext, then decrypts offline with AES-CBC followed by the XOR step. No debugger or execution is needed, and it was validated against several independently generated laZzzy samples.
- Stage 4: donut_decryptor is run again on the recovered shellcode, yielding the final module.
- Stage 5: An embedded .NET DLL whose encrypted resources are parsed, loaded into memory like shellcode and then executed.
Remediação
The article gives no explicit remediation. Its defensive takeaway is that detection should not rely on a single format signature. Static unpacking is feasible when a loader generator's source or instruction patterns are known, and the laZzzy_dump approach can be reused to recover payloads without dynamic execution.
Linha do tempo
-
Multi-stage loader chains Kramer-obfuscated Python, Donut shellcode and laZzzy to deliver an embedded .NET DLL
ler a análise desta fonte ocultar
TrustedSec analyzed a multi-stage malware loader that starts as obfuscated Python bytecode (.pyc, CPython 3.13) and ends in an encrypted .NET payload. The chain layers Kramer Python obfuscation, two Donut shellcode layers and a laZzzy shellcode-loader PE, so a signature for any single format misses the others. The author unpacked it with public tooling, modified one tool and wrote a new one, laZzzy_dump, to extract laZzzy payloads statically. The article does not name the final payload's family, any attribution, victims or IOCs.
Technical Details
- Stage 1: Python bytecode obfuscated with Kramer and protected by a key. The key was brute-forced with kramer_python_deobfuscator.py. The author modified it to read only the small section it needed, cutting recovery from hours to under a minute. The recovered source holds a base64-encoded, RC4-encrypted shellcode blob, which is copied into executable memory and run.
- Stage 2: The first shellcode was generated by Donut (TheWover). It was decoded with Volexity's donut_decryptor, which yields a laZzzy-wrapped executable.
- Stage 3: laZzzy is an open-source loader that injects shellcode into the current or a remote process. It encrypts the payload with AES-CBC plus an extra XOR layer. Its AESDecrypt function is called by MovePayload and is used by all but one of the injection methods. No public static extractor existed, so the author built laZzzy_dump. It finds the decryption routine by its x86-64 byte-pattern signature, resolves RIP-relative LEA/MOV operands to locate the AES key, IV and ciphertext, then decrypts offline with AES-CBC followed by the XOR step. No debugger or execution is needed, and it was validated against several independently generated laZzzy samples.
- Stage 4: donut_decryptor is run again on the recovered shellcode, yielding the final module.
- Stage 5: An embedded .NET DLL whose encrypted resources are parsed, loaded into memory like shellcode and then executed.
Remediation
The article gives no explicit remediation. Its defensive takeaway is that detection should not rely on a single format signature. Static unpacking is feasible when a loader generator's source or instruction patterns are known, and the laZzzy_dump approach can be reused to recover payloads without dynamic execution.