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.rhkb.cn/news/257284.html

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

相关文章

安卓价值1-如何在电脑上运行ADB

ADB&#xff08;Android Debug Bridge&#xff09;是Android平台的调试工具&#xff0c;它是一个命令行工具&#xff0c;用于与连接到计算机的Android设备进行通信和控制。ADB提供了一系列命令&#xff0c;允许开发人员执行各种操作&#xff0c;包括但不限于&#xff1a; 1. 安…

ubuntu下如何查看显卡及显卡驱动

ubuntu下如何查看显卡及显卡驱动 使用nvidia-smi 工具查看 查看显卡型号nvida-smi -L $ nvidia-smi -L GPU 0: NVIDIA GeForce RTX 3050 4GB Laptop GPU (UUID: GPU-4cf7b7cb-f103-bf56-2d59-304f8996e28c)当然直接使用nvida-smi 命令可以查看更多信息 $ nvidia-smi Mon Fe…

Redis复制

文章目录 1.Redis复制是什么2.Redis能干嘛3.权限细节4.基本操作命令5.常用三招5.1 一主二仆5.2 薪火相传5.3 反客为主 6.复制原理和工作流程7.复制的缺点 1.Redis复制是什么 就是主从复制&#xff0c;master以写为主&#xff0c;Slave以读为主。当master数据变化的时候&#x…

Oracle的学习心得和知识总结(三十二)|Oracle数据库数据库回放功能之论文四翻译及学习

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

C++ //练习 6.55 编写4个函数,分别对两个int值执行加、减、乘、除运算;在上一题创建的vector对象中保存指向这些函数的指针。

C Primer&#xff08;第5版&#xff09; 练习 6.55 练习 6.55 编写4个函数&#xff0c;分别对两个int值执行加、减、乘、除运算&#xff1b;在上一题创建的vector对象中保存指向这些函数的指针。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#x…

智慧园区的可视化大屏,比你见过的更漂亮。

智慧园区云平台的建设旨在建立统一的工作流程&#xff0c;协同、调度和共享机制&#xff0c;以云平台为枢纽&#xff0c;形成一个紧密联系的整体&#xff0c;获得高效、协同、互动、整体的效益。

如何流畅进入Github

前言 以下软件是免费的&#xff0c;放心用 一、进入右边的下载链接https://steampp.net/ 二、点击下载 三、点击接受并下载 四、随便选一个下载链接进行下载 五、软件安装好打开后&#xff0c;找到Github 六、点击全部启用 七、再点击左上角的一键加速 八、这个时候你再进Git…

SQLyog安装配置(注册码)连接MySQL

下载资源 博主给你打包好了安装包&#xff0c;在网盘里&#xff0c;只有几Mb&#xff0c;防止你下载到钓鱼软件 快说谢谢博主&#xff08;然后心甘情愿的点个赞~&#x1f60a;&#xff09; SQLyog.zip 安装流程 ①下载好压缩包后并解压 ②打开文件夹&#xff0c;双击安装包 ③…

幻兽帕鲁Palworld专用服务器CPU内存配置怎么选择?

腾讯云幻兽帕鲁服务器配置怎么选&#xff1f;根据玩家数量选择CPU内存配置&#xff0c;4到8人选择4核16G、10到20人玩家选择8核32G、2到4人选择4核8G、32人选择16核64G配置&#xff0c;腾讯云百科txybk.com来详细说下腾讯云幻兽帕鲁专用服务器CPU内存带宽配置选择方法&#xff…

2024年智能算法优化PID参数,ITAE、ISE、ITSE、IAE四种适应度函数随意切换,附MATLAB代码...

PID 参数整定就是确定比例系数&#xff08;Kp &#xff09;、积分系数&#xff08;Ki&#xff09;和微分系数&#xff08;Kd &#xff09;的过程&#xff0c;以便使 PID 控制器能够在系统中实现稳定、快速、准确的响应。 本期的主题 采用四种2024年的智能优化算法优化PID的三个…

STM32F1 - 标准外设库_规范

