Windows 虚拟桌面信息(一)分析注册表

目录

前言

一、理论分析

二、代码实现

总结


本文为原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136110636

前言

Win 10/11 的虚拟桌面微软暂时没有开放接口,有很多信息对开发者是闭塞的,对于开发动态壁纸程序来说,这个功能也是需要的,我们需要检测多桌面的情况,以允许不同桌面用不同的壁纸。相关的研究目前就是对未公开的 COM 接口进行操作的,可以实现很强大的功能。本系列将逐一复现外网的相关研究结论。当然,这一部分研究注册表的结果是我自己发现的。

一、理论分析

在 Explorer 运行时,会向注册表如下位置写入部分虚拟桌面信息:

HKEY_CURRENT_USER\Software\Microsoft\Windows\

CurrentVersion\Explorer\VirtualDesktops

如下图所示: 

注册表 VirtualDesktops 下的值项

主要包含三个值项:CurrentVirtualDesktop、VDSoftLandingCampaignDone 和 VirtualDesktopIDs。

其中,VirtualDesktopIDs 下的二进制数据包含目前虚拟桌面的 UUID 列表,按照桌面编号顺序排列,编号从 1 开始。

VirtualDesktopIDs 中的二进制信息

目前有 3 个桌面:

虚拟桌面截图

而 CurrentVirtualDesktop 则表示当前桌面的 UUID:

当前活动虚拟桌面 UUID 信息

这里的数据会随着修改同步的。

然后,我们再看看 Desktops 子键:

\HKEY_CURRENT_USER\Software\Microsoft\Windows\

CurrentVersion\Explorer\VirtualDesktops\Desktops

显然,下面有很多以 GUID 命名的子键:
 

Desktops 子键的结构

而 GUID 的第五个部分(圈起来的)和上面的桌面信息中的对应,并且每个 GUID 子键下面都有一个名为 Wllpaper 的值项:

包含的路径信息为壁纸路径

他代表这个桌面的系统壁纸路径。

所以,我们的方法是,通过解析 VirtualDesktopIDs 并在 Desktops 的子键中查找包含最后一部分的子串,这种模式匹配就可以构建整个虚拟桌面基本信息结构。

二、代码实现

实现代码如下:

