# laZzzy Donut: Multi-Stage Loader Chain (Kramer-obfuscated Python, Donut shellcode, laZzzy loader)

> TrustedSec (Scott Nusbaum) unpacked a six-stage loader chain: a Kramer-obfuscated Python 3.13 .pyc carrying RC4-encrypted shellcode, a first Donut layer, the open-source laZzzy AES-CBC/XOR shellcode loader, a second Donut layer, and a .NET DLL with encrypted resources that are loaded into memory as shellcode. The write-up shows every layer can be unwound statically, and releases a new extraction tool, LazZzy_Dump.

- **Published:** 2026-11-06T00:07:55Z
- **Last reviewed:** 2026-11-06T00:07:55Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-5357
- **Card image:** https://intel.threadlinqs.com/og/threat/TL-2026-5357.png
- **ID:** TL-2026-5357
- **Severity:** MEDIUM
- **Category:** MALWARE
- **Status:** ACTIVE
- **Detections:** 9 (1 shown free below) · **IOCs:** 9

## Description

TrustedSec's 'Unpacking a laZzzy Donut' (published 2026-09-17) walks through a layered loader in which each stage hides the next, so that no single signature or format-specific tool covers the whole chain. The source does not name an actor, campaign, victim, delivery vector or C2 server, and it does not state in-the-wild exploitation; severity is analyst-assigned.

Stage 1 is a Python 3.13 bytecode file (.pyc, magic number 3571, compile timestamp Wed Jun 24 06:18:29 2026 UTC, recorded .py size 8,371,083 bytes) obfuscated with the public Kramer tool (billythegoat356/Kramer). Kramer output is compiled bytecode rather than source. The analyst decompiled it with depyo and recovered the key with a modified kramer_python_deobfuscator.py, cutting a brute-force step from hours to under a minute. The recovered payload is Base64-encoded, RC4-encrypted shellcode, which the script copies into an executable memory section and runs.

Stage 2 is shellcode generated by TheWover's Donut, a position-independent shellcode generator. Volexity's donut_decryptor was used to extract the Donut instance and module. The module wraps a complete Windows PE built with laZzzy (capt-meelo/laZzzy), which is a full PE rather than shellcode, so Donut tooling alone could not unwrap it.

Stage 3 is the laZzzy loader. It AES-CBC-decrypts an embedded payload (ciphertext length 0x12a50) and applies an additional rolling 16-byte XOR layer. The upstream project is a Windows x64 shellcode loader offering ten execution techniques (Early-bird APC queue, thread hijacking, KernelCallbackTable, section view mapping, thread suspension, LineDDA callback, EnumSystemGeoID callback, FLS callback, SetTimer, clipboard), direct syscalls with Nt* functions, IAT evasion, XOR-encrypted strings, PPID spoofing, blocking of non-Microsoft-signed DLLs, and optional icon/metadata cloning and spoofed code signing. The article does not say which injection method this sample used. Because no tool handled this stage, the author wrote LazZzy_Dump (trustedsec/LazZzy_Dump), which byte-pattern scans for the compiled AESDecrypt routine, resolves RIP-relative LEA/MOV operands to recover the AES key, IV, XOR key and ciphertext, and decrypts statically without a debugger or emulator.

Stage 4 is a second Donut layer, again unwrapped with donut_decryptor, yielding a .NET DLL (stage 5; analyzable with dnSpy/ILSpy). In stage 6 the DLL's resources are encrypted; their sections are parsed, loaded into memory as shellcode, and control is transferred to them. Donut itself, per its documentation, uses Chaskey encryption, optional aPLib/LZNT1/Xpress compression, patches AMSI, WLDP and ETW, can overwrite PE headers, and hosts .NET assemblies by loading the CLR into a new AppDomain. These are properties of the tool, not behaviors the article reports for this specific sample.

Defensive value: detection should be layered across behavior and memory (Python spawning executable memory and injecting, CLR loading into unexpected processes, laZzzy-style syscall injection), and the compiled-artifact patterns LazZzy_Dump uses can support YARA/memory hunting. No network IOCs or sample hashes are published, so BeaconBeagle correlation was not applicable.

