# CVE-2023-36033 — Windows DWM Core Library (`dwmcore.dll`) EoP via Shared-Section CPathData Pointer UAF

---

## Summary

| **Product**           | Microsoft Windows — `dwmcore.dll` (Desktop Window Manager Core Library) |
|-----------------------|-------------------------------------------------------------------------|
| **Vendor**            | Microsoft Corporation |
| **Severity**          | Important (CVSS 7.8) |
| **Affected Versions** | Windows 10 21H2+, Windows 11 21H2/22H2, Windows Server 2022+ |
| **Tested Version**    | Windows 10.0.22621.2506 (pre-patch) vs 10.0.22621.2715 (post-patch) |
| **Impact**            | Elevation of Privilege — Controlled virtual call in DWM (SYSTEM context) |
| **CVE ID**            | CVE-2023-36033 |
| **CWE**               | CWE-416: Use After Free / CWE-843: Type Confusion |
| **PoC Available**     | Yes (trigger PoC — demonstrates reaching the vulnerable code path) |
| **Exploit Available** | Exploited in the wild (Microsoft advisory: "Exploitation Detected") |
| **Patch Available**   | Yes |
| **Patch Date**        | November 2023 — KB5032190 |
| **Exploitation Maturity** | Exploitation Detected |

---

## CVSS 3.1 Scoring

**Base Score:** 7.8 (HIGH)
**Vector String:** `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`

| **Metric** | **Value** | **Rationale** |
|---|---|---|
| **Attack Vector (AV)** | Local | Requires local session; shared section is process-local |
| **Attack Complexity (AC)** | Low | Predictable shared memory layout; no race conditions |
| **Privileges Required (PR)** | Low | Standard user can create DWM animations |
| **User Interaction (UI)** | None | No interaction from any other user required |
| **Confidentiality (C)** | High | Kernel/DWM memory read via controlled virtual call |
| **Integrity (I)** | High | Arbitrary code execution in DWM (SYSTEM) context |
| **Availability (A)** | High | DWM crash on failed exploitation |

---

## Product Description

`dwmcore.dll` is the Desktop Window Manager's core rendering library, running in the privileged `dwm.exe` process (SYSTEM context on Windows 10/11). DWM uses a shared-memory section between each application and the DWM process to exchange animation parameters, property values, and rendering state. Applications write animation data (keyframe values, expression types, resource pointers) into this shared section, and DWM reads them during composition.

The `CKeyframeAnimation` class manages keyframed animations. Each animation type (float, color, point, path, matrix, etc.) stores its sampled starting value in a cache region within the shared section. For PATH animations (type `0xb`), the cache stored both a type tag and a raw `CPathData*` pointer — a live COM object pointer — in the shared section that the client process can write to.

---

## Vulnerability Summary

`CKeyframeAnimation::GetSampledStartingValue` reads a `CPathData*` pointer directly from the shared section cache at `*(CPathData **)(piVar2 + 2)` (where `piVar2 = *(int **)(this + 0x180)` is the shared-section cache pointer). For PATH (type `0xb`) animations, this cache region is 0x10 bytes: 4 bytes for the type field, 4 bytes padding, and 8 bytes for the `CPathData*` pointer.

Because the shared section is mapped read-write into the client application's address space, an attacker can **overwrite the CPathData pointer** with an arbitrary value. When DWM calls `GetSampledStartingValue` during the next composition frame, it reads the attacker-controlled pointer and passes it to `Microsoft::WRL::ComPtr<CPathData>::operator=`, which calls `AddRef()` and `Release()` — virtual method dispatches on the fake object. This gives the attacker a **controlled virtual call** in the SYSTEM-context DWM process.

---

## Prerequisites and Constraints

