# CVE-2022-41033 — Windows COM+ Event System Service `es.dll` Type Confusion in `InMemoryRegRow::PutPropertyBag` (Exploited In-The-Wild)

---

## Summary

| | |
|---|---|
| **Product** | Microsoft Windows — `es.dll` (COM+ Event System service) |
| **CVE ID** | CVE-2022-41033 |
| **Impact** | Elevation of Privilege (LOCAL SERVICE + SeImpersonate → SYSTEM) |
| **MSRC severity** | Important |
| **CWE** | CWE-843: Type Confusion (access of resource using incompatible type) |
| **Exploited ITW** | **Yes** — Windows in-the-wild 0-day (Project Zero RCA) |
| **Patch Date** | October 11, 2022 |
| **Pre-patch binary** | `es.dll` 2001.12.10941.16384 (Sep 2022) — SHA256 `a8240c2690b6ef5b8792e8f2baec6f282445073efea17d275d16f7a5079daf56` |
| **Post-patch binary** | `es.dll` 2001.12.10941.16384 (Oct 11 2022 fix) — SHA256 `eb6fbbefd6b16ef0cd80356ce1ae6af87478bbabed8b09bf29356a138782bb5e` |
| **Fix gating** | None — added validation function |

---

## Product Description

`es.dll` implements the **COM+ Event System** service. The COM class
`EventSystemTier2` (CLSID `1be1f766-5536-11d1-b726-00c04fb926af`) is accessible to
**any normal user** (but not sandboxed processes) and auto-starts the service.
Calling `IEventSystemTier2::CreateSubscription(name, TRUE, ...)` (transient →
in-memory property bag) yields an `IEventSubscriptionTier2`; its `Store` method
reaches the vulnerable code. The service runs as **LOCAL SERVICE** with
**SeImpersonatePrivilege** — administrator-equivalent — so code execution in the
process is a full local privilege escalation.

---

## Vulnerability Summary

`InMemoryRegRow::PutPropertyBag(PROPVARIANT& Names, PROPVARIANT& Values)` builds
an `IPropertyBag` from two `PROPVARIANT`s expected to be a vector of strings and a
vector of `PROPVARIANT`s — but **never checks the `vt` field**:

```c
HRESULT InMemoryRegRow::PutPropertyBag(PROPVARIANT& Names, PROPVARIANT& Values) {
  this->PropertyBag = CreatePropertyBag(Values.capropvar.cElems);
  for (int i = 0; i < Values.capropvar.cElems; ++i) {
    LPWSTR name = WStringCopy(Names.calpwstr.pElems[i]);
    PROPVARIANT var;
    PropVariantCopy(&var, &Values.capropvar.pElems[i]);   // *** derefs attacker-shaped array ***
    this->PropertyBag->Add(name, var);
  }
}
```

Because `PROPVARIANT` is a union, an attacker passes a **`VT_BLOB`** whose fields
alias `CAPROPVARIANT` (`BLOB.cbSize ↔ capropvar.cElems`, `BLOB.pBlobData ↔
capropvar.pElems`). The COM runtime validates the *blob length* but not that the
bytes are real `PROPVARIANT`s, so the attacker supplies an arbitrary byte array
interpreted as a `PROPVARIANT` array — e.g. a fake `VT_UNKNOWN` holding a
**bogus pointer**. `PropVariantCopy` then dereferences that pointer to read its
vtable and call `IUnknown::AddRef` — a type confusion giving a controlled pointer
dereference / virtual call (P0's crash: `mov rax,[rcx]` with `rcx = 0x12345678`).

---

## Prerequisites and Constraints

- Local, **any normal (non-sandboxed) user** — the `EventSystemTier2` COM class
  grants Everyone/INTERACTIVE access and auto-starts the service.
- Reach the sink via `IEventSystemTier2::CreateSubscription(name, TRUE, ...)` →
  `IEventSubscriptionTier2::Store(...)`, passing a `VT_BLOB` in the `Values`
  vector that fakes a `VT_UNKNOWN` variant with an attacker pointer.
- Exploited in the wild; the controlled dereference/virtual-call is turned into
  code execution in the LOCAL SERVICE process, then SeImpersonate → SYSTEM.

---

## Vulnerability Details

### Call Chain

```
Normal user:
  CoCreateInstance(EventSystemTier2) -> IEventSystemTier2::CreateSubscription(name, TRUE) -> IEventSubscriptionTier2
    -> IEventSubscriptionTier2::Store(...)
      -> es!CSubscription2::Store
        -> es!InMemoryRegRow::PutPropertyBag   [*** type confusion ***]
             PropVariantCopy(fake VT_UNKNOWN) -> deref attacker pointer -> AddRef vcall
```

### Root Cause

`PutPropertyBag` trusts the `PROPVARIANT` union shape without checking `vt`. A
`VT_BLOB` aliases the `capropvar` array pointer, so attacker bytes are treated as
a `PROPVARIANT[]` and a fake `VT_UNKNOWN` pointer is dereferenced (CWE-843).

### The patch (confirmed — diff)

The October build adds **`ValidatePropertyBag`** and calls it first in both
`InMemoryRegRow::PutPropertyBag` and `RegistryRegRow::PutPropertyBag`:

```c
HRESULT ValidatePropertyBag(const PROPVARIANT& Names, const PROPVARIANT& Values) {
  if (Names.vt  == (VT_VECTOR | VT_LPWSTR) &&
      Values.vt == (VT_VECTOR | VT_VARIANT) &&
      Names.calpwstr.cElems == Values.capropvar.cElems)
    return S_OK;
  return E_INVALIDARG;                     // wrong type/size -> whole operation fails
}
```

By requiring `Names` to be a `VT_VECTOR|VT_LPWSTR`, `Values` to be a
`VT_VECTOR|VT_VARIANT`, and the element counts to match, a `VT_BLOB` (or any
mismatched type) is rejected before any array is dereferenced — killing the
aliasing. Our diff shows `ValidatePropertyBag` and a helper `IsEmptyOrBSTR` added,
`InMemoryRegRow::PutPropertyBag` reworked (68% match) to call it, and
`RegistryRegRow::PutPropertyBag` (99% match) gaining the same call.

### Patch Completeness Assessment

Unconditional fix — a validation function is added and invoked before the sink
(2022, pre-CFR-gating). P0 notes `RegistryRegRow::PutPropertyBag` may retain a
lesser info-disclosure concern, but the exploitable type confusion is closed.

---

## Detection Guidance

**Behavioural.** A non-sandboxed process instantiating `EventSystemTier2`
(CLSID `1be1f766-…`) and calling `CreateSubscription(transient=TRUE)` then `Store`
with a `Values` vector containing a `VT_BLOB` in place of `VT_VECTOR|VT_VARIANT` —
anomalous; legitimate callers pass typed variant vectors.

**Crash signature.** Access violations in `es!InMemoryRegRow::PutPropertyBag` /
`OLEAUT32!VariantCopy` / `combase!PropVariantCopy` dereferencing a low/bogus
pointer, in the COM+ Event System (`svchost`/`LOCAL SERVICE`) process.

---

## References

- Google Project Zero — *CVE-2022-41033: Type confusion in Windows COM+ Event
  System Service* (James Forshaw), 0-days In-The-Wild RCA.
- MSRC advisory — CVE-2022-41033
- Full binary diff: `/data/patch_diffs/es_dll-cve-2022-41033-ghidriff.md`