## MITRE ATT&CK

- T1059.006 Command and Scripting Interpreter: Python
- T1027 Obfuscated Files or Information
- T1027.013 Obfuscated Files or Information: Encrypted/Encoded File
- T1027.009 Obfuscated Files or Information: Embedded Payloads
- T1140 Deobfuscate/Decode Files or Information
- T1620 Reflective Code Loading
- T1055 Process Injection
- T1055.004 Process Injection: Asynchronous Procedure Call
- T1055.003 Process Injection: Thread Execution Hijacking
- T1106 Native API
- T1134.004 Access Token Manipulation: Parent PID Spoofing
- T1553.002 Subvert Trust Controls: Code Signing
- T1685 Disable or Modify Tools

## Free detection (Splunk SPL, Microsoft KQL, Sigma)

**laZzzy Donut Stage 1 - Python Interpreter Injecting Into Another Process** (`TL-2026-5357-SPL-001`)

Detects python.exe, pythonw.exe or py.exe creating a remote thread (Sysmon EID 8) or opening another process with write/operation access rights (Sysmon EID 10). In the TL-2026-5357 laZzzy Donut chain, the Kramer-obfuscated Python 3.13 stage decrypts RC4 shellcode, runs a Donut layer, and the embedded laZzzy loader then injects its payload into other processes. A scripting interpreter writing into foreign processes is rare in normal operations and is a high-signal behavioural indicator that does not depend on file hashes, which the source does not publish.

- **Languages:** Splunk SPL, Microsoft KQL, Sigma
- **ATT&CK:** T1059.006, T1055, T1620
- **Data sources:** sysmon:EventID8, sysmon:EventID10, index=sysmon sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational
- **Severity:** high
- **Confidence:** high

### Splunk SPL

```spl
index=sysmon sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" earliest=-24h (EventCode=8 OR EventCode=10) (SourceImage="*\\python.exe" OR SourceImage="*\\pythonw.exe" OR SourceImage="*\\py.exe") | eval is_hit=if(EventCode=="8" OR (EventCode=="10" AND match(GrantedAccess, "(?i)^0x(1fffff|1f3fff|1f0fff|143a|1438|1478|28|2a|3a|38|1028|102a)$")), 1, 0) | where is_hit=1 AND lower(SourceImage)!=lower(TargetImage) | eval threat="TL-2026-5357 laZzzy Donut Python stage-1 injection" | stats count min(_time) as first_seen max(_time) as last_seen values(EventCode) as event_codes values(GrantedAccess) as granted_access values(TargetImage) as injected_targets by host, SourceImage, threat | convert ctime(first_seen) ctime(last_seen)
```

### Microsoft KQL

```kql
// TL-2026-5357 laZzzy Donut - Python interpreter performing cross-process injection primitives (Defender XDR)
let lookback = 24h;
DeviceEvents
| where Timestamp > ago(lookback)
| where ActionType in ("CreateRemoteThreadApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "WriteProcessMemoryApiCall", "QueueUserApcRemoteApiCall", "SetThreadContextRemoteApiCall", "NtMapViewOfSectionRemoteApiCall")
| where InitiatingProcessFileName in~ ("python.exe", "pythonw.exe", "py.exe")
| summarize ApiCalls = make_set(ActionType), TargetProcesses = make_set(FileName), EventCount = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine, InitiatingProcessAccountName
| order by LastSeen desc
```

### Sigma

