# CVE-2024-38077 — "MadLicense": Pre-Auth RCE in the Windows RD Licensing Service via a 21-Byte Heap Overflow in `CDataCoding::DecodeData`

---

## Summary

| | |
|---|---|
| **Product** | Microsoft Windows Server — `lserver.dll` (Remote Desktop Licensing / RDL service) |
| **CVE ID** | CVE-2024-38077 |
| **Impact** | Remote Code Execution — **pre-authentication, 0-click** |
| **MSRC severity** | Critical — Remote Code Execution |
| **CVSS** | 9.8 — `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` |
| **CWE** | CWE-122: Heap-based Buffer Overflow |
| **Patch Date** | July 9, 2024 |
| **Service context** | `NT AUTHORITY\NETWORK SERVICE` |
| **Pre-patch binary** | `lserver.dll` 10.0.17763.5830 (KB5039705, June 2024) — SHA256 `db97ce34a74442f2c2faeac368e4c427dfb6c537dccac81957615f3e5ef03dfa` |
| **Post-patch binary** | `lserver.dll` 10.0.17763.6054 (KB5040430, July 9 2024) — SHA256 `e8ff982568c33c565da2e552f2ec28d1e48aa873ca1680cc8410b54d3823cd2c` |
| **Feature flag** | `Feature_1932091707_50631810` — **the bounds-check fix is CFR-gated** |

---

## Product Description

The Remote Desktop Licensing Service (RDL, `lserver.dll`) issues and tracks RDS
CALs on Windows Server. It exposes an **RPC interface over DCERPC (TCP 135)**.
The relevant method, `TLSRpcTelephoneRegisterLKP` (opnum 49), is reachable
**without authentication** — any host that can reach the RPC endpoint can call
it. A single internet-exposed RDL server is enough for a full pre-auth,
zero-interaction compromise, which is why this rated CVSS 9.8.

---

## Vulnerability Summary

`TLSRpcTelephoneRegisterLKP` decodes a caller-supplied Base24 string via
`CDataCoding::DecodeData`. That function allocates a **fixed 21-byte** heap
buffer and then decodes the encoded string into it in a loop bounded only by the
string's NULL terminator — **the input length is never checked against the
21-byte allocation**. A long enough encoded string overflows the buffer with
attacker-controlled bytes.

```c
// lserver!CDataCoding::DecodeData — PRE-PATCH, from our ghidriff diff of 17763.5830
ulong CDataCoding::DecodeData(CDataCoding *this, ushort *param_1, uchar **param_2, ulong *param_3)
{
    uint  allocSize = DAT_0;               // fixed allocation size (21 bytes per MadLicense)
    ulonglong idx   = 0;                   // uVar7 — highest byte index written
    ...
    _Dst = HeapAlloc(GetProcessHeap(), 8, (ulonglong)allocSize);   // FIXED-size buffer
    memset(_Dst, 0, DAT_0);

    for (; *param_1 != L'\0'; param_1++) {                 // *** bound is only the NUL ***
        pwVar3 = wcschr(L"BCDFGHJKMPQRTVWXY2346789", *param_1);
        if (pwVar3 == 0) { HeapFree(...); return 0xd; }
        uVar1 = (int)idx + 1;                              // span length = idx+1
        uVar5 = (pwVar3 - Base24) >> 1;                    // digit value 0..23
        pbVar6 = _Dst;
        do {                                               // multiply-accumulate the digit
            uVar4  = (int)uVar5 + (uint)*pbVar6 * DAT_1;
            *pbVar6 = (byte)uVar4;  pbVar6++;
            uVar5  = uVar4 >> 8;                            // carry
        } while (--span != 0);
        if (uVar4 >> 8 != 0) {
            _Dst[uVar1] = (byte)(uVar4 >> 8);              // *** NO bound check — writes past _Dst ***
            idx = uVar1;
        }
    }
    *param_3 = DAT_0;  *param_2 = _Dst;  return 0;
}
```

`idx` grows by one every time the base-24 accumulation carries out of the current
digit span, and **nothing checks `idx` against the allocation size**. An attacker
who supplies a sufficiently long Base24 string (the RPC stub parameter is fully
attacker-controlled) drives `_Dst[uVar1]` past the end of the fixed buffer with
attacker-influenced bytes.

---

## Prerequisites and Constraints

- **Network reachability only** — `AV:N`, `PR:N`, `UI:N`. No credentials, no
  interaction. The RDL RPC endpoint on TCP 135 is the entire requirement.
- The target must be running the Remote Desktop Licensing role.
- Overflow contents are attacker-controlled (the decoded Base24 bytes), so this
  is a *controlled* heap overflow, not a zero-fill.
- Corruption occurs in the `NT AUTHORITY\NETWORK SERVICE` process. Full
  code execution as NETWORK SERVICE; reaching SYSTEM needs a separate local
  escalation, but the initial foothold is remote and unauthenticated.

---

## Vulnerability Details

### Call Chain

```
Remote attacker (no auth):
  DCERPC / TCP 135 → RDL RPC interface
    → lserver!TLSRpcTelephoneRegisterLKP   (opnum 49)
      → lserver!CDataCoding::DecodeData    [*** 21-byte heap overflow ***]
```

### Root Cause

Two facts combine:

1. **The output buffer is a fixed size** (`HeapAlloc(hHeap, 8, DAT_0)` — 21 bytes
   per the MadLicense analysis), sized for a legitimate license key, not for
   arbitrary input.
