# msi.dll Binary Diff Report — KB5055523 (April 2025)

**CVE:** CVE-2025-27727 — Windows Installer Elevation of Privilege  
**Binary:** msi.dll  
**KB:** KB5055523  
**Pre-patch version:** 5.0.26100.3323 (March 2025, KB5053598)  
**Post-patch version:** 5.0.26100.3775 (April 2025, KB5055523)  
**Diff tool:** Ghidra 11.x with PDB symbols from Microsoft symbol server  
**CVSS:** 7.8 (Local/Low/Low/None) — Elevation of Privilege  
**Exploitation:** Exploited in the wild  

---

## Executive Summary

The April 2025 patch for CVE-2025-27727 fixes a logic vulnerability in `CMsiTransaction::SetEEUIDirectoryAndFilter` that allowed an unprivileged user to schedule arbitrary folder deletions executed as SYSTEM. The fix inserts a WIL feature flag gate (`Feature_1997029688`) that completely suppresses the call to `ScheduleFileOrFolderDelete`, neutralizing the attack surface. Only **1 function has a code change**; 4 related functions in the call chain are **unchanged** (address-only shifts from relinking).

---

## Changed Functions

### 1. CMsiTransaction::SetEEUIDirectoryAndFilter — PRIMARY PATCH SITE

| Property | Pre-patch | Post-patch |
|---|---|---|
| Address | `0x18018bb90` | `0x18018be10` |
| Change type | — | code, address |

**Pre-patch decompilation:**

```c
uint CMsiTransaction::SetEEUIDirectoryAndFilter(
    CMsiTransaction *this, ushort *param_1, ulong param_2)
{
    bool bVar1 = IsValidCaller(this);
    if (bVar1) {
        StringCchCopyW((ushort *)(this + 0xa4), 0x104, param_1);
        *(ulong *)(this + 0xa0) = param_2;
        uVar2 = ScheduleFileOrFolderDelete(this, (ushort *)(this + 0xa4), true);
        //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //      UNCONDITIONAL — no feature flag check
    } else {
        uVar2 = 5;
    }
    return uVar2;
}
```

**Post-patch decompilation:**

```c
uint CMsiTransaction::SetEEUIDirectoryAndFilter(
    CMsiTransaction *this, ushort *param_1, ulong param_2)
{
    bool bVar1 = IsValidCaller(this);
    if (bVar1) {
        StringCchCopyW((ushort *)(this + 0xa4), 0x104, param_1);
        *(ulong *)(this + 0xa0) = param_2;

        // NEW: Feature flag gate
        bVar1 = wil::details::FeatureImpl<__WilFeatureTraits_Feature_1997029688>
                    ::__private_IsEnabled(&impl);

        if (bVar1) {
            uVar2 = 0;   // Feature ENABLED → skip ScheduleFileOrFolderDelete, return success
        } else {
            uVar2 = ScheduleFileOrFolderDelete(this, (ushort *)(this + 0xa4), true);
            //      Kill-switch path: old behavior preserved when feature disabled
        }
    } else {
        uVar2 = 5;
    }
    return uVar2;
}
```

**Analysis:** The patch wraps the `ScheduleFileOrFolderDelete` call inside a feature flag check. When `Feature_1997029688` is enabled (the default on patched systems), the function returns `0` (ERROR_SUCCESS) without writing anything to the TempPackages registry key. The kill-switch path (feature disabled) preserves the old behavior for controlled rollout. This is the same WIL Feature Experimentation pattern used across recent Windows kernel patches (e.g., CVE-2025-62455 mqac.sys, CVE-2025-60705 csc.sys).

The feature flag implementation at `0x18018e8a0` calls `GetCachedFeatureEnabledState` and `ReportUsage`, confirming Microsoft's staged rollout telemetry infrastructure.

---

## Unchanged Functions (address-only shift from relinking)

### 2. CMsiConfigurationManager::SetEEUIDirectoryAndFilter

| Property | Pre-patch | Post-patch |
|---|---|---|
| Address | `0x18018ba10` | `0x18018bc90` |
| Change type | address only |

COM entry point. Acquires `g_csServerInterfaceLock`, checks `IsAdmin()`, and if non-admin calls `StartImpersonating()` + `CreateFileW(param_1, DELETE (0x10000), SHARE_READ, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS (0x2200000))` to verify the caller has DELETE permission on the target path. On success, delegates to `CMsiTransaction::SetEEUIDirectoryAndFilter`. **No code change** — the vulnerability was in the callee, not the caller.

### 3. ScheduleFileOrFolderDelete

