目录
本文为原创文章,转载请注明出处:
https://blog.csdn.net/qq_59075481/article/details/136110636
前言
Win 10/11 的虚拟桌面微软暂时没有开放接口,有很多信息对开发者是闭塞的,对于开发动态壁纸程序来说,这个功能也是需要的,我们需要检测多桌面的情况,以允许不同桌面用不同的壁纸。相关的研究目前就是对未公开的 COM 接口进行操作的,可以实现很强大的功能。本系列将逐一复现外网的相关研究结论。当然,这一部分研究注册表的结果是我自己发现的。
一、理论分析
在 Explorer 运行时,会向注册表如下位置写入部分虚拟桌面信息:
HKEY_CURRENT_USER\Software\Microsoft\Windows\
CurrentVersion\Explorer\VirtualDesktops
如下图所示:
主要包含三个值项:CurrentVirtualDesktop、VDSoftLandingCampaignDone 和 VirtualDesktopIDs。
其中,VirtualDesktopIDs 下的二进制数据包含目前虚拟桌面的 UUID 列表,按照桌面编号顺序排列,编号从 1 开始。
目前有 3 个桌面:
而 CurrentVirtualDesktop 则表示当前桌面的 UUID:
这里的数据会随着修改同步的。
然后,我们再看看 Desktops 子键:
\HKEY_CURRENT_USER\Software\Microsoft\Windows\
CurrentVersion\Explorer\VirtualDesktops\Desktops
显然,下面有很多以 GUID 命名的子键:
而 GUID 的第五个部分(圈起来的)和上面的桌面信息中的对应,并且每个 GUID 子键下面都有一个名为 Wllpaper 的值项:
他代表这个桌面的系统壁纸路径。
所以,我们的方法是,通过解析 VirtualDesktopIDs 并在 Desktops 的子键中查找包含最后一部分的子串,这种模式匹配就可以构建整个虚拟桌面基本信息结构。
二、代码实现
实现代码如下:
// TestIVDesktop.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <windows.h>
#include <atlstr.h>
#include <vector>
// 定义用于保存虚拟桌面信息的结构体
typedef struct _tgaVirtualDesktop
{
int VirtualDesktopId; // 虚拟桌面 ID
GUID VirtualDesktopGuid; // 虚拟桌面全局唯一标识符
LPCWSTR DesktopWallpaperPath; // 虚拟桌面壁纸文件路径
UINT DesktopCreateFlag; // 虚拟桌面状态标识
HWND DesktopActWnd; // 虚拟桌面上当前顶层窗口的句柄
} VirtualDesktop, * lpVirtualDesktop;
CAtlString ReadRegBinaryValue(HKEY hKey, LPCWSTR lpSubKey, LPCWSTR lpValue);
void NumToHexStrW(DWORD dwNum, CAtlStringW& str);
CAtlStringW RegBinaryStrProcessingW(LPCWSTR lpszData, DWORD dwLength);
std::vector<CAtlStringW> ExtractSubGUIDs(const CAtlString& buffer);
std::vector<GUID> FindMatchingGUIDs(const std::vector<CAtlStringW>& subGUIDs);
VirtualDesktop CreateVirtualDesktopStruct(const CAtlStringW& subkeyName);
std::vector<VirtualDesktop> CreateVirtualDesktopStructList(
const std::vector<GUID>& matchingGUIDs);
int main()
{
// 读取注册表中的二进制数据
CAtlString buffer = ReadRegBinaryValue(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops",
L"VirtualDesktopIDs"
);
// 提取子串
std::vector<CAtlStringW> subGUIDs = ExtractSubGUIDs(buffer);
// 输出子串数据
printf("Signature:\n");
for (const auto& guid : subGUIDs)
{
printf("%ws\n", guid.GetString());
}
// 在 buffer 中找到匹配的 GUIDs
std::vector<GUID> matchingGUIDs = FindMatchingGUIDs(subGUIDs);
// 输出匹配到的 GUIDs
printf("Matching GUIDs:\n");
wchar_t szGuid[64] = { 0 };
for (const auto& guid : matchingGUIDs)
{
// 输出 GUID
memset(szGuid, 0, sizeof(szGuid));
::StringFromGUID2(guid, szGuid, 64);
printf("%ws\n", szGuid);
}
// 创建包含完整信息的 VirtualDesktop 结构体链表
std::vector<VirtualDesktop> virtualDesktopList =
CreateVirtualDesktopStructList(matchingGUIDs);
// 输出 VirtualDesktop 结构体链表
printf("Virtual Desktop Information:\n");
for (const auto& virtualDesktop : virtualDesktopList)
{
wchar_t szGuid[64] = { 0 };
::StringFromGUID2(virtualDesktop.VirtualDesktopGuid, szGuid, 64);
printf("DesktopID: %d, GUID: %ws, Wallpaper Path: %ws\n",
virtualDesktop.VirtualDesktopId,
szGuid,
virtualDesktop.DesktopWallpaperPath);
}
system("pause");
return 0;
}
void NumToHexStrW(DWORD dwNum, CAtlStringW& str)
{
UINT Temp = 0;
UINT index = 0;
DWORD dwCurNum = dwNum;
if (dwCurNum == 0) // 遇到 0 就返回
return;
while (dwCurNum > 0)
{
Temp = dwCurNum % 16;
if (Temp < 10) {
str.AppendChar(Temp + _T('0'));
}
else {
str.AppendChar(_T('A') + Temp - 10);
}
dwCurNum = dwCurNum >> 4;
index++;
}
// 补全字符串
for (UINT j = 0; j < 4 - index; j++)
{
str.AppendChar(L'0');
}
str.MakeReverse();
CAtlStringW aa = str.Mid(2, 3);
CAtlStringW bb = str.Left(2);
str.Format(L"%ws%ws", aa, bb);
}
// 还原注册表二进制数据类型格式
CAtlStringW RegBinaryStrProcessingW(LPCWSTR lpszData, DWORD dwLength)
{
CAtlStringW strData;
size_t guidSum = dwLength >> 4;
for (DWORD n = 0; n < guidSum; n++)
{
for (ULONG i = 5 + (n << 3); i < 8 + (n << 3); i++) // 从第 5 个双字开始,此时是 GUID 的第五个部分,该部分未混淆
{
CAtlStringW str;
NumToHexStrW(lpszData[i], str);
strData.AppendFormat(L"%ws", str);
}
strData.AppendFormat(L"\n");
}
return strData;
}
CAtlString ReadRegBinaryValue(HKEY hKey, LPCWSTR lpSubKey, LPCWSTR lpValue)
{
CAtlString strBinaryValue;
DWORD dwFlags = REG_BINARY;
HKEY hKeyResult;
BOOL ret = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, &hKeyResult);
if (ERROR_SUCCESS == ret)
{
DWORD dwLength = 0;
// 获得读取键值有多少个字符
RegQueryValueExW(hKeyResult, lpValue, NULL, &dwFlags, NULL, &dwLength);
//printf("%d\n", dwLength);
// 申请一段空间,并初始化为空
DWORD BinaryLen = dwLength + 1;
TCHAR* BinaryInfo = new TCHAR[BinaryLen];
memset(BinaryInfo, 0, sizeof(TCHAR) * BinaryLen);
if (ERROR_SUCCESS == RegQueryValueExW(hKeyResult, lpValue, NULL,
&dwFlags, (LPBYTE)BinaryInfo, &dwLength))
{
// 解析还原成字符串
strBinaryValue = RegBinaryStrProcessingW(BinaryInfo, dwLength + 1);
}
delete[] BinaryInfo;
}
RegCloseKey(hKeyResult);
return strBinaryValue;
}
std::vector<CAtlStringW> ExtractSubGUIDs(const CAtlString& buffer)
{
std::vector<CAtlStringW> subGUIDs;
// 提取子串
size_t startPos = 0;
while ((startPos = buffer.Find(L"\n", startPos)) != -1)
{
CAtlStringW subGUID = buffer.Mid(startPos - 12, 12); // 每个子串都是 12 个字符长,不包含包括换行符
startPos += 13; // 跳到下一个子串的起始位置
// 将子串插入到 vector 的开头
subGUIDs.insert(subGUIDs.begin(), subGUID);
}
// 反转整个 vector
std::reverse(subGUIDs.begin(), subGUIDs.end());
return subGUIDs;
}
std::vector<GUID> FindMatchingGUIDs(const std::vector<CAtlStringW>& subGUIDs)
{
std::vector<GUID> matchingGUIDs;
// 打开注册表中的 Desktops 键
HKEY hKeyDesktops;
if (RegOpenKeyExW(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\\Desktops",
0, KEY_READ, &hKeyDesktops) == ERROR_SUCCESS)
{
// 遍历 Desktops 下的子键
WCHAR subkeyName[MAX_PATH];
DWORD index = 0;
DWORD subkeyNameLen = MAX_PATH;
while (RegEnumKeyExW(hKeyDesktops, index++, subkeyName,
&subkeyNameLen, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
{
// 检查子键名称的尾部是否匹配子串
CAtlStringW subkeyNameStr = subkeyName;
for (const auto& subGUID : subGUIDs)
{
if (subkeyNameStr.Find(subGUID.GetString()) > 0)
{
// 将字符串转换为 GUID
GUID guid;
if (SUCCEEDED(::CLSIDFromString(subkeyNameStr.GetString(), &guid)))
{
// 找到匹配的子键,加入 vector
matchingGUIDs.push_back(guid);
break; // 跳出内循环,继续下一个子键
}
}
}
// 重置子键名称缓冲区长度
subkeyNameLen = MAX_PATH;
}
// 关闭 Desktops 键
RegCloseKey(hKeyDesktops);
}
// 反转整个 vector
std::reverse(matchingGUIDs.begin(), matchingGUIDs.end());
return matchingGUIDs;
}
VirtualDesktop CreateVirtualDesktopStruct(const CAtlStringW& subkeyName)
{
VirtualDesktop desktop;
// 从子键名称中提取 GUID
if (SUCCEEDED(::CLSIDFromString(subkeyName.GetString(), &desktop.VirtualDesktopGuid)))
{
// 读取壁纸文件路径
HKEY hKeyDesktop;
if (RegOpenKeyExW(HKEY_CURRENT_USER,
(L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\\Desktops\\" + subkeyName).GetString(),
0, KEY_READ, &hKeyDesktop) == ERROR_SUCCESS)
{
DWORD dwType;
WCHAR szWallpaper[MAX_PATH];
DWORD dwSize = sizeof(szWallpaper);
if (RegQueryValueExW(hKeyDesktop, L"Wallpaper", nullptr,
&dwType, reinterpret_cast<LPBYTE>(szWallpaper), &dwSize) == ERROR_SUCCESS)
{
if (dwType == REG_SZ)
{
desktop.DesktopWallpaperPath = szWallpaper;
}
}
RegCloseKey(hKeyDesktop);
}
}
return desktop;
}
std::vector<VirtualDesktop> CreateVirtualDesktopStructList(const std::vector<GUID>& matchingGUIDs)
{
std::vector<VirtualDesktop> virtualDesktopList;
DWORD desktopCount = 0;
for (const auto& guid : matchingGUIDs)
{
desktopCount++;
CAtlStringW subkeyName;
subkeyName.Format(L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
VirtualDesktop desktop = CreateVirtualDesktopStruct(subkeyName);
// 设置虚拟桌面 ID,从 1 开始
desktop.VirtualDesktopId = desktopCount;
virtualDesktopList.push_back(desktop);
}
return virtualDesktopList;
}
// TODO: 枚举虚拟桌面窗口信息
BOOL EnumerateVirtualDesktopWindows(std::vector<VirtualDesktop> &DesktopStructList)
{
std::vector<VirtualDesktop> VecList;
return TRUE;
}
// 54 F8 07 0D-60 E8 BF 48 A0 41 9D 55 B9-72 22 00
// 7D D7 88 B5 D0 8C B5 4D 98 6E 88 61 7F-A6 B6 E5
运行效果如图:
总结
Win 10/11 的虚拟桌面微软暂时没有开放接口,有很多信息对开发者是闭塞的,对于开发动态壁纸程序来说,这个功能也是需要的,我们需要检测多桌面的情况,以允许不同桌面用不同的壁纸。相关的研究目前就是对未公开的 COM 接口进行操作的,可以实现很强大的功能。本文首先研究注册表中关于虚拟桌面的信息,并给出了解析信息的代码。下一节我们将更深入地逆向分析未公开的 COM 接口,用以获取更多信息。
发布于:2024.02.13;更新于:2024.02.14