2. **The decode loop is bounded only by the input string's NUL terminator** —
   the encoded length is never compared against the allocation. Each
   carry-producing digit advances `idx` and writes `_Dst[idx]`, walking off the
   end for a long enough string. The July patch adds exactly this missing
   comparison (`DAT_0 <= idx`), confirming the root cause.

Because `pSrcEncodedBuffer` comes straight from the unauthenticated RPC stub,
the attacker controls both the length and the content of the overflow.

### Exploitation (per the MadLicense research)

The overflow is used to leak heap base and function addresses (defeating ASLR),
bypass CFG, and pivot to code execution — e.g. driving `LoadLibraryA` /
`CreateProcess` toward a payload on an attacker-controlled remote SMB path. The
original researchers noted the bug remains triggerable on Windows Server 2025
despite the LFH Delay-Free mitigation, using a free-then-delay heap-grooming
technique.

---

## The Patch — two independent layers

The June→July 2024 diff (`17763.5830 → .6054`) shows Microsoft fixed this in
**two separate ways**, either of which alone would block the reported attack.

### Layer 1 — the bounds check (memory safety), and it is CFR-gated

The decode loop gains a length guard, added at **both** write sites — the inner
accumulation and the trailing carry write:

```c
// CDataCoding::DecodeData — PATCHED (17763.6054)
do {
    iVar2 = EvaluateCurrentState(&g_Feature_1932091707_50631810_...);
    if ((iVar2 != 0) && (DAT_0 <= (uint)uVar8)) goto LAB_180058626;  // bail if idx >= allocSize
    ...
} while (uVar7 <= uVar1);
if (uVar5 >> 8 != 0) {
    iVar2 = EvaluateCurrentState(&g_Feature_1932091707_50631810_...);
    if ((iVar2 != 0) && (DAT_0 <= uVar7)) goto LAB_180058626;        // same guard before carry write
    _Dst[uVar8] = (byte)(uVar5 >> 8);
}
```

The guard fires **only when the feature is enabled** — the `iVar2 != 0`
conjunct. `Feature_1932091707_50631810` is a Controlled Feature Rollout flag.
**With the flag disabled, the patched binary still overflows**: the bounds check
is compiled in but gated off, so file version alone does not determine whether
the memory-safety fix is live. Even a CVSS 9.8, pre-auth, in-the-wild-class RCE
shipped its primitive fix behind a rollout flag.

### Layer 2 — RPC authentication enforcement (reachability)

The diff also adds a brand-new function, `TLSRpcSecurityCallback`, registered on
the RDL interface via `RpcServerRegisterIf2` (also newly imported). It inspects
each incoming call with `RpcServerInqCallAttributesW` and:

- rejects the call unless the **authentication level is `6`**
  (`RPC_C_AUTHN_LEVEL_PKT_PRIVACY`);
- confirms the protocol sequence is one of `ncalrpc`, `ncacn_np`, `ncacn_ip_tcp`;
- honours a registry escape hatch — new strings `DisableWorkgroupAuthEnforcement`
  under `System\CurrentControlSet\Policies` — so workgroup deployments that
  break under enforcement can opt out.

This is the layer that actually removes the **pre-authentication** property that
made MadLicense a 9.8: an unauthenticated DCERPC caller is now rejected by the
security callback before ever reaching `DecodeData`. Unlike Layer 1, the callback
is unconditional (not flag-gated) — but the registry opt-out means an
administrator can still turn it off.

### Patch Completeness Assessment

Defence in depth: the reachability fix (Layer 2) is the hard stop for the
reported pre-auth vector, while the memory-safety fix (Layer 1) protects any
*authenticated* path that can still reach `DecodeData`. The caveat worth
recording is that **Layer 1 is CFR-gated and Layer 2 has a registry opt-out**, so
"patched" is not a single boolean — a host on `.6054` with the flag off *and*
`DisableWorkgroupAuthEnforcement` set would retain a reachable overflow. This
puts CVE-2024-38077 into the same CFR-gated set as the CLFS 2025–2026 fixes and
the August 2026 CVEs — the pattern now spans a 9.8 pre-auth RCE, not just kernel
LPEs.

---

## Detection Guidance

**Network.** Unauthenticated DCERPC calls to the RD Licensing interface on
TCP 135, particularly `TLSRpcTelephoneRegisterLKP` (opnum 49) carrying an
over-long Base24 string parameter. The RDL RPC surface should never be reachable
from untrusted networks.

**Crash signature.** Heap-corruption bugchecks or `lserver`/`svchost` (RDL host)
crashes with the faulting allocation tag/size around 21 bytes, in the
`NETWORK SERVICE` process.

**Exposure hunt.** Inventory servers with the Remote Desktop Licensing role and
confirm TCP 135 is not exposed beyond management networks — the single most
effective mitigation short of patching.

---

## Mitigation

- **Patch** (MS July 2024). Affected: Server 2008 through 2022 / 23H2 and all
  out-of-support builds running RDL — see the MSRC advisory for exact versions.
- **Restrict TCP 135 / DCERPC** to trusted networks; do not expose the RDL
  endpoint to the internet. Use a VPN for remote management.
- Where feasible, remove the RD Licensing role from hosts that do not require it.

---

## References

- Zhiniang Peng — *MadLicense* (original disclosure):
  `sites.google.com/site/zhiniangpeng/blogs/MadLicense`
- 78ResearchLab — CVE-2024-38077 analysis (full `DecodeData` decompilation)
- MSRC advisory — CVE-2024-38077
- Full binary diff: `/data/patch_diffs/lserver_dll-cve-2024-38077-ghidriff.md`