| Property | Pre-patch | Post-patch |
|---|---|---|
| Address | `0x1801af264` | `0x1801af544` |
| Change type | address only |

Elevates via `CElevate(true)`, opens `HKLM\Software\Microsoft\Windows\CurrentVersion\Installer\TempPackages` (HKEY 0x80000002 = HKLM), enumerates existing value count, builds an `MsiString` from the caller-supplied path, prepends `"#2"` folder flag, and writes via vtable offset `0x30` (SetValue). **No code change** — the fix prevents this function from being reached, rather than modifying its behavior.

### 4. CleanupTempPackagesInternal

| Property | Pre-patch | Post-patch |
|---|---|---|
| Address | `0x180186c6c` | `0x180186c6c` |
| Change type | none (identical address) |

The cleanup consumer. Elevates via `CElevate(true)`, opens TempPackages registry key, enumerates all values. For each entry: reads the value data, strips `#` prefix, parses flags. If flag `& 2` (folder marker), extracts the path string and calls `FDeleteFolder` for recursive deletion. If `FDeleteFolder` succeeds, deletes the registry value. For file entries (flag `& 1`), uses `IMsiPath::RemoveFile` with retry loop (4 attempts, 100ms backoff). **No code change** — this function runs as SYSTEM and trusts whatever paths are in TempPackages. The fix prevents attacker-controlled paths from being written there in the first place.

### 5. FDeleteFolder

| Property | Pre-patch | Post-patch |
|---|---|---|
| Address | `0x1801b29ac` | `0x1801b2c8c` |
| Change type | address only (minor PDB type annotation differences) |

Recursive folder deletion. `FindFirstFileW`/`FindNextFileW` loop skipping `.` and `..`. For files: `DeleteFileW`, with `LockdownPath` + `SetFileAttributesW(0)` + retry on failure. For subdirectories: recursive `FDeleteFolder` call. After enumeration: `RemoveDirectoryW` on the parent, again with `LockdownPath` fallback. **No code change** — the function itself is not vulnerable; it faithfully deletes whatever path it's given. The vulnerability was that an attacker could control what paths reached it.

---

## New Functions in Post-Patch Binary

### wil::details::FeatureImpl<__WilFeatureTraits_Feature_1997029688>::__private_IsEnabled

| Property | Value |
|---|---|
| Address | `0x18018e8a0` |
| Size | ~40 bytes |

```c
bool FeatureImpl<__WilFeatureTraits_Feature_1997029688>::__private_IsEnabled(
    FeatureImpl *this)
{
    GetCachedFeatureEnabledState(this);
    ReportUsage(this, (bool)(local_res10 & 1), in_R8D, in_R9);
    return (bool)(local_res10 & 1);
}
```

WIL (Windows Implementation Library) feature flag evaluator. Reads cached feature state from a global; if not yet initialized, queries the Windows Feature Store. Returns `true` when the feature is enabled (patch active), `false` when disabled (kill-switch engaged). The `ReportUsage` call sends telemetry for Microsoft's staged rollout monitoring.

---

## Vulnerability Summary

**Root cause:** `CMsiTransaction::SetEEUIDirectoryAndFilter` unconditionally called `ScheduleFileOrFolderDelete`, writing a caller-controlled directory path into the `HKLM\...\Installer\TempPackages` registry key. `CleanupTempPackagesInternal`, running as SYSTEM during installer cleanup, reads these paths and calls `FDeleteFolder` for recursive deletion without validating path origin.

**Exploitation chain:**
1. Attacker calls `MsiBeginTransactionW` → `SetEEUIDirectoryAndFilter` via COM (`CLSID {000C101C-...}`)
2. Target path (e.g., `C:\Config.Msi`) written to TempPackages registry
3. Installer cleanup triggers `CleanupTempPackagesInternal` → `FDeleteFolder` as SYSTEM
4. Recursive deletion of target directory
5. Attacker re-creates `C:\Config.Msi` with NULL DACL, plants `.rbs` rollback script
6. SYSTEM executes rollback script → arbitrary code execution

**Patch approach:** Feature flag `Feature_1997029688` gates the `ScheduleFileOrFolderDelete` call. When enabled, `SetEEUIDirectoryAndFilter` returns success (0) without writing to TempPackages, completely eliminating the attack surface. The kill-switch path preserves old behavior for emergency rollback during staged deployment.

---

## Statistics

| Metric | Value |
|---|---|
| Functions with code changes | 1 |
| Functions with address-only changes | 3 |
| Functions unchanged (same address) | 1 |
| New functions added | 1 (`__private_IsEnabled`) |
| Total functions in call chain analyzed | 6 |