STM32F10x_StdPeriph_Lib_V3.6.0 1> 头文件包含关系2> .c文件内部结构3> 宏定义位置4> 位掩码bit mask5> .c文件中定义私有变量6> 枚举类型定义 1> 头文件包含关系 1个头文件stm32f10x.h 就把整个MCU以及标准外设库&#xff0c;就管理了&#xff1b; 2>…

前沿技术期刊追踪——以电机控制为例

一、背景 前沿技术期刊追踪是指科研人员、学者或专业人士通过关注和阅读各类顶级科技期刊&#xff0c;了解并跟踪相关领域的最新研究成果和发展动态。以下是一些常见的前沿技术期刊以及追踪方法&#xff1a; 1. **知名科技期刊**&#xff1a; - 自然&#xff08;Nature&#…

C++的进阶泛型编程学习(1):函数模板的基本概念和机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、模板1.1 模板的概念1.1.1 形象的解释&#xff1a;模板就是通用的模具&#xff0c;目的是提高通用性1.1.1 模板的特点&#xff1a;1.1.2 综述模板的作用 1.2…

力扣精选算法100道——【模板】前缀和(一维)

【模板】前缀和_牛客题霸_牛客网 (nowcoder.com) 目录 &#x1f6a9;了解题意 &#x1f6a9;算法原理 &#x1f388;设定下标为1开始 &#x1f388;取值的范围 &#x1f6a9;实现代码 &#x1f6a9;了解题意 第一行的3和2&#xff0c;3代表行数&#xff0c;2代表q次查询(…

PyTorch深度学习快速入门教程 - 【小土堆学习笔记】

小土堆Pytorch视频教程链接 声明&#xff1a; 博主本人技术力不高&#xff0c;这篇博客可能会因为个人水平问题出现一些错误&#xff0c;但作为小白&#xff0c;还是希望能写下一些碰到的坑&#xff0c;尽力帮到其他小白 1 环境配置 1.1 pycharm pycharm建议使用2020的&…

算法沉淀——哈希算法(leetcode真题剖析)

算法沉淀——哈希算法 01.两数之和02.判定是否互为字符重排03.存在重复元素04.存在重复元素 II05.字母异位词分组 哈希算法&#xff08;Hash Algorithm&#xff09;是一种将任意长度的输入&#xff08;也称为消息&#xff09;映射为固定长度的输出的算法。这个输出通常称为哈希…

MATLAB 1:基础知识

MATLAB中的数据类型主要包括数值类型、逻辑类型、字符串、函数句柄、结构体和单元数组类型。这六种基本的数据类型都是按照数组形式存储和操作的。 MATLAB中还有两种用于高级交叉编程的数据类型&#xff0c;分别是用户自定义的面向对象的用户类类型和Java类类型。 1.1.1数值类…

SpringBoot 接入讯飞星火大模型实现对话

申请地址 https://xinghuo.xfyun.cn/sparkapi?scrprice 免费申请200万Token 开发文档 https://www.xfyun.cn/doc/spark/Web.html#_1-接口说明 页面最下面有相关demo可以参考 介绍 接口是以套接字的形式分段返回&#xff0c;而且非http请求&#xff0c;比较繁琐&#xff0c;官…

【Linux】yum软件包管理器

目录 Linux 软件包管理器 yum 什么是软件包 Linux安装软件 查看软件包 关于rzsz Linux卸载软件 查看yum源 扩展yum源下载 Linux开发工具 vim编辑器 上述vim三种模式之间的切换总结&#xff1a; 命令模式下&#xff0c;一些命令&#xff1a; vim配置 Linux 软件包管理…

微服务—ES数据同步

目录 数据同步 问题分析 方案1. 同步调用 方案2. 异步通知 方案3. 监听binlog​编辑 各方案对比 案例——利用MQ实现数据同步 步骤1. 导入hotel-admin项目 步骤2. 声明交换机、队列 步骤3. 发送MQ消息 步骤4. 接收MQ消息 步骤5. 测试同步功能 数据同步 elasticsea…