HollowGraph — Microsoft 365 Calendar as Covert C2
Executive Summary
HollowGraph is a .NET NativeAOT-compiled DLL implant reported by Group-IB on 2026-07-20. Its defining trait is that it never talks to attacker infrastructure for tasking or exfil: it drives the Microsoft Graph API against a compromised Microsoft 365 mailbox calendar, treating the calendar as a two-way dead drop. Operators plant tasking as a calendar event buried at 2050-05-13 (24 years out, so the mailbox owner never scrolls to it); the implant reads it with a get command, and exfiltrates stolen files with a send command as encrypted attachments (File{n}.txt) on new far-future events. Payloads use hybrid RSA + AES-256 with separate inbound/outbound key pairs.
A second, out-of-band channel refreshes the implant's stolen OAuth credentials (tenantId / clientId / clientSecret / target mailbox) by decoding bytes smuggled inside IPv6 AAAA records answered by the attacker domain cloudlanecdn[.]com, then writing them to an on-disk config named logAzure.txt (dressed up as a routine log). Group-IB ties the implant to the Cavern framework with high confidence (matching command grammar, the 003 debug self-command) and notes only a low-confidence overlap with Lyceum (an OilRig subgroup); it does not attribute the campaign. Roughly 12 machines were seen infected, about 3 actively communicating, with victim traffic 2026-06-03 to 2026-07-09.
Highest-value defensive angle: because the C2 rides trusted Microsoft cloud services, endpoint network detection alone is weak. The strongest signals are (1) the on-disk artifact logAzure.txt, (2) DNS AAAA lookups to cloudlanecdn[.]com and its *.p/*.q tunnel labels, and (3) M365/Entra audit evidence of an application identity writing far-future calendar events with GUID / "Event ID:" subjects and File{n}.txt attachments.
Defender priority: Hunt the M365 audit trail and the credential channel, not just the box. Enumerate application registrations holding Calendars.ReadWrite / Mail.Read application permissions, hunt calendar items dated 2050 across all mailboxes, and rotate any tenant/client secrets found in logAzure.txt. The endpoint DNS + config-file hunts here are the tripwire; the M365 audit hunts are the confirmation.
Source Review & Web-Hunter Notes
Four sources fetched and saved verbatim to HollowGraph-M365Calendar-Hunt-sources/ at research time. Every atomic indicator in this pack traces to snapshot 01 (Group-IB primary) and is corroborated by the T2 press. The Register carries the narrative but not the atomic IOC appendix.
| # | Source | Tier | Contributed |
|---|---|---|---|
| 01 | Group-IB — HOLLOWGRAPH blog (primary) | T1 | Domain, 3× SHA256, logAzure.txt, Graph endpoints, get/send/003 grammar, DNS AAAA tunnel format, RSA/AES scheme, Cavern link, Lyceum pivot hash, victim stats |
| 02 | The Hacker News | T2 | Corroborates domain, logAzure.txt, File{n}.txt, 2050 dating, get/send, RSA+AES, Cavern Manticore note, 12/3 victims, timeline |
| 03 | BleepingComputer | T2 | Corroborates domain, logAzure.txt, AAAA 14-byte payload detail, 22:00–23:00 UTC retrieval window, Event ID:/Boss ID subject split |
| 04 | The Register | T2 | Narrative: 2050 dating, Graph calendar C2, 12/3 victims, Israeli targeting, Cavern (high) / Lyceum (low) |
Provenance note: the SHA256 samples and the Lyceum pivot hash are carried from the Group-IB appendix (snapshot 01). No public IPs were published; the C2 is domain-only. Where a campaign artifact is a pattern rather than a literal (File{n}.txt, GUID subjects), it is shipped as a behavioral signature, not an atomic block.
Hunt Brief & Attack Chain
Hypothesis: If HollowGraph is active, we should see a .NET DLL host process on an endpoint that (a) drops/reads logAzure.txt, (b) issues bursts of AAAA DNS lookups to cloudlanecdn[.]com subdomains, and — in the M365 tenant — an application identity creating/reading calendar events dated 2050 with GUID / "Event ID:" subjects and File{n}.txt attachments.
| # | Stage | Behavior | Telemetry |
|---|---|---|---|
| 1 | Foothold | .NET NativeAOT DLL loaded by a host process (rundll32/regsvr32/custom) | ProcessRollup2 |
| 2 | Config staging | Reads/writes logAzure.txt holding tenantId/clientId/clientSecret + RSA keys | File-write / ProcessRollup2 CommandLine |
| 3 | Credential refresh | AAAA lookups to *.p/*.q.cloudlanecdn[.]com; decodes 14 bytes per IPv6 answer | DnsRequest (AAAA) |
| 4 | Tasking (get) | Graph calendarView query for the planted 2050-05-13 event; downloads + RSA-decrypts attachment | M365 audit / Graph sign-in |
| 5 | Collection + Exfil (send) | Creates far-future events, uploads AES-256 encrypted File{n}.txt attachments; PATCH subject to GUID/Boss ID | M365 audit (Exchange item create/update) |
| 6 | Evasion | All C2 rides Graph over trusted Microsoft infra; on-disk name mimics a log; events buried at 2050 | Cross-source correlation |
Consolidated IOC Table
| Type | Value | Conf | Action | Context |
|---|---|---|---|---|
| Domain | cloudlanecdn[.]com | high | detect | DNS AAAA C2 / credential-refresh tunnel |
| SHA256 | 75e51774b8f79e5f256eaae639635f911b3e744d4774fd6068dd980255621509 | high | detect | HollowGraph .NET DLL implant |
| SHA256 | f3f3006f8304788251b153d53b305322b8acab0c66ec816b8d9f101bcc851da3 | high | detect | HollowGraph .NET DLL implant |
| SHA256 | b3d0f6e4e3be395fd7cf9e8101c89963d77216578cbb117a6ac9bc3564485eff | high | detect | HollowGraph .NET DLL implant |
| Filename | logAzure.txt | high | hunt | On-disk config storing creds + RSA keys |
| Attachment | File{n}.txt | medium | hunt | Encrypted calendar-event attachments (behavioral) |
| Calendar date | 2050-05-13 | high | hunt | Planted far-future tasking / exfil events |
| Event subject | Event ID: {taskID} / Boss{..}ID{..} / bare GUID | medium | hunt | get / send / operator-rename subject patterns |
| DNS labels | *.p.cloudlanecdn[.]com / *.q.cloudlanecdn[.]com | high | hunt | Length (p) + data (q) tunnel query labels |
| Command grammar | get / send / 003 (debug) | medium | enrich | Cavern-style tasking verbs |
| SHA256 | 315bdba98c6fe863d39f6afccc727e17d5aea63bf21259444fa988cae56d61c1 | low | pivot | Related Lyceum 2025 .NET backdoor — attribution pivot only, do NOT block |
Affected Surface & Telemetry Matrix
| Surface | Exposure | Telemetry | Priority | Gap |
|---|---|---|---|---|
| Windows endpoint (.NET runtime) | DLL implant host, logAzure.txt, DNS channel | Falcon ProcessRollup2 / DnsRequest | Critical | Managed-assembly load has limited telemetry — hunt host process |
| Microsoft 365 / Exchange Online | Calendar dead drop, exfil attachments | M365 Unified Audit Log (Exchange item ops) | Critical | Requires UAL ingest; RecordType/field names parser-specific |
| Entra ID (Azure AD) | Stolen client-credential OAuth app driving Graph | Entra sign-in (service principal) + audit logs | High | Non-interactive token use easy to miss without SPN sign-in ingest |
| Egress DNS | AAAA tunnel to cloudlanecdn[.]com | Falcon DnsRequest / resolver logs | High | Split-horizon / on-box resolvers may hide the loader process |
MITRE ATT&CK Mapping
| Tactic | Technique | Behavior | Query / Control |
|---|---|---|---|
| Initial Access / Persistence | T1078.004 Valid Accounts: Cloud Accounts | Uses stolen M365 client-credential app to reach Graph | S7-B, S12 |
| Execution / Defense Evasion | T1218.011 Rundll32 / T1218.010 Regsvr32 | .NET DLL hosted by a signed system binary | Q7 |
| Command & Control | T1102.002 Web Service: Bidirectional | M365 calendar as two-way dead drop via Graph | S7-A |
| Command & Control | T1071.004 Application Layer Protocol: DNS | AAAA-record channel to cloudlanecdn[.]com | Q3, Q4 |
| Command & Control | T1573.001/.002 Encrypted Channel (AES / RSA) | Hybrid RSA + AES-256, split in/out keys | Q7 (host), S7 |
| Defense Evasion | T1036.005 Masquerading: Match Legit Name | logAzure.txt log-lookalike; cloudlanecdn "CDN" name | Q1, Q2 |
| Credential Access | T1552.001 Unsecured Credentials in Files | tenant/client secret stored in logAzure.txt | Q1, Q2, S12 |
| Collection / Exfiltration | T1567 Exfiltration Over Web Service | Stolen files as encrypted File{n}.txt calendar attachments | S7-A |
| C2 / Ingress | T1132 Data Encoding | Config bytes encoded in IPv6 AAAA answers (14B/record) | Q4 |
Native Audit-Log Hunts (M365 / Entra / Graph)
The calendar C2 lives in the Microsoft cloud, not on the wire, so the confirming evidence is in the M365 Unified Audit Log and Entra sign-in/audit logs. These run in Microsoft 365 / Purview / Sentinel (KQL over ingested logs), not Falcon CQL. Field names and RecordType values are tenant/parser specific — validate against a sample row before deploying.
S7-A · M365 Unified Audit Log — far-future calendar events by an app identity
// Microsoft 365 Unified Audit Log (Purview) / Sentinel — Graph-driven calendar dead drop
// Anomalous Exchange calendar item create/update by a client-credentials application,
// with far-future (2050) dates or HollowGraph subject/attachment patterns.
OfficeActivity
| where Operation in ("Create", "Update", "Send")
| where RecordType startswith "Exchange" // calendar items are Exchange records
| where isnotempty(ClientAppId) // application-driven (non-interactive) write
| where Item has "2050" // planted 2050-05-13 tasking / exfil events
or Subject matches regex @"^Event ID:|^Boss.*ID|^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-"
or Item has "File" and Item has ".txt" // File{n}.txt attachment naming
| project TimeGenerated, UserId, ClientAppId, ClientIP, Operation, Subject, Item
| sort by TimeGenerated desc
S7-B · Entra ID — client-credential app grant + non-interactive Graph token use
// (a) An app registration granted broad calendar/mail APPLICATION permissions or a fresh secret
AuditLogs
| where OperationName in ("Add app role assignment to service principal",
"Add delegated permission grant",
"Consent to application",
"Add service principal credentials")
| where Result == "success"
| project TimeGenerated, OperationName, InitiatedBy, TargetResources
// (b) Service-principal (client-credentials) Graph sign-ins spiking from one IP/ASN
AADServicePrincipalSignInLogs
| where isnotempty(AppId)
| where ResourceDisplayName == "Microsoft Graph"
| summarize calls = count() by AppId, IPAddress, bin(TimeGenerated, 1h)
| where calls >= 50 // tune per tenant baseline
Pair these with the endpoint hunts in section 8: a host firing Q1/Q3 plus an app identity firing S7-A on the same day is a high-confidence HollowGraph confirmation.
CrowdStrike LogScale CQL Hunt Queries
Looks for: any process whose command line references the on-disk config logAzure.txt. FP: legitimate tooling rarely uses this exact basename; the discriminator is logAzure.txt, not the substring "azure".
// HUNT: HollowGraph on-disk config/log basename logAzure.txt referenced by a process // CONF: medium FP: medium COST: low | REQUIRES: ProcessRollup2 CommandLine // MITRE: T1552.001 Unsecured Credentials In Files; T1036.005 Masquerading (log-lookalike) // TUNING: exclude backup/log agents that legitimately touch files named *Azure*; require the exact basename and correlate the same aid with Q3/Q4 DNS to cloudlanecdn #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | CommandLine=/logAzure\.txt/i | groupBy([aid, ComputerName, UserName, FileName, ParentBaseFileName], function=[count(as=Hits), collect([CommandLine], limit=5)], limit=200) | sort(Hits, order=desc, limit=100)
Looks for: the config file being created on disk, matched by basename via FileName so there is no path slash inside the regex. FP: exclude AV quarantine / backup copy paths seen in TargetFileName.
// HUNT: HollowGraph config file logAzure.txt created on disk (BASENAME match, no path slash in regex) // CONF: medium FP: medium COST: low | REQUIRES: file-write telemetry — validate event availability in tenant // MITRE: T1552.001 Unsecured Credentials In Files // TUNING: match by BASENAME via FileName (^logAzure\.txt$) to avoid a slash inside /.../; exclude backup/quarantine dirs in TargetFileName #event_simpleName=/NewExecutableWritten|PeFileWritten|NewScriptWritten/ | FileName=/^logAzure\.txt$/i | table([@timestamp, aid, ComputerName, UserName, FileName, TargetFileName], limit=200) | sort(@timestamp, order=desc, limit=100)
Looks for: any DNS resolution for the attacker C2 domain or any subdomain. Atomic IOC — a hit is high confidence.
// HUNT: DNS resolution to HollowGraph C2 domain cloudlanecdn[.]com (and subdomains) // CONF: high FP: low COST: low | REQUIRES: DnsRequest // MITRE: T1071.004 Application Layer Protocol: DNS; T1568 Dynamic Resolution #event_simpleName=DnsRequest | DomainName=/(^|\.)cloudlanecdn\.com$/i | groupBy([aid, ComputerName, DomainName], function=[count(as=Queries), collect([ContextBaseFileName], limit=5)], limit=500) | sort(Queries, order=desc, limit=100)
Looks for: the credential-refresh tunnel — bursts of AAAA lookups to the *.p (length) and *.q (data) labels under the C2 domain. Volume threshold surfaces the beaconing cadence.
// HUNT: HollowGraph DNS-tunnel credential refresh — high-volume AAAA lookups to *.p/*.q.cloudlanecdn[.]com // CONF: high FP: low COST: medium | REQUIRES: DnsRequest with RequestType (AAAA=28) // MITRE: T1071.004 DNS; T1132 Data Encoding #event_simpleName=DnsRequest | DomainName=/\.(p|q)\.cloudlanecdn\.com$/i | RequestType=28 | groupBy([aid, ComputerName], function=[count(as=AAAAQueries), count(DomainName, distinct=true, as=UniqueLabels), collect([ContextBaseFileName], limit=5)], limit=500) | AAAAQueries >= 20 | sort(AAAAQueries, order=desc, limit=100)
Looks for: correlates the process making the C2 DNS lookups with its process record, exposing the .NET DLL host. FP: enterprise forwarders/proxies can appear as the resolving process — exclude by ContextBaseFileName.
// HUNT: process making DNS lookups to HollowGraph C2 (ties the loading process to cloudlanecdn traffic) // CONF: medium FP: medium COST: medium | REQUIRES: ProcessRollup2 + DnsRequest correlation // MITRE: T1071.004 DNS; T1218.011 Rundll32 (DLL host) // TUNING: exclude enterprise resolvers/proxies as the process; expect rundll32.exe/regsvr32.exe/dotnet.exe or an unusual signed host as the resolver #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2|DnsRequest/ | falconPID:=ContextProcessId | falconPID:=TargetProcessId | selfJoinFilter(field=[aid, falconPID], where=[{#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/}, {#event_simpleName=DnsRequest DomainName=/cloudlanecdn\.com$/i}]) | groupBy([aid, ComputerName, falconPID], function=collect([FileName, ImageFileName, CommandLine, DomainName], limit=8), limit=300)
Looks for: execution/load of any of the three reported HollowGraph DLL hashes. Atomic — any hit is a confirmed sample.
// HUNT: HollowGraph .NET DLL implant by SHA256 (execution / load) // CONF: high FP: low COST: low | REQUIRES: ProcessRollup2 / ImageHash // MITRE: T1204 User Execution; T1218 Signed/System Binary Proxy (DLL host) #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | in(SHA256HashData, values=["75e51774b8f79e5f256eaae639635f911b3e744d4774fd6068dd980255621509", "f3f3006f8304788251b153d53b305322b8acab0c66ec816b8d9f101bcc851da3", "b3d0f6e4e3be395fd7cf9e8101c89963d77216578cbb117a6ac9bc3564485eff"]) | table([@timestamp, aid, ComputerName, UserName, ImageFileName, SHA256HashData, CommandLine], limit=200)
Looks for: a signed DLL-host binary loading a DLL from a user-writable path — the NativeAOT loader shape. FP: rundll32/regsvr32 are heavily used legitimately; gate on user-writable path and correlate with Q3/Q4 on the same host.
// HUNT: suspicious .NET DLL host (rundll32/regsvr32/dllhost/dotnet) loading a DLL from a user-writable path // CONF: medium FP: high COST: medium | REQUIRES: ProcessRollup2; managed-assembly-load telemetry is limited — hunt the host process // MITRE: T1218.010 Regsvr32; T1218.011 Rundll32; T1573 Encrypted Channel (RSA/AES) // TUNING: require the DLL path to be user-writable (Users/ProgramData/AppData/Temp/Public) AND the same aid firing Q3/Q4; exclude System32 and Program Files signed hosts #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | in(FileName, values=["rundll32.exe", "regsvr32.exe", "dllhost.exe", "dotnet.exe"], ignoreCase=true) | CommandLine=/\.dll/i | ImageFileName=/\\(Users|ProgramData|AppData|Temp|Public)\\/i | groupBy([aid, ComputerName, UserName, FileName, ImageFileName], function=[count(as=Hits), collect([CommandLine], limit=5)], limit=300) | sort(Hits, order=desc, limit=100)
Custom IOA Recommendations
IOA-1 · DLL host resolving the C2 domain
Rule group: Covert C2 · Type: Network Connection / DNS Request · Action: Detect (promote to Block only after FP baseline) · Severity: High · MITRE: T1071.004.
- Domain (PCRE):
(^|\.)cloudlanecdn\.com$ - Exclusions: none needed — atomic C2 domain.
- Rationale: real-time detect of any host touching the DNS channel; complements Q3/Q4 retro hunts.
IOA-2 · rundll32/regsvr32 loading a DLL from a user-writable path
Rule group: LOLBIN Abuse · Type: Process Creation · Action: Detect · Severity: Medium · MITRE: T1218.010/.011.
- Image Filename (PCRE):
.*\\(rundll32|regsvr32)\.exe$ - Command Line (PCRE):
.*\\(Users|ProgramData|AppData|Temp|Public)\\.*\.dll.* - FP tuning: heavy legitimate use — keep in Detect; pilot on a canary group before any Block promotion.
Do not promote either IOA to Block until it has run in Detect for 14+ days with a measured FP rate under 5%. IOA-2 in particular has a high legitimate-use baseline.
Machine-Readable IOC Appendix
Quick-copy blocks for import and hunting. Atomic indicators (domain + hashes) trace to the saved Group-IB snapshot; pattern indicators ship as behavioral signatures, not blocks.
type,value,action,severity,expiration,description,tags domain,cloudlanecdn[.]com,detect,high,2027-01-21,HollowGraph DNS C2 and credential-refresh tunnel,campaign:HollowGraph sha256,75e51774b8f79e5f256eaae639635f911b3e744d4774fd6068dd980255621509,prevent,critical,2027-07-21,HollowGraph .NET NativeAOT DLL implant,campaign:HollowGraph sha256,f3f3006f8304788251b153d53b305322b8acab0c66ec816b8d9f101bcc851da3,prevent,critical,2027-07-21,HollowGraph .NET NativeAOT DLL implant,campaign:HollowGraph sha256,b3d0f6e4e3be395fd7cf9e8101c89963d77216578cbb117a6ac9bc3564485eff,prevent,critical,2027-07-21,HollowGraph .NET NativeAOT DLL implant,campaign:HollowGraph # attribution pivot only (do NOT block) - related Lyceum 2025 .NET backdoor: # 315bdba98c6fe863d39f6afccc727e17d5aea63bf21259444fa988cae56d61c1
logAzure.txt on-disk config storing tenantId/clientId/clientSecret + RSA keys
File{n}.txt encrypted calendar-event attachment naming (File0.txt, File1.txt, ...)
2050-05-13 far-future calendar event date for planted tasking / exfil events
Subject Event ID: {7-char taskID} GET / tasking event subject pattern
Subject Boss{..}ID{..} SEND / exfil event subject pattern
Subject bare GUID operator-recognition rename pattern
*.p.cloudlanecdn[.]com DNS-tunnel length-query labels (AAAA)
*.q.cloudlanecdn[.]com DNS-tunnel data-query labels (AAAA, 14 bytes/record)
command grammar: get / send / 003(debug) Cavern-style tasking verbs
HollowGraph .NET NativeAOT DLL implant (this campaign) Cavern modular espionage framework - HIGH-confidence code overlap Cavern Manticore Iranian MOIS-linked cluster associated with Cavern (per THN) Lyceum / OilRig Iranian-nexus subgroup - LOW-confidence overlap only, NOT attributed
- Enumerate app registrations with Calendars.ReadWrite / Mail.Read / Mail.ReadWrite APPLICATION permissions
- Review client-secret creation (Add service principal credentials) in the last 90 days
- Hunt calendar items dated 2050 across all mailboxes (Graph: /users/{id}/calendarView)
- Revoke + rotate any tenantId/clientId/clientSecret found in logAzure.txt on affected hosts
- Conditional Access: block non-interactive client-credential flows from unmanaged IPs
- Alert on app-only Graph calendar writes (Calendars.ReadWrite used by a daemon app)
Detection Validation Gates
| Gate | Check | Pass criteria |
|---|---|---|
| Telemetry ready | DnsRequest + ProcessRollup2 flowing; M365 UAL + Entra SPN sign-in ingested | Non-empty samples in a 1h window for each source |
| Benign baseline | Run Q5/Q7 read-only for 48h; classify hits | Legit DLL-host resolvers enumerated and excluded |
| Positive test | On an isolated host, nslookup a benign *.p.cloudlanecdn.com-shaped label against a lab resolver; write a benign file named logAzure.txt | Q1/Q2 and Q3/Q4 return the test host within 2 min |
| Promotion | IOA FP rate measured on canary group | Under 5% for 14 days before any Block promotion |
Validate with benign payloads on isolated/canary endpoints only — never real malware, never production.
Hardening — Tiered
- Audit and lock down application permissions: revoke any app registration holding
Calendars.ReadWrite/Mail.Read(Write)application scopes that isn't explicitly required. MITRE M1018; CIS M365 Foundations (app registration governance). - Rotate any tenant/client secrets that may be exposed; enforce short client-secret lifetimes and prefer certificate credentials. MITRE M1027.
- Block resolution of
cloudlanecdn[.]comat the resolver/proxy and add the 3 SHA256 to Falcon prevent. MITRE M1031.
- Conditional Access: block or challenge non-interactive client-credential Graph flows from unmanaged / unexpected IP ranges. MITRE M1032 / M1037.
- Enable and ingest M365 Unified Audit Log + Entra service-principal sign-in logs into the SIEM if not already. MITRE M1047.
- App-consent governance: require admin approval for new application permission grants. MITRE M1018.
- Constrain DNS egress to approved resolvers; alert on high-volume AAAA to low-prevalence domains. MITRE M1037.
- Application control (WDAC / AppLocker) to constrain rundll32/regsvr32 loading DLLs from user-writable paths. MITRE M1038.
- Least-privilege review of all daemon/app-only identities touching Exchange Online. MITRE M1018.
Deployable Playbooks
P1 · Enumerate risky Graph application permissions (Microsoft Graph PowerShell)
Connect-MgGraph -Scopes "Application.Read.All","AuditLog.Read.All"
$risky = "Calendars.ReadWrite","Mail.Read","Mail.ReadWrite"
Get-MgServicePrincipal -All | ForEach-Object {
$sp = $_
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id |
Where-Object { $risky -contains $_.AppRoleId } |
Select-Object @{n='App';e={$sp.DisplayName}}, AppRoleId, ResourceDisplayName
}
P2 · Hunt 2050-dated calendar events across mailboxes (Graph)
# For each in-scope mailbox, pull events in the 2050 window and flag suspicious subjects
# GET /users/{id}/calendarView?startDateTime=2050-01-01T00:00:00Z&endDateTime=2050-12-31T23:59:59Z
# Review Subject for: GUID-only, "Event ID:", "Boss..ID..", and any File{n}.txt attachment
P3 · Sinkhole / block the DNS channel
# Resolver RPZ / proxy block # cloudlanecdn.com CNAME sinkhole.internal. ; block + log all *.cloudlanecdn.com # Alert on any client that attempted resolution prior to the block taking effect
Deploying via your RMM: the snippets above are raw PowerShell / config. Hand them to your RMM deployment tooling for admin checks, error handling, and custom-field writeback for compliance tracking.
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Identify | Run Q1–Q7 + S7 hunts; scope affected hosts and the compromised mailbox/app identity | SOC | aid list, app IDs, calendar event IDs |
| Contain | Network-isolate implicated hosts; disable the abused app registration; block cloudlanecdn[.]com | IR + Identity | Isolation + app-disable timestamps |
| Eradicate | Remove implant DLL + logAzure.txt; rotate tenant/client secrets; delete planted 2050 events | IR | File hashes, rotated-secret log |
| Recover | Restore least-privilege app scopes; re-enable hosts after clean scan | IT | Clean-scan reports |
| Lessons | App-consent governance + AAAA DNS monitoring gaps closed | Security Eng | Config change tickets |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1552.001 | logAzure.txt config/creds on disk | Q1, Q2 | — | Good |
| T1071.004 | DNS AAAA C2 to cloudlanecdn | Q3, Q4 | IOA-1 | Good |
| T1218.010/.011 | .NET DLL host / loader | Q5, Q7 | IOA-2 | Partial |
| Known samples | SHA256 execution/load | Q6 | — | Good |
| T1102.002 | M365 calendar dead drop (Graph) | S7-A (audit) | — | Partial |
| T1078.004 | Client-credential app abuse | S7-B (audit) | — | Partial |
| T1573.001/.002 | RSA/AES managed crypto load | Q7 (proxy) | — | GAP |
Coverage: 6 of 7 behaviors have at least partial detection. The residual GAP is direct observation of managed RSA/AES assembly loading — Falcon has limited managed-assembly-load telemetry, so it is hunted indirectly via the DLL-host process (Q7). Validation gates (section 11) must pass before these are relied on operationally.
Hunt Summary Ticket
TITLE: HollowGraph - .NET DLL implant using M365 calendar dead drop + DNS AAAA C2
SEVERITY: Critical (targeted espionage; ~12 hosts, ~3 active)
SCOPE: Windows endpoints (.NET) + M365/Entra tenant; egress DNS
HYPOTHESIS: Implant drops logAzure.txt, beacons AAAA to cloudlanecdn[.]com, and
drives Graph calendar events dated 2050 with File{n}.txt attachments.
QUERIES: Q1-Q2 logAzure.txt | Q3-Q4 cloudlanecdn DNS/AAAA | Q5 loader tie
Q6 SHA256 | Q7 DLL host | S7-A/B M365 + Entra audit
DO FIRST: Run Q3 (DNS) fleet-wide and S7-A (2050 calendar events) in the tenant.
A host on both = high-confidence confirmation.
FINDINGS: (analyst completes - queries must be run in Falcon/M365; not auto-run)
GAPS: Direct RSA/AES assembly-load telemetry (hunted via Q7 host proxy)
ACTIONS: Block domain + 3 SHA256; audit Graph app permissions; rotate secrets
OWNER: SOC / IR / Identity
VERSION: v0.1 - 2026-07-21
Changelog
References
| Tier | Source | Used for | Accessed |
|---|---|---|---|
| T1 | Group-IB — HOLLOWGRAPH | Primary IOCs, TTPs, attribution | 2026-07-21 |
| T2 | The Hacker News | Corroboration, Cavern Manticore note | 2026-07-21 |
| T2 | BleepingComputer | AAAA payload detail, subject split | 2026-07-21 |
| T2 | The Register | Narrative corroboration | 2026-07-21 |