- Local authenticated session (standard user; no administrative rights required)
- DWM must be running (it always is on Windows 10/11 desktop editions)
- Attacker creates a DWM animation with PATH type (0xb) via DirectComposition APIs
- The shared section is accessible to the creating process — no inter-process access needed
- Attacker must know or leak the shared section base address (ASLR bypass may be needed)
- The virtual call target must satisfy Control Flow Guard (CFG) — valid call targets only
- Exploited in the wild prior to the November 2023 patch

---

## Vulnerability Details

### Call Chain (Ghidra MCP–Verified)

The complete call chain from userspace to the vulnerable dereference, verified via Ghidra MCP decompilation against the **pre-patch** binary (`dwmcore.dll`, 10.0.22621.2506):

```
User mode:
  DCompositionCreateDevice()          → creates DirectComposition device
  IDCompositionDevice::CreateAnimation() → creates CKeyframeAnimation
  Set animation type to PATH (0xb)    → populates shared section cache (0x10 bytes)
  Overwrite CPathData* at cache+0x08  → attacker writes fake pointer into shared memory
                                        ↓
DWM process (SYSTEM):
  Composition frame tick
    → CBaseExpression::Calculate()                    [expression evaluation]
      → CKeyframeAnimation::GetSampledStartingValue() [*** VULNERABLE ***]
        → reads CPathData* from shared section cache (piVar2 + 2)
        → Microsoft::WRL::ComPtr<CPathData>::operator=(param_1+0x40, pCVar3)
          → pCVar3->AddRef()    ← controlled virtual call on fake object
          → old->Release()
```

### GetSampledStartingValue (Pre-Patch — VULNERABLE)

```c
// CKeyframeAnimation::GetSampledStartingValue @ 0x1800ea354
long CKeyframeAnimation::GetSampledStartingValue(
        CKeyframeAnimation *this, CExpressionValue *param_1)
{
    int *piVar2 = *(int **)(this + 0x180);  // shared section cache pointer

    if (*piVar2 == 0) {
        // Cache not populated — call SampleStartingValue to fill it
        lVar5 = SampleStartingValue(this);
        if (lVar5 < 0) goto error;
    }

    int type = *piVar2;  // read type from shared section

    if (type == 0xb) {  // PATH animation
        // *** VULNERABLE: reads CPathData* from shared section ***
        CPathData *pCVar3 = *(CPathData **)(piVar2 + 2);  // offset +8

        *(uint *)(param_1 + 0x48) = 0xb;
        param_1[0x4c] = 1;

        // Calls AddRef/Release on attacker-controlled pointer!
        Microsoft::WRL::ComPtr<CPathData>::operator=(
            (ComPtr<CPathData> *)(param_1 + 0x40), pCVar3);
    }
    else if (type == 0x11) { ... }  // FLOAT
    else if (type == 0x12) { ... }  // INT
    // ... other types (0x23, 0x2a, 0x34, 0x45, 0x46, 0x47)
    // All other types store plain values, not pointers

    return 0;
}
```

**The bug:** For type `0xb` (PATH), the function reads a raw COM object pointer from the shared section at `piVar2 + 2`. The shared section is mapped R/W into the client process, so the attacker can overwrite this 8-byte pointer with any value. When `ComPtr::operator=` calls `AddRef()` on the fake pointer, it dispatches through the vtable — giving a controlled virtual call.

### GetCacheSizeForType (Pre-Patch)

```c
// GetCacheSizeForType @ 0x1800f7cbc
uint GetCacheSizeForType(DCOMPOSITION_EXPRESSION_TYPE type) {
    switch (type) {
        case 0xb:  return 0x10;  // PATH: 4 (type) + 4 (pad) + 8 (CPathData*)
        case 0x11: return 0x0c;  // FLOAT
        case 0x12: return 0x0c;  // INT
        case 0x23: return 0x10;  // POINT
        case 0x2a: return 0x0c;  // SIZE
        case 0x34: return 0x14;  // COLOR
        case 0x45:               // MATRIX3x2
        case 0x46:               // MATRIX4x4
        case 0x47: return 0x18;  // QUATERNION
        default:   return 0;
    }
}
```

