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

news2024/11/28 19:05:37

目录

前言

一、理论分析

二、代码实现

总结


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

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;        // 虚拟桌面 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

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

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

相关文章

Qt QWidget以及各种控件、布局 核心属性(适合入门使用时查询)

目录 1. QWidget核心属性 2. 按钮类控件 2.1 PushButton 核心属性 2.2 RadioButton 核心属性 2.3 CheckBox 和 Tool Button 核心属性 3. 显示类控件 3.1 Label 核心属性 3.2 LCDNumber 核心属性 3.3 ProgressBar 核心属性 3.4 Calendar Widget 核心属性 4. 输入类控…

Photoshop 中的“彩蛋”

在 Photoshop 中隐藏了几个“彩蛋” Easter Eggs&#xff0c;是开发者留下的小秘密或玩笑功能&#xff0c;也许是他们在紧张的开发过程中的一种自我调节吧&#xff0c;就如复活节彩蛋一样&#xff0c;同样也可以给 Photoshop 的用户们带来一点小“惊喜”。 这些彩蛋通常以有趣的…

上位机图像处理和嵌入式模块部署(借鉴与学习)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于很多学院派的同学来说&#xff0c;他们对市场的感觉一般是比较弱的。如果写一个软件的话&#xff0c;或者说开发一个项目的话&#xff0c;他们…

【C++关联式容器】unordered_map

目录 unordered_map 1. pair类型 2. 关联式容器额外的类型别名 3. 哈希桶 4. 无序容器对关键字类型的要求 5. Member functions 5.1 constructor、destructor、operator 5.1.1 constructor 5.1.2 destructor 5.1.3 operator 5.2 Capacity ​5.2.1 empty 5.2.2 si…

C/C++内存管理:new、delete功能及原理实现

目录 一、C/C内存分布 二、C中内存管理方式 2.1new/delete操作内置类型 2.2 new和delete操作自定义类型 三、operator new与operator delete函数 四、new和delete的实现原理 4.1内置类型 4.2自定义类型 五、定位new 一、C/C内存分布 int globalVar 1; static int sta…

Java学习第十三节之三种初始化和内存分析

三种初始化 package array;public class ArrayDemo02 {public static void main(String[] args) {//静态初始化&#xff1b;创建赋值int[] a {1, 2, 3, 4, 5, 6, 7, 8};System.out.println(a[0]);for (int i 0; i <a.length; i) {System.out.println(a[i]);}//动态初始化…

你的电脑关机吗

目录 程序员为什么不喜欢关电脑&#xff1f; 电脑长时间不关机会怎样? 电脑卡顿 中度风险 硬件损耗 能源浪费 散热问题 软件问题 网络安全问题 程序员为什么不喜欢关电脑&#xff1f; 大部分人都会选择将电脑进行关机操作。其实这不难理解&#xff0c;毕竟人类都需要…

Qt初使用(使用Qt创建项目,在创建的项目中添加类,Qt中输出内容到控制台,设置窗口大小和窗口标题,Qt查看说明文档)

目录 一.创建带模板的项目新建项目运行在文件中查看该项目文件 二.在创建好的项目中添加类三.创建空项目&#xff08;不使用自带的模板&#xff09;四.Qt中输出内容到控制台五.设置窗口大小 , 窗口标题 ,固定窗口大小QWidget组件的说明 六.Pro文件帮助文档 按windows键&#xf…

1_opencv3环境搭建与测试

之前2020年5月写过一次&#xff0c;时隔3年多&#xff0c;有机会再重新写一次。相比之前&#xff0c;应该是有一点儿进步的。之前是使用默认安装路径&#xff0c;所以无需配置共享库的搜索路径。这次是自定义安装路径&#xff0c;略有区别。随着写程序的时间增长&#xff0c;编…

LeetCode Python - 14.最长公共前缀

目录 题目答案运行结果 题目 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; 输入&#xff…

react 【二】 setState/react性能优化/dom操作

文章目录 1、setState1.1 setState的三种用法1.2 setState为什么是异步 2、React性能优化2.1 react的更新机制2.2 如何优化性能2.2.1 shouldComponentUpdate2.2.2 PureComponent2.2.3 memo 3、不可变数据的力量4、dom操作4.1 通过ref获取dom的三种方式4.2 执行子组件的方法&…

关于node与node-sass那些事

昨晚找了之前的一个项目想要复习下&#xff0c;结果npm i报错&#xff0c;大致意思就是noda-sass的版本和node的对不上&#xff0c;那怎么办呢&#xff1a; 1.换node版本&#xff0c;那好吧&#xff0c;首先要明白&#xff0c;对应的版本关系 2.然后我开始用nvm换node版本&am…

猫头虎分享: 探索软件系统架构的革新之路

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【AI视野·今日Sound 声学论文速览 第四十九期】Wed, 17 Jan 2024

AI视野今日CS.Sound 声学论文速览 Wed, 17 Jan 2024 Totally 23 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers From Coarse to Fine: Efficient Training for Audio Spectrogram Transformers Authors Jiu Feng, Mehmet Hamza Erol, Joon Son Chung,…

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

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

六、Redis之数据持久化及高频面试题

6.1 数据持久化 官网文档地址&#xff1a;https://redis.io/docs/manual/persistence/ Redis提供了主要提供了 2 种不同形式的持久化方式&#xff1a; RDB&#xff08;Redis数据库&#xff09;&#xff1a;RDB 持久性以指定的时间间隔执行数据集的时间点快照。AOF&#xff0…

车载诊断协议DoIP系列 —— DoIP会话模式(安全与非安全)

车载诊断协议DoIP系列 —— DoIP会话模式(安全与非安全) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖…

LLVM的中间表示

概括 选择编译器IR的决策很重要&#xff0c;它决定了优化过程将拥有多少信息来使代码运行得更快。 一方面非常高层级的IR允许优化器轻松地提取原始源代码的相关信息。 另一方面&#xff0c;低层的IR更加贴近目标机器&#xff0c;这样编译器更容易为特定的硬件生成相应的代码…

MyBatis篇----第三篇

系列文章目录 文章目录 系列文章目录前言一、模糊查询 like 语句该怎么写?二、如何获取自动生成的(主)键值三、在 mapper 中如何传递多个参数?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,…

Golang中的fmt包:格式化输入输出的利器

Golang中的fmt包&#xff1a;格式化输入输出的利器 在软件开发的世界里&#xff0c;fmt包就像是一位忠实的伙伴&#xff0c;始终陪伴着开发人员。它简化了格式化输入输出的过程&#xff0c;让打印和扫描数据变得轻松自如。无论是向控制台输出简单的消息&#xff0c;还是处理复杂…