```yaml
title: Python Interpreter Creating Remote Thread (laZzzy Donut Stage-1 Injection)
id: 1a7c9e30-5f42-4b8d-a6e1-3d0f82b97c54
status: experimental
description: Detects python.exe, pythonw.exe or py.exe creating a thread in another process. In the TL-2026-5357 laZzzy Donut chain the Kramer-obfuscated Python stage runs Donut shellcode which hands off to the laZzzy loader for process injection.
references:
    - https://trustedsec.com/blog/unpacking-a-lazzzy-donut
    - https://github.com/capt-meelo/laZzzy
author: AII-Detector — ThreadLinqs Intelligence
date: 2026/11/06
tags:
    - attack.execution
    - attack.t1059.006
    - attack.defense-evasion
    - attack.privilege-escalation
    - attack.t1055
    - attack.t1620
    - attack.s0695
logsource:
    category: create_remote_thread
    product: windows
detection:
    selection:
        SourceImage|endswith:
            - '\python.exe'
            - '\pythonw.exe'
            - '\py.exe'
    condition: selection
falsepositives:
    - Python-based debuggers, instrumentation or automation frameworks that legitimately inject threads
level: high
```

**Known false positives:**

- Python-based debuggers, profilers or instrumentation frameworks (e.g. frida, pydevd) opening or injecting into other processes
- Developer workstations running Python automation that uses ctypes/pywin32 against other processes

**Tuning notes:** Change index=sysmon to your Sysmon index. Sysmon EID 8 and EID 10 must be enabled in the Sysmon config (EID 10 targeted at write-capable GrantedAccess values). Exclude known developer hosts and sanctioned debugger/target pairs via a lookup on SourceImage+TargetImage. Add pyinstaller-style renamed interpreters if you see them in your estate.

### 8 more detection rules (Splunk SPL, Microsoft KQL, Sigma), unlock with TL Creds

- laZzzy Donut Stage 1 - Python Executing Oversized .py From User-Writable Path (Kramer Bytecode Masquerade) (locked: unlock with TL Creds)
- laZzzy Donut Stage 4-6 - Unbacked In-Memory .NET Module Load or CLR in Python (locked: unlock with TL Creds)
- laZzzy Donut Stage 3 - Remote Injection API Chain (Early-bird APC / Thread Hijack / Section Mapping) (locked: unlock with TL Creds)
- laZzzy Donut Stage 1 - Python ctypes Shellcode Runner Command Line (locked: unlock with TL Creds)
- laZzzy Donut Stage 2/4 - AMSI or WLDP Library Loaded Into Python or Utility Host (Donut Bypass) (locked: unlock with TL Creds)
- laZzzy Donut Stage 3 - Sacrificial Windows Host Process Launched Without Arguments (locked: unlock with TL Creds)
- laZzzy Donut Stage 2/4 - CLR Loaded Into Python or Non-.NET Host Process (locked: unlock with TL Creds)
- laZzzy Donut Stage 3 - Process Access With Unbacked CallTrace (Direct-Syscall / Shellcode Injection) (locked: unlock with TL Creds)

Sign up free to unlock the detection rules: https://threadlinqs.com/?modal=signup

## Sources

- [Unpacking a laZzzy Donut (Scott Nusbaum, TrustedSec)](https://trustedsec.com/blog/unpacking-a-lazzzy-donut)
- [capt-meelo/laZzzy - shellcode loader](https://github.com/capt-meelo/laZzzy)
- [trustedsec/LazZzy_Dump - static laZzzy payload extractor](https://github.com/trustedsec/LazZzy_Dump)
- [billythegoat356/Kramer - Python obfuscator](https://github.com/billythegoat356/Kramer)
- [TheWover/donut - position-independent shellcode generator](https://github.com/TheWover/donut)
- [volexity/donut-decryptor - Donut config and module extractor](https://github.com/volexity/donut-decryptor)
- [Binary Refinery kramer unit (Kramer deobfuscation)](https://binref.github.io/units/malware/kramer.html)
- [donut-decryptor on PyPI](https://pypi.org/project/donut-decryptor/)

## Full data

The other 8 detection rules (Splunk SPL / Microsoft KQL / Sigma) unlock with TL Creds; a free account includes daily creds: https://threadlinqs.com/?modal=signup
Indicator values unlock with TL Creds, at a lower price on Red and Purple; AI agents can query rules and IOCs through the Threadlinqs MCP server (Purple): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-5357