Post-patch: type `0xb` returns `0x08` instead of `0x10` — the 8-byte pointer slot is removed from the shared section cache.

### SampleStartingValue (Populates the Cache)

```c
// CKeyframeAnimation::SampleStartingValue @ 0x1800e1700
long CKeyframeAnimation::SampleStartingValue(CKeyframeAnimation *this)
{
    int *piVar2 = *(int **)(this + 0x180);  // shared section cache

    if (*piVar2 == 0) {
        // Resolve target, get current value
        CResource *target = CBaseExpression::ResolveTargetNoRef(this);
        // ... get value into local_68/local_28 ...

        int type = *(int *)(this + 0x98);
        *piVar2 = type;  // write type into shared section

        if (type == 0xb) {
            // Write CPathData* into shared section cache at piVar2+2
            Microsoft::WRL::ComPtr<CPathData>::operator=(
                (ComPtr<CPathData> *)(piVar2 + 2), local_28);
            // *** This stores a live kernel pointer in writable shared memory ***
        }
        // ... other types store plain values ...
    }
    return 0;
}
```

### Constructor (Pre-Patch)

```c
// CKeyframeAnimation::CKeyframeAnimation @ 0x180088434
CKeyframeAnimation::CKeyframeAnimation(CKeyframeAnimation *this, CComposition *param_1)
{
    CBaseExpression::CBaseExpression((CBaseExpression *)this, param_1);
    *(void ***)this = &_vftable_;
    *(uint64 *)(this + 0x150) = 0;
    *(uint64 *)(this + 0x158) = 0;
    *(uint64 *)(this + 0x188) = 0;
    // ... array init at +0x190/0x198/0x1a0 ...
    *(uint64 *)(this + 0x1c8) = 0;  // CPathData vector begin
    *(uint64 *)(this + 0x1d0) = 0;  // CPathData vector end
    *(uint64 *)(this + 0x1d8) = 0;  // CPathData vector capacity
    // ... float constants at +0x220, +0x208 ...
}
```

Post-patch: adds a `wil::com_ptr_t<CPathData>` member (private, not in shared section) to store the CPathData pointer safely. Object grows by 8 bytes.

### Destructor (Pre-Patch)

```c
// CKeyframeAnimation::~CKeyframeAnimation @ 0x180032de4
void CKeyframeAnimation::~CKeyframeAnimation(CKeyframeAnimation *this)
{
    // ... flag cleanup, vtable set ...
    // Release DynArray elements via XFG dispatch
    // ...

    // Release shared section
    if (*(CSharedSection **)(this + 0x178) == NULL) {
        if (*(void **)(this + 0x180) != NULL) {
            DefaultHeap::Free(*(void **)(this + 0x180));
        }
    } else {
        ReleaseInterface<CSharedSection>((CSharedSection **)(this + 0x178));
    }

    // Destroy CPathData ComPtr vector at this+0x1c8
    if (*(ComPtr<CPathData> **)(this + 0x1c8) != NULL) {
        std::_Destroy_range(*(this + 0x1c8), *(this + 0x1d0), ...);
        std::_Deallocate(*(this + 0x1c8), ...);
    }

    // ... parent destructor ...
}
```

Post-patch: the destructor gains `~com_ptr_t()` call on the new private CPathData member to properly release the private copy.

---

## Exploitation Scenario

### Step 1 — Create a DirectComposition Animation

```cpp
// Create a DComp device and animation
IDCompositionDevice *device;
DCompositionCreateDevice(dxgiDevice, IID_PPV_ARGS(&device));

IDCompositionAnimation *animation;
device->CreateAnimation(&animation);
// Set keyframes that use PATH type (0xb)
```

### Step 2 — Locate the Shared Section

The DWM shared section is mapped into the client process's address space. Its base address can be found via:
- `NtQueryVirtualMemory` scanning for SEC_COMMIT mappings of the right size
- Process information APIs
- Leaking from another DWM info-disclosure bug

