NeedyMantis: Sentinel Hunts for Targeted Post-Compromise Malware

Threat Overview

Microsoft Threat Intelligence disclosed NeedyMantis on September 28, 2026. It is a modular Windows post-compromise framework used for long-term access in a limited set of targeted intrusions affecting telecommunications organizations, universities, medical nonprofits, intergovernmental organizations, and government contractors. Microsoft has observed the malware since at least October 2025 and associates one user, Storm-3069, with the DAEMON Tools supply-chain compromise, but Microsoft has not observed NeedyMantis itself delivered through that supply chain. Initial access varies and occurs before the malware is deployed. NeedyMantis is packaged with legitimate applications such as Poedit, curl, Vim, or TightVNC. A malicious DLL is sideloaded when the legitimate executable starts and extracts a same-named encrypted archive. Later stages use custom compressed/XOR-encoded archives, x64 shellcode, a minimized PE-like format, dynamic API resolution, anti-debugging, and a modular C2 architecture. The analyzed sample used HTTPS and WebSockets to corp.tripswithengine[.]com on TCP/443, URI /library/zip/, and hard-coded user agent firefox/21.0. Microsoft confirmed module load, unload, and dispatch capabilities but did not confirm the functions of downloaded modules.

References

Impacted Systems

  • Platform: Windows endpoints and servers on which an attacker already has execution or deployment access.
  • Deployment role: Targeted enterprise hosts; one observed deployment used Impacket to copy the bundle from a network share and execute it remotely.
  • Abused software: Poedit, curl, Vim, TightVNC, and DLL names masquerading as Microsoft Office, Broadcom, Intel, or NVIDIA components.
  • Prerequisite: Prior compromise; NeedyMantis is not documented as the initial-access mechanism.
  • Versions: No vulnerable product version is implicated. Legitimate software versions alone cannot determine exposure.
  • Network: Outbound HTTPS/WebSocket connectivity to attacker infrastructure; published C2 is corp.tripswithengine[.]com:443.

Why this matters

NeedyMantis is confirmed post-compromise malware used selectively for persistent access. Its DLL sideloading, legitimate-program abuse, network-share staging, encrypted archives, and modular C2 can blend into administrative activity. The affected sectors and overlap with remote-management software make the behavior relevant across MSSP endpoint estates.

Exploitation Status

Microsoft directly observed NeedyMantis in targeted intrusions and confirmed at least one operator, Storm-3069. Microsoft assesses activity associated with China-based actors but has not attributed it to a Chinese nation-state or determined whether all deployments share one operator. The published hashes and domain are confirmed indicators. No claim is made that the current C2 remains active or that every DLL at a listed path is malicious.

What this hunt looks for

Published hashes and C2 artifacts, abnormal DLL loads by signed utilities, archive-to-side-load sequences, Impacket-style network-share staging, PowerShell artifacts, service persistence, the published URI and user agent, and unusual WebSocket-capable egress.

Required logs

Microsoft Defender for Endpoint DeviceProcessEvents, DeviceFileEvents, DeviceImageLoadEvents, DeviceRegistryEvents, and DeviceNetworkEvents; proxy or firewall logs in CommonSecurityLog or AzureDiagnostics; and Windows Security Events or WindowsEvent for process and service-creation fallback coverage.

Hunt 1 — High-confidence hashes, domain, paths, and user agent

let Hashes = dynamic(["e842dd7642c8e04b5ec20b6393848a9c904e4832930950c16664fe7800ba382e","9cb68f986043a576e19d32184c583b7d8f571c7219d8dc0065dced1c13f077ef","c82520eb03c084226be4eafbff46f56dca0aa8804a2a7f23a085a96afe71ef77"]);
union isfuzzy=true
(DeviceFileEvents | where TimeGenerated > ago(90d) | where SHA256 in (Hashes) or FolderPath has_any ("\\ProgramData\\USOShared\\","\\ProgramData\\VIM\\","\\ProgramData\\TightVNC\\VIM\\","\\ProgramData\\office\\","\\ProgramData\\broadcom\\","\\ProgramData\\Intel\\","\\ProgramFiles\\modifiable\\","\\ProgramData\\ics\\") | project TimeGenerated, DeviceName, Evidence="File", Detail=strcat(FolderPath,"\\",FileName), SHA256, InitiatingProcessFileName, InitiatingProcessCommandLine),
(DeviceNetworkEvents | where TimeGenerated > ago(90d) | where RemoteUrl =~ "corp.tripswithengine.com" | project TimeGenerated, DeviceName, Evidence="Network", Detail=strcat(RemoteUrl," ",RemoteIP,":",RemotePort), SHA256=InitiatingProcessSHA256, InitiatingProcessFileName, InitiatingProcessCommandLine)
| order by TimeGenerated desc

Hunt 2 — Legitimate applications loading DLLs from suspicious adjacent paths

DeviceImageLoadEvents
| where TimeGenerated > ago(90d)
| where InitiatingProcessFileName in~ ("poedit.exe","curl.exe","vim.exe","gvim.exe","tvnserver.exe","tvnviewer.exe")
| where FileName in~ ("WinSparkle.dll","libcurl.dll","vim64.dll","dbghelp.dll","jli.dll","nvml.dll")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FolderPath, FileName, SHA256, InitiatingProcessAccountName
| order by TimeGenerated desc

Hunt 3 — Published DLL names created with extensionless companion archives

