# DYNOWIPER: Destructive Malware Targeting Poland's Energy Sector

DYNOWIPER wiper destroyed data at 30+ Polish energy facilities after attackers entered via internet-exposed FortiGate VPNs lacking MFA and with reused credentials.

- Payout: Real-world hack
- Severity: critical
- Program: Poland energy sector
- Analysis by: Elastic Security Labs
- Disclosed: 2026-02-06
- Write-up quality: 6/10
- Original: https://www.elastic.co/security-labs/blog/dynowiper
- Library page: https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-blog-dynowiper
- Tags: area:devices domain:infra domain:network impact:data-leak impact:dos impact:mass-exploit kind:incident platform:threat-intel sev:critical tech:windows technique:recon technique:reverse-engineering vuln:auth-bypass vuln:mfa-bypass vuln:misconfiguration

**Target:** Internet-exposed FortiGate VPN/SSL-VPN interfaces and Windows OT/SCADA hosts
**Root cause:** Initial access came from FortiGate devices exposed to the internet with VPN interfaces permitting authentication without MFA, reused credentials across facilities, and historical vulnerabilities on unpatched devices. The wiper itself corrupts file headers and 16-byte sequences at random offsets using a Mersenne Twister PRNG, skipping system directories, then forces reboot.
**Steps:**
1. Gain VPN access to exposed FortiGate via accounts without MFA, reused credentials, or unpatched device vulnerabilities
2. Conduct months-long recon of SCADA/OT networks; exfiltrate AD databases, FortiGate configs, OT modernization data via reverse proxy `31.172.71[.]5`
3. Deploy DYNOWIPER (`dynacom_update.exe`, SHA256 `835b0d87...cede68d5`) via scheduled tasks/GPO
4. Wiper enumerates drives with `GetLogicalDrives()`, targeting only `DRIVE_FIXED` and `DRIVE_REMOVABLE`
5. For each file: `SetFileAttributesW(FILE_ATTRIBUTE_NORMAL)`, `CreateFileW` read/write, overwrite header with 16 random bytes, then up to 4,096 random offsets each overwritten with 16-byte sequences
6. Skip directories: windows, system32, program files, program files(x86), boot, appdata, temp, recycle.bin, $recycle.bin, perflogs, documents and settings
7. Enable `SeShutdownPrivilege` via `OpenProcessToken()`/`AdjustTokenPrivileges()`, then `ExitWindowsEx(EWX_REBOOT | EWX_FORCE)`
**How found:** Elastic Security Labs analyzed an open-source DYNOWIPER sample matching CERT Polska's version A, reverse-engineering its drive enumeration, corruption routine, exclusion list, and shutdown logic; CERT Polska attributed infrastructure and initial access.
**Exploitation:** Attackers pre-positioned for months inside OT networks of 30+ wind/solar farms and a CHP plant serving ~500k customers, exfiltrating AD and FortiGate configs, then on 2025-12-29 pushed the standalone wiper (no C2, no persistence) to corrupt files across fixed and removable drives and force reboot. At the CHP plant, canary-based EDR halted overwriting on 100+ machines already executing the wiper.
**Escalation:** Months of recon and credential/config theft enabled simultaneous destructive execution across many facilities; wiper deliberately spares system directories so the OS stays up long enough to finish corruption before reboot.
**Insight:** Wipers that skip system directories to stay stable are detectable behaviorally: alert on `GetLogicalDrives` followed by mass `SetFileAttributesW(FILE_ATTRIBUTE_NORMAL)` and header writes, and on `SeShutdownPrivilege` enablement plus `ExitWindowsEx` — canary files catch indiscriminate corruption even without C2 or persistence.

## Proof of concept (copied exactly from the original report)

**YARA rule detecting DYNOWIPER wiper**

```
rule DYNOWIPER { 
 meta: 
 author = "CERT Polska"
 description = "Detects DYNOWIPER data destruction malware" 
 severity = "CRITICAL" 
 reference = "https://mwdb.cert.pl/" 
 
 strings: 
 $a1 = "$recycle.bin" wide 
 $a2 = "program files(x86)" wide 
 $a3 = "perflogs" wide 
 $a4 = "windows\x00" wide 
 $b1 = "Error opening file: " wide 
 $priv = "SeShutdownPrivilege" wide 
 $api1 = "GetLogicalDrives" 
 $api2 = "ExitWindowsEx" 
 $api3 = "AdjustTokenPrivileges" 
 
 condition: 
 uint16(0) == 0x5A4D 
 and filesize < 500KB 
 and 4 of ($a*, $b1) 
 and $priv 
 and 2 of ($api*) 
}
```

**Elastic Defend canary ransomware detection alert**

```
{ 
 "message": "Ransomware Prevention Alert", 
 "event": { 
 "code": "ransomware", 
 "action": "canary-activity", 
 "type": ["info", "start", "change", "denied"], 
 "category": ["malware", "intrusion_detection", "process", "file"], 
 "outcome": "success" 
 }, 
 "Ransomware": { 
 "feature": "canary", 
 "version": "1.9.0" 
 } 
}
```