### Step 3 — Overwrite the CPathData Pointer

```cpp
// The shared section cache for this animation is at a known offset
// For PATH (0xb), the layout is:
//   +0x00: int type = 0xb
//   +0x08: CPathData* pointer (8 bytes) ← ATTACKER OVERWRITES THIS
int *cache = (int *)(shared_section_base + animation_cache_offset);
*(uint64_t *)(cache + 2) = (uint64_t)fake_object_address;
```

### Step 4 — Trigger the Virtual Call

On the next composition frame, DWM calls:
```
GetSampledStartingValue → reads fake CPathData* → ComPtr::operator= → AddRef()
```

`AddRef()` dispatches through the vtable at `*(void***)fake_object_address`. The attacker controls the vtable pointer and thus the call target — subject to CFG validation.

### Step 5 — Achieve Code Execution

With CFG bypass (e.g., using valid call targets as gadgets, or corrupting the CFG bitmap), the attacker can:
- Redirect execution to a ROP chain
- Overwrite a `_TOKEN` to escalate privileges
- Use the `NtCurrentTeb()->PreviousMode` technique for arbitrary kernel R/W

### Impact

Full local privilege escalation from any authenticated user to SYSTEM via controlled virtual call in the DWM process. Exploited in the wild before the November 2023 patch.

---

## Patch Analysis

### Mechanism

The patch moves the `CPathData*` pointer out of the shared section and into a private member field:

1. **`GetCacheSizeForType`**: PATH (0xb) cache size reduced from `0x10` to `0x08` — the 8-byte pointer slot is removed from the shared section.

2. **`CKeyframeAnimation` constructor**: gains initialization of a new `wil::com_ptr_t<CPathData>` member at `this + 0x1c8`. The object grows by 8 bytes.

3. **`GetSampledStartingValue`**: For type 0xb, reads CPathData from the private member (`this + 0x1c8`) instead of from the shared section (`piVar2 + 2`).

4. **`~CKeyframeAnimation` destructor**: gains `~com_ptr_t()` cleanup of the private CPathData member.

5. **`ComPtr<CPathData>::operator=`**: renamed to `wil::com_ptr_t<CPathData>::operator=` with XFG CFG dispatch — a defense-in-depth change.

### Files Changed

| Function | Change |
|---|---|
| `CKeyframeAnimation::GetSampledStartingValue` | CPathData source changed from shared section to private member at `this+0x1c8` |
| `CKeyframeAnimation::CKeyframeAnimation` | +8 bytes; initializes new `wil::com_ptr_t<CPathData>` |
| `CKeyframeAnimation::~CKeyframeAnimation` | +`~com_ptr_t()` call on private CPathData member |
| `GetCacheSizeForType` | PATH (0xb) size: `0x10 → 0x08` (pointer removed from shared section) |
| `CKeyframeAnimation::CalculateValueWorker` | Mechanical +8 offset shifts from object growth |
| `Microsoft::WRL::ComPtr<CPathData>::operator=` | Renamed to `wil::com_ptr_t<CPathData>::operator=`; uses XFG CFG |

---

## Trigger PoC

A proof-of-concept is available at [`poc/poc_cve_2023_36033.c`](/data/patch_diffs/poc/poc_cve_2023_36033.c). It demonstrates the vulnerability by:

1. Creating a DirectComposition device and animation
2. Setting up a PATH (0xb) type keyframe animation
3. Locating the shared section in the process address space
4. Reading the CPathData pointer from the shared section cache to prove it's accessible
5. Optionally overwriting the pointer (disabled by default — would crash DWM)

On a **pre-patch** system, the PoC will find and display the CPathData pointer from the shared section, confirming the pointer is in attacker-writable memory. On a **post-patch** system, the shared section cache for PATH type is only 8 bytes (no pointer) and the CPathData is stored in DWM-private memory.