let DllWrites = DeviceFileEvents
| where TimeGenerated > ago(90d) and ActionType in~ ("FileCreated","FileRenamed","FileModified")
| where FileName in~ ("WinSparkle.dll","libcurl.dll","vim64.dll","dbghelp.dll","jli.dll","nvml.dll")
| extend BaseName=replace_regex(FileName,@"(?i)\.dll$","")
| project DeviceId, DeviceName, FolderPath, BaseName, DllTime=TimeGenerated, DllFile=FileName, DllHash=SHA256, InitiatingProcessFileName;
let Archives = DeviceFileEvents
| where TimeGenerated > ago(90d) and ActionType in~ ("FileCreated","FileRenamed","FileModified")
| where FileName !contains "."
| project DeviceId, FolderPath, BaseName=FileName, ArchiveTime=TimeGenerated, ArchiveHash=SHA256;
DllWrites
| join kind=inner Archives on DeviceId, FolderPath, BaseName
| where ArchiveTime between (DllTime-10m .. DllTime+10m)
| project DllTime, ArchiveTime, DeviceName, FolderPath, DllFile, DllHash, ArchiveHash, InitiatingProcessFileName

Hunt 4 — Network-share staging followed by remote execution

DeviceFileEvents
| where TimeGenerated > ago(90d)
| where InitiatingProcessFileName in~ ("wmiexec.exe","psexec.exe","smbexec.exe","python.exe","python3.exe","cmd.exe") or InitiatingProcessCommandLine has_any ("impacket","ADMIN$","C$","IPC$")
| where FolderPath startswith "\\\\" or InitiatingProcessCommandLine has "\\\\"
| project TimeGenerated, DeviceName, ActionType, FolderPath, FileName, SHA256, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc

Hunt 5 — PowerShell-extension files that are written but not executed by PowerShell

DeviceFileEvents
| where TimeGenerated > ago(90d) and ActionType in~ ("FileCreated","FileModified")
| where FileName endswith ".ps1"
| where FileName in~ ("encryptbase64.ps1") or InitiatingProcessFileName in~ ("poedit.exe","curl.exe","vim.exe","gvim.exe","tvnserver.exe")
| project TimeGenerated, DeviceName, FolderPath, FileName, SHA256, FileSize, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc

Hunt 6 — Windows service persistence referencing suspicious paths or DLL names

DeviceRegistryEvents
| where TimeGenerated > ago(90d)
| where RegistryKey has @"SYSTEM\CurrentControlSet\Services\"
| where RegistryValueName in~ ("ImagePath","ServiceDll")
| where RegistryValueData has_any ("\\ProgramData\\USOShared\\","\\ProgramData\\VIM\\","\\ProgramData\\TightVNC\\VIM\\","WinSparkle","libcurl","vim64.dll","dbghelp.dll","jli.dll","nvml.dll")
| project TimeGenerated, DeviceName, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc

Hunt 7 — C2 domain, URI, and hard-coded user agent in proxy or web telemetry

union isfuzzy=true CommonSecurityLog, AzureDiagnostics
| where TimeGenerated > ago(90d)
| extend Url=tostring(coalesce(column_ifexists("RequestURL_s",""),column_ifexists("requestUri_s",""),column_ifexists("DestinationHostName",""))), UserAgent=tostring(coalesce(column_ifexists("RequestClientApplication",""),column_ifexists("userAgent_s","")))
| where Url has "corp.tripswithengine.com" or Url has "/library/zip/" or UserAgent =~ "firefox/21.0"
| project TimeGenerated, SourceIP=column_ifexists("SourceIP",""), DestinationIP=column_ifexists("DestinationIP",""), Url, UserAgent, DeviceVendor=column_ifexists("DeviceVendor",""), DeviceProduct=column_ifexists("DeviceProduct","")
| order by TimeGenerated desc

Hunt 8 — Suspicious WebSocket egress from sideloaded applications

DeviceNetworkEvents
| where TimeGenerated > ago(90d) and ActionType == "ConnectionSuccess"
| where InitiatingProcessFileName in~ ("poedit.exe","curl.exe","vim.exe","gvim.exe","tvnserver.exe","tvnviewer.exe")
| where RemotePort in (443,80)
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), RemoteIPs=make_set(RemoteIP,20), RemoteUrls=make_set(RemoteUrl,20) by DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessSHA256
| order by Connections desc

Hunt 9 — Windows event fallback for service creation and suspicious execution

union isfuzzy=true
(SecurityEvent | where TimeGenerated > ago(90d) and EventID in (4688,4697) | project TimeGenerated, Computer, EventID, User=SubjectUserName, Process=NewProcessName, Parent=ParentProcessName, Detail=coalesce(CommandLine,ServiceFileName)),
(WindowsEvent | where TimeGenerated > ago(90d) and EventID == 7045 | project TimeGenerated, Computer, EventID, User="", Process="", Parent="", Detail=tostring(EventData))
| where Detail has_any ("\\ProgramData\\USOShared\\","\\ProgramData\\VIM\\","\\ProgramData\\TightVNC\\VIM\\","encryptbase64.ps1","WinSparkle.dll","libcurl.dll","vim64.dll","dbghelp.dll","jli.dll","nvml.dll")
| project TimeGenerated, Computer, EventID, User, Process, Parent, Detail
| order by TimeGenerated desc

Detection Notes

  • Hunts 1 and 2 are highest signal. The Poedit WinSparkle.dll path is legitimate in clean installations, so verify the hash and signature before escalating.
  • The published C2 domain and hashes are historical and not exhaustive. Actor infrastructure and archive names can change.
  • DLL sideloading is best detected with image-load telemetry. Process-only data may show only the legitimate executable.
  • Event 7045 is collected from the System channel and requires WindowsEvent; Event 4697 requires Security-log auditing.
  • Proxy connectors do not share a universal schema. The column_ifexists approach may need field mapping for the deployed connector.
  • NeedyMantis is post-compromise malware. A match should trigger investigation of preceding credential access, lateral movement, and remote administration within available telemetry, but the public research does not define a single initial-access path.