// TestIVDesktop.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <windows.h>
#include <atlstr.h>
#include <vector>// 定义用于保存虚拟桌面信息的结构体
typedef struct _tgaVirtualDesktop
{int       VirtualDesktopId;        // 虚拟桌面 IDGUID      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 中找到匹配的 GUIDsstd::vector<GUID> matchingGUIDs = FindMatchingGUIDs(subGUIDs);// 输出匹配到的 GUIDsprintf("Matching GUIDs:\n");wchar_t szGuid[64] = { 0 };for (const auto& guid : matchingGUIDs){// 输出 GUIDmemset(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);}// 反转整个 vectorstd::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){// 将字符串转换为 GUIDGUID guid;if (SUCCEEDED(::CLSIDFromString(subkeyNameStr.GetString(), &guid))){// 找到匹配的子键,加入 vectormatchingGUIDs.push_back(guid);break; // 跳出内循环,继续下一个子键}}}// 重置子键名称缓冲区长度subkeyNameLen = MAX_PATH;}// 关闭 Desktops 键RegCloseKey(hKeyDesktops);}// 反转整个 vectorstd::reverse(matchingGUIDs.begin(), matchingGUIDs.end());return matchingGUIDs;
}VirtualDesktop CreateVirtualDesktopStruct(const CAtlStringW& subkeyName)
{VirtualDesktop desktop;// 从子键名称中提取 GUIDif (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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/681281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react【六】 React-Router

文章目录 1、Router1.1 路由1.2 认识React-Router1.3 Link和NavLink1.4 Navigate1.5 Not Found页面配置1.6 路由的嵌套1.7 手动路由的跳转1.7.1 在函数式组件中使用hook1.7.2 在类组件中封装高阶组件 1.8 动态路由传递参数1.9 路由的配置文件以及懒加载 1、Router 1.1 路由 1.…

面试经典150题——无重复字符的最长子串

我生来就是高山而非溪流&#xff0c;我欲于群峰之巅俯视平庸的沟壑 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力解法 看到这个题目&#xff0c;我们是不是发现和上一篇内容刚刚讲过的长度最小的子数组题目很像&#xff1f;首先自然的暴力解法&#xff0c;就是遍历字符…

音视频基础

本篇文章我们来讲一下音视频基础 像素点: 将以下图片的美女眼睛放大 能够看到一个一个的小方块 这就是像素点 照片像素宽像素点*高像素点 像素点 代码实例&#xff1a; #include <opencv2/opencv.hpp>int main() {// 创建一个200x100的黑色图像cv::Mat image(100, 200,…

web3知识体系汇总

web3.0知识体系 1.行业发展 2. web3的特点&#xff1a; 1、统一身份认证系统 2、数据确权与授权 3、隐私保护与抗审查 4、去中心化运行 Web3.0思维技术思维✖金融思维✖社群思维✖产业思维”&#xff0c;才能从容理解未来Web3.0时代的大趋势。 3.技术栈 Web3.jsSolidit…

拼写检查应用程序:基于词典编辑的解释

一、说明 拼写检查器项目涉及创建一个可以自动检测并纠正给定文本中的拼写错误的程序。此类项目在各种应用程序中非常有用&#xff0c;例如文字处理器、电子邮件客户端和网络浏览器&#xff0c;可确保用户生成的文本没有拼写错误。 您可以找到我创建的拼写检查器应用程序&#…

Waymo数据集下载与使用

在撰写论文时&#xff0c;接触到一个自动驾驶数据集Waymo Dataset 论文链接为&#xff1a;https://arxiv.org/abs/1912.04838v7 项目链接为&#xff1a;https://github.com/waymo-research/waymo-open-dataset 数据集链接为&#xff1a;https://waymo.com/open waymo提供了两种…

23种计模式之Python/Go实现

目录 设计模式what?why?设计模式&#xff1a;设计模式也衍生出了很多的新的种类&#xff0c;不局限于这23种创建类设计模式&#xff08;5种&#xff09;结构类设计模式&#xff08;7种&#xff09;行为类设计模式&#xff08;11种&#xff09; 六大设计原则开闭原则里氏替换原…

单链表基础知识点

单链表的读取 对于单链表实现获取第i个元素的数据的操作 GetElem&#xff0c;在算法上&#xff0c;相对要麻烦一些。 获得链表第i个数据的算法思路: 声明一个结点p指向链表第一个结点&#xff0c;初始化j从1开始;当j<i时&#xff0c;就遍历链表&#xff0c;让p的指针向后移…

【小赛1】蓝桥杯双周赛第5场(小白)思路回顾

我的成绩&#xff1a;小白(5/6) 完稿时间&#xff1a;2024-2-13 比赛地址&#xff1a;https://www.lanqiao.cn/oj-contest/newbie-5/ 相关资料&#xff1a; 1、出题人题解&#xff1a;“蓝桥杯双周赛第5次强者挑战赛/小白入门赛”出题人题解 - 知乎 (zhihu.com) 2、矩阵快速幂&…

MATLAB | 情人节画个花瓣venn图?

之前七夕节情人节各种花&#xff0c;相册&#xff0c;爱心啥的都快画够了&#xff0c;今年画个花瓣韦恩图&#xff1f; 花瓣上的数字是仅属于该类的样本数&#xff0c;而中心的数字是属于每一类的样本数 教程部分 0 数据准备 % 给组起名t1 t2 t3...t15 setName compose(t%d,…

mysql数据库concat指定连接符号

SELECT CONCAT_WS(;;;, 你好,华为) FROM DUAL;

uniapp API文档地址 以及 HBuilder安装

uniapp API文档地址 以及 HBuilder安装 一、进入 当前网站 uni-app 官网 [uni-app](https://zh.uniapp.dcloud.io/quickstart-hx.html)二、点击截图下载文件 三、 进入 当前网站 &#xff08;https://www.dcloud.io/hbuilderx.html&#xff09; 浏览器会识别 也可以自行选择…

Java 基于 SpringBoot+Vue 的社区医院系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Hive的Join连接

前言 Hive-3.1.2版本支持6种join语法。分别是&#xff1a;inner join&#xff08;内连接&#xff09;、left join&#xff08;左连接&#xff09;、right join&#xff08;右连接&#xff09;、full outer join&#xff08;全外连接&#xff09;、left semi join&#xff08;左…

《Java 简易速速上手小册》第4章:Java 中的异常处理(2024 最新版)

文章目录 4.1 异常类型和错误 - 遇见你的小怪兽4.1.1 基础知识4.1.2 重点案例&#xff1a;文件读取处理4.1.3 拓展案例 1&#xff1a;处理空指针异常4.1.4 拓展案例 2&#xff1a;捕获多个异常 4.2 异常处理机制 - 穿上你的超级英雄斗篷4.2.1 基础知识4.2.2 重点案例&#xff1…

【开源】SpringBoot框架开发食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

[FFmpeg学习]从视频中获取图片

从视频中获取图片是一个比较直观的例子&#xff0c;这里从一个基础的例子来查看FFmpeg相关api的使用&#xff0c;从mp4文件中获取一帧图像&#xff0c;保存为jpeg格式图片&#xff0c;mp4文件比较好准备&#xff0c;一般手机录屏文件就是mp4格式。 原理还是比较清楚&#xff0…

2.13学习总结

1.出差&#xff08;Bleeman—ford&#xff09;&#xff08;spfa&#xff09; &#xff08;dijkstra&#xff09; 2.最小生成树&#xff08;prim&#xff09;&#xff08;Kruskal&#xff09; 最短路问题&#xff1a; 出差https://www.luogu.com.cn/problem/P8802 题目描述 AA …

Netty应用(九) 之 编解码器概念 Netty常见的编解码器

目录 22.编解码器 22.1 编解码的概念 22.2 netty中的编解码 22.3 序列化 23.编解码器在使用过程中的两部分核心内容 23.1 序列化协议&#xff08;编码格式&#xff09;&#xff08;传输数据的格式&#xff09; 23.1.1 Java默认的序列化与反序列化 23.1.2 XML的序列化与反…

Ps:焦点堆栈

焦点堆栈 Focus Stacking是一种摄影和图像处理技术&#xff0c;通过合并多张在不同焦距拍摄的照片来创建一张具有更大景深的图像&#xff0c;特别适用于微距摄影、风景摄影和任何需要在整个场景中保持尖锐对焦的情况。 ◆ ◆ ◆ 拍摄注意事项 1、使用三脚架 为了确保图像之间…