> **Note:** This PoC is a **trigger/detector** only. It demonstrates the pointer exposure but does not include fake-object construction, CFG bypass, or privilege escalation. It is designed for blue teams to validate detection rules.

---

## Detection Rules

### YARA Rule — Detecting PoC Binaries

```yara
rule CVE_2023_36033_DWM_SharedSection_PoC {
    meta:
        description = "Detects compiled PoC for CVE-2023-36033 DWM shared-section CPathData UAF"
        author = "OnlyFm252"
        date = "2026-07-17"
        cve = "CVE-2023-36033"
        severity = "critical"
        tlp = "white"

    strings:
        $api1 = "DCompositionCreateDevice" ascii wide
        $api2 = "CreateAnimation" ascii wide
        $api3 = "NtQueryVirtualMemory" ascii wide
        $api4 = "IDCompositionDevice" ascii wide
        $dcomp = "dcomp.dll" ascii wide nocase
        $shared = "shared" ascii wide nocase
        $path_type = { 0B 00 00 00 }
        $msg1 = "CPathData" ascii wide
        $msg2 = "dwmcore" ascii wide nocase

    condition:
        uint16(0) == 0x5A4D and
        filesize < 500KB and
        ($api1 or $api4) and
        ($api3 or $shared) and
        ($dcomp or $msg2) and
        ($path_type or $msg1)
}
```

### YARA Rule — Detecting Vulnerable dwmcore.dll (Pre-Patch)

```yara
rule CVE_2023_36033_Vulnerable_DWMCore {
    meta:
        description = "Detects pre-patch dwmcore.dll vulnerable to CVE-2023-36033"
        author = "OnlyFm252"
        date = "2026-07-17"
        cve = "CVE-2023-36033"

    strings:
        $ver_pre = "10.0.22621.2506" wide
        $comptr = "Microsoft::WRL::ComPtr<CPathData>" ascii
        $wil_comptr = "wil::com_ptr_t<CPathData>" ascii

    condition:
        uint16(0) == 0x5A4D and
        $ver_pre and
        $comptr and
        not $wil_comptr
}
```

### Sigma Rule — DirectComposition Shared Section Access

```yaml
title: Suspicious DirectComposition Shared Section Probing (CVE-2023-36033)
id: e6f4d7a8-3c5e-6f0a-d2e7-4f8a1b3c5d9e
status: experimental
description: >
    Detects processes that load dcomp.dll (DirectComposition) and perform
    memory scanning operations that may indicate shared-section pointer
    extraction for CVE-2023-36033 exploitation.
author: OnlyFm252
date: 2026/07/17
references:
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36033
    - https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36033.html
    - https://onlyfm252.starlabs.sg/patch-tuesday/2023-11/cve-2023-36033/
logsource:
    category: image_load
    product: windows
detection:
    selection_dcomp:
        ImageLoaded|endswith: '\dcomp.dll'
    selection_suspicious_process:
        Image|contains:
            - '\Users\'
            - '\Temp\'
            - '\AppData\'
        Image|endswith: '.exe'
    filter_legitimate:
        Image|endswith:
            - '\explorer.exe'
            - '\dwm.exe'
            - '\svchost.exe'
            - '\SearchHost.exe'
            - '\ShellExperienceHost.exe'
            - '\StartMenuExperienceHost.exe'
            - '\SystemSettings.exe'
            - '\TextInputHost.exe'
            - '\WindowsTerminal.exe'
            - '\msedge.exe'
            - '\chrome.exe'
            - '\firefox.exe'
    condition: selection_dcomp and selection_suspicious_process and not filter_legitimate
falsepositives:
    - Custom DirectComposition applications
    - UI frameworks that use DComp directly
    - Game engines and multimedia renderers
level: medium
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2023.36033
```

### Sigma Rule — DWM Crash Detection

