# dwmcore.dll Patch Diff — CVE-2023-36033

| | |
|---|---|
| Binary | dwmcore.dll (DWM Core Library — Direct Composition) |
| Pre-patch version | 10.0.22621.2506 |
| Post-patch version | 10.0.22621.2715 |
| KB | KB5032190 (Windows 11 22H2) |
| CVE | CVE-2023-36033 — Use of User-Controlled Pointer (CWE-822), Elevation of Privilege, CVSS 7.8 |
| Exploited | **Yes — exploited in the wild** |
| Diff tool | ghidriff (Ghidra VersionTrackingDiff engine) |
| Functions changed | 8 with code changes (out of 28,251 total, 99.4% avg similarity) |
| Functions added | 0 |
| Independent analysis | [Google Project Zero — 0-day RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36033.html) by Maddie Stone |

## Summary

The fix closes a **use of user-controlled pointer** vulnerability in
the Windows Desktop Window Manager (DWM) Core Library that allows an
unprivileged local attacker to escalate privileges to SYSTEM.

`CKeyframeAnimation::GetSampledStartingValue` reads a `CPathData`
pointer from a **shared memory section** (`CSharedSection`) for
animations of type `DCOMPOSITION_EXPRESSION_TYPE_PATH` (0xb). The shared
section is mapped into the calling process's address space, so an
attacker can replace the pointer with an arbitrary value. This pointer
is then passed to `ComPtr<CPathData>::operator=`, which calls
`AddRef`/`Release` on it — giving the attacker a controlled virtual
function call in a privileged DWM context.

The fix introduces a **new 8-byte member field** in `CKeyframeAnimation`
at offset `this+0x1c8` (a `wil::com_ptr_t<CPathData>`) that stores the
`CPathData` pointer privately. `GetSampledStartingValue` now reads from
this member field instead of the shared section, eliminating the
user-controlled pointer dereference. The shared section cache size for
type 0xb was correspondingly reduced from 0x10 to 0x8 bytes.

**Confidence:** high. The ghidriff output matches the Project Zero RCA
exactly — the new member field, the source change in
`GetSampledStartingValue`, and the object size growth are all
confirmed.

## Product background

`dwmcore.dll` is the DWM Core Library, the engine behind Windows'
desktop composition. It implements the Direct Composition animation
system, which uses `CKeyframeAnimation` objects to drive visual property
animations. Animation parameters are passed through shared memory
sections (`CSharedSection`) mapped between the application process and
DWM, allowing usermode applications to set animation data that DWM
reads during composition.

The `CKeyframeAnimation` class stores its shared section pointer at
`this+0x178` and the data pointer at `this+0x180`. For PATH-type
animations (expression type 0xb), the animation's starting value
includes a `CPathData*` pointer that was previously read from the shared
section data at offset +8 (i.e., `piVar2 + 2` where `piVar2` points to
the shared section data).

## Functions changed

### GetSampledStartingValue (the vulnerability)

| | |
|---|---|
| Change type | code, length |
| Similarity | 0.98 (b_ratio) |
| Fix pattern | User-controlled pointer replaced with private member field |

Before (pre-patch — vulnerable):

```c
// For expression type 0xb (PATH):
piVar2 = *(int **)(this + 0x180);  // shared section data pointer
if (*piVar2 == 0xb) {
    pCVar3 = *(CPathData **)(piVar2 + 2);  // READ FROM SHARED SECTION
    *(uint *)(param_1 + 0x48) = 0xb;
    param_1[0x4c] = 1;
    ComPtr<CPathData>::operator=(param_1 + 0x40, pCVar3);  // AddRef on attacker-controlled pointer
}
```

The `CPathData*` at `piVar2 + 2` comes from the shared section, which
is writable by the calling process. An attacker can:

1. Create a `CKeyframeAnimation` with expression type PATH (0xb)
2. Map the shared section and replace the `CPathData*` at offset +8
   with a pointer to a fake vtable
3. When DWM calls `GetSampledStartingValue`, it reads the attacker's
   pointer and passes it to `ComPtr::operator=`, which calls
   `AddRef` (vtable offset +8) — a controlled call primitive

After (post-patch — fixed):

```c
// For expression type 0xb (PATH):
if (*piVar2 == 0xb) {
    pCVar3 = *(CPathData **)(this + 0x1c8);  // READ FROM PRIVATE MEMBER FIELD
    *(uint *)(param_1 + 0x48) = 0xb;
    param_1[0x4c] = 1;
    ComPtr<CPathData>::operator=(param_1 + 0x40, pCVar3);
}
```

The pointer is now read from `this+0x1c8`, a private member field on the
`CKeyframeAnimation` object in kernel pool — not from the user-writable
shared section.

### CKeyframeAnimation::CKeyframeAnimation (constructor — new member)

| | |
|---|---|
| Change type | code, length, address |
| Similarity | 0.98 (b_ratio) |
| Length | 145 → 152 bytes |

The constructor now initializes a new pointer field. The object grew by
8 bytes — all member offsets above `this+0x1c8` shifted by +8:

- `this+0x1c8`: now holds `wil::com_ptr_t<CPathData>` (was start of vector)
- `this+0x1d0, 0x1d8, 0x1e0`: `std::vector<ComPtr<CPathData>>` (shifted from 0x1c8, 0x1d0, 0x1d8)
- `this+0x23c` → `this+0x244`: animation state flags
- `this+0x208` → `this+0x210`: speed multiplier
- `this+0x228` → `this+0x230`: play state

### CKeyframeAnimation::~CKeyframeAnimation (destructor — releases new member)

| | |
|---|---|
| Change type | code, refcount, length, called |
| Similarity | 0.60 (b_ratio) |
| Length | 502 → 470 bytes |

The destructor now calls `wil::com_ptr_t<...>::~com_ptr_t()` on
`this+0x1c8` to release the privately held `CPathData`. All offset
references shifted by +8, consistent with the object growth.

### GetCacheSizeForType (reduced shared section cache)

| | |
|---|---|
| Change type | code, length, address |
| Similarity | 0.97 (b_ratio) |

For expression type 0xb (PATH), the cache size was reduced from
**0x10 to 0x8** bytes. Previously the cache stored both the type
indicator and the `CPathData*` pointer (16 bytes). Now the pointer is
in the private member field, so only the type indicator needs caching
(8 bytes).

### CalculateValueWorker (offset shifts only)

| | |
|---|---|
| Change type | code, length, address, called |
| Similarity | 0.97 (b_ratio) |
| Length | 2059 → 2199 bytes |

All changes are mechanical offset shifts (+8) from the object growth.
No logic changes — the vulnerability is not in this function, but it
references many of the shifted member fields (state flags, play state,
speed multiplier, delay counters).

### ComPtr\<CPathData\>::operator= → wil::com_ptr_t\<CPathData\>::operator=

| | |
|---|---|
| Change type | code, fullname, refcount, length, sig, address, calling, called, parent |
| Similarity | 0.76 (b_ratio) |

The `operator=` used by the new member field was migrated from
`Microsoft::WRL::ComPtr` to `wil::com_ptr_t`. The WIL version uses
**XFG CFG dispatch** (`_guard_xfg_dispatch_icall_nop`) for virtual calls
instead of direct `InternalAddRef`/`InternalRelease` — adding an extra
layer of control-flow integrity protection.

Critically, the calling-function list shows
`CKeyframeAnimation::SampleStartingValue` was **removed** from the old
`ComPtr::operator=` callers — confirming that `SampleStartingValue` now
uses the new WIL-based smart pointer at `this+0x1c8` instead.

### Other functions (FUN_180117704, FeatureImpl — mechanical changes)

Two additional functions show code changes:

- `FUN_180117704` (b_ratio 0.91): separated code fragment of
  `CalculateValueWorker`; same +8 offset shifts
- `FeatureImpl<...CompositionSwapchainCrossBindingScanout>::GetCurrentFeatureEnabledState`
  (b_ratio 0.81): unrelated WIL CFR feature state calculation change;
  not part of the security fix

## Exploitation

This vulnerability was **exploited in the wild** and patched in
November 2023 (Patch Tuesday). The P0 RCA describes the attack surface:

1. An unprivileged process creates a DirectComposition animation with
   expression type `DCOMPOSITION_EXPRESSION_TYPE_PATH` (0xb)
2. The shared section backing the animation data is mapped into the
   process's address space
3. The attacker overwrites the `CPathData*` pointer in the shared
   section with a pointer to a fake object containing a crafted vtable
4. When DWM calls `GetSampledStartingValue` → `ComPtr::operator=`, it
   calls `AddRef` (vtable+8) on the attacker's fake object
5. This gives a controlled call primitive in the context of the DWM
   process (running as SYSTEM)

The shared section is particularly attractive for exploitation because
its contents are directly writable by the attacking process, and the
pointer is used without any validation or integrity check.

---

<sub>Source: ghidriff diff of dwmcore-2023-10.dll (10.0.22621.2506,
pre-patch) vs dwmcore-2023-11.dll (10.0.22621.2715, post-patch) —
[download pre](/data/patch_diffs/binaries/dwmcore-2023-10.dll) /
[download post](/data/patch_diffs/binaries/dwmcore-2023-11.dll).
Reference: [Google Project Zero RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36033.html).</sub>