```yaml
title: Desktop Window Manager Crash (Potential CVE-2023-36033 Exploitation)
id: f7a5e8b9-4d6f-7a1b-e3f8-5a9b2c4d6e0f
status: experimental
description: >
    Detects DWM process crashes that may indicate failed exploitation of
    CVE-2023-36033. A failed virtual call on a corrupted CPathData pointer
    will cause dwm.exe to crash with an access violation. DWM automatically
    restarts, but repeated crashes are suspicious.
author: OnlyFm252
date: 2026/07/17
logsource:
    product: windows
    service: application
detection:
    selection:
        EventID: 1000
        Application|endswith: 'dwm.exe'
        Faulting_module|endswith: 'dwmcore.dll'
    condition: selection | count() > 2
    timeframe: 5m
falsepositives:
    - GPU driver crashes causing DWM instability
    - Legitimate DWM bugs unrelated to exploitation
level: high
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2023.36033
```

### Sysmon Configuration

```xml
<!-- Sysmon config addition for CVE-2023-36033 detection -->
<RuleGroup name="CVE-2023-36033" groupRelation="or">

  <!-- ImageLoad: dcomp.dll loaded by suspicious process -->
  <ImageLoad onmatch="include">
    <Rule name="DComp_Suspicious_Load" groupRelation="and">
      <ImageLoaded condition="end with">dcomp.dll</ImageLoaded>
      <Image condition="excludes">C:\Windows\</Image>
      <Image condition="excludes">C:\Program Files\</Image>
      <Image condition="excludes">C:\Program Files (x86)\</Image>
    </Rule>
  </ImageLoad>

  <!-- ProcessAccess: Process accessing dwm.exe memory -->
  <ProcessAccess onmatch="include">
    <Rule name="DWM_Memory_Access" groupRelation="and">
      <TargetImage condition="end with">dwm.exe</TargetImage>
      <GrantedAccess condition="is">0x1F0FFF</GrantedAccess>
      <SourceImage condition="excludes">C:\Windows\System32\</SourceImage>
    </Rule>
  </ProcessAccess>

  <!-- ProcessTerminate: DWM crash/restart -->
  <ProcessTerminate onmatch="include">
    <Rule name="DWM_Crash" groupRelation="and">
      <Image condition="end with">dwm.exe</Image>
    </Rule>
  </ProcessTerminate>

</RuleGroup>
```

---

## Remediation

1. **Apply KB5032190** (November 2023 cumulative update) immediately — this CVE was exploited in the wild
2. **Verify patch**: Check `dwmcore.dll` version is ≥ 10.0.22621.2715
3. **Monitor**: Deploy the Sysmon and Sigma rules above to detect exploitation attempts
4. **Audit**: Watch for repeated DWM crashes (`dwm.exe` faulting in `dwmcore.dll`)
5. **Hunt**: Look for processes loading `dcomp.dll` from unusual locations or with unusual parent processes

---

## Timeline

| Date | Event |
|---|---|
| 2023-11-14 | Microsoft releases November 2023 Patch Tuesday (KB5032190), marks as "Exploitation Detected" |
| 2023-11-14 | Credited to Quan Jin (DBAPPSecurity WeBin Lab) |
| 2026-07-17 | This Ghidra MCP–verified analysis published |

---

## References

- [Microsoft Security Response Center — CVE-2023-36033](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36033)
- [Google Project Zero — CVE-2023-36033 RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-36033.html)
- [OnlyFm252 Diff Report](/data/patch_diffs/dwmcore_dll-kb5032190.md)

---

<sub>Analysis: OnlyFm252 — Ghidra MCP–verified decompilation of pre-patch dwmcore.dll 10.0.22621.2506.
Binary diff: ghidriff of dwmcore-2023-10.dll (10.0.22621.2506, pre-patch) vs dwmcore-2023-11.dll (10.0.22621.2715, post-patch).
[Download pre-patch](/data/patch_diffs/binaries/dwmcore-2023-10.dll) / [Download post-patch](/data/patch_diffs/binaries/dwmcore-2023-11.dll).</sub>
