目录
前言
原理解释
原理实现
Winlogon 桌面窗口层次
本文出处链接:https://blog.csdn.net/qq_59075481/article/details/141608316。
前言
众所周知,从 Windows 7 开始,Winlogon 桌面不再使用 SASWindow 作为背景窗口,而是采用了一套新的安全桌面模式。我发现 CSDN 上在这方面的研究很少。
在我前面的几篇文章里面已经详细分析并实现了,编程化拦截与 Winlogon 有关的登陆事件(如 Ctrl + Alt + Delete 快捷键)。在之前,我浅谈过一些有关 LogonUI Interface 登陆 UI 界面的内容,但并未做详细的解释。这篇文章将就如何动态枚举 Winlogon 桌面的窗口层次并进行记录进行详细的讲解。如有错误,敬请点拨。
专栏文章:
快捷键机制系列文章 | 涟幽 516https://blog.csdn.net/qq_59075481/category_12568641.html
原理解释
首先,我们需要知道 Winlogon 桌面(登陆桌面,运行在控制台会话)和 Default 桌面(用户默认桌面,运行在 UI 会话)均是由 Winlogon.exe 所创建的。
Winlogon.exe 是 Windows 操作系统中负责处理用户登录和注销等会话管理功能的进程。通常情况下,每个控制台会话(即本地会话)会有一个对应的 Winlogon.exe 实例,用于处理用户认证等任务。
UI 会话 是指用户交互的会话,通常指的是图形用户界面(GUI)环境下的桌面会话。每个用户登录到系统时,Windows 会为该用户分配一个会话 ID,并启动一个桌面环境来处理用户的输入和输出。
在多用户环境中,每个用户的 UI 会话可以与一个特定的控制台会话关联。控制台会话是指直接连接到物理屏幕、键盘和鼠标的会话(如通过直接本地登录),而其他 UI 会话则可能是通过远程桌面或其他方式登录的。
所以,用户一般接触最多的就是交互式用户会话和桌面,对于 Winlogon 桌面可能知之甚少。其实用户所熟知的 UAC 对话框,就是运行在 Winlogon 桌面下的系统程序所创建的窗口。
LogonUI.exe 进程:这是登陆用户交互式界面进程,Winlogon.exe 进程会在需要在登陆桌面下显示交互式界面时,启动该进程。并通过进程间通信(IPC)技术,如管道、RPC 等完成多进程同步事务。用户看到的登陆界面、CAD 界面、UAC 界面均最终由它启动。
LogonUI 运行时模块:LogonUI.exe 实质上是一个加载容器,用于装载运行时需要的接口模块。第一个加载的模块为:LogonUIController.dll。其他运行时模块通过延迟加载技术完成加载。
窗口工作站:窗口工作站是与进程关联的安全对象,包含一个或多个绑定的桌面对象、剪贴板等等。Winlogon 桌面和 Default 桌面默认运行在 Winsta0 窗口工作站下。进程必须以合适的访问权限打开窗口工作站,才能够打开指定的桌面访问句柄。进程每次获取的窗口工作站的访问句柄不同,桌面的访问句柄就不同。如果需要获知对象句柄的名称,则需要通过 WINAPI GetUserObjectInformation。
工作线程:进程连接到窗口工作站后,系统会将桌面分配给建立连接的线程。
为了在运行时研究清楚 Winlogon 桌面有哪些窗口,就必须切换程序的工作线程然后枚举活动桌面的窗口。
频繁切换工作线程绑定的桌面是不被允许的,因为 SetThreadDesktop 之前不能有任何桌面窗口服务正在运作(微软说会造成安全问题)。在实际测试过程当中,我们发现连续第二次切换时就会引起失败。但是我们又需要进行动态监测,所以必须要每次都能够及时切换工作线程的桌面。解决方案很简单,就是每次切换时创建一个新的线程作为工作线程,然后在新的线程里面切换桌面。
然后,打开窗口工作站和桌面需要 SYSTEM 令牌的 Local System 权限,所以必须首先以管理员身份运行,然后再从 SYSTEM 进程复制模拟令牌来重新启动高权限进程。
然后,枚举窗口这边也很简单,通过 EnumWindows 和 EnumChildWindows 枚举并保存窗口层次。
至于桌面的切换检测,最简单的就是通过一个循环不断去获取当前活动桌面(ActiveDesktop),主要通过 OpenInputDesktop 来获取,然后分析桌面名称,当桌面切换到 Winlogon 桌面时切换线程并遍历桌面窗口,当桌面切换到 Default 桌面时,停止记录并保存日志;
原理实现
目前在管理员用户下运行一切正常(对于多用户登陆是否正常还未测试)。
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <ctime>
#include <sstream>
#include <codecvt>
#include <functional>
#include <tlhelp32.h>
#include <userenv.h>
#include <sddl.h>
// 记录窗口信息的结构体,使用宽字符
struct WindowInfo {
std::wstring className;
std::wstring windowTitle;
HWND hwnd;
std::vector<WindowInfo> children;
};
// 工作线程传递信息的结构体
struct MYTHREADINFO {
HDESK hDesktop;
std::vector<WindowInfo> wndInfo;
};
// 启用特定的权限(例如 SeDebugPrivilege)
bool EnablePrivilege(LPCWSTR privilege) {
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
std::wcerr << L"Failed to open process token." << std::endl;
return false;
}
if (!LookupPrivilegeValueW(NULL, privilege, &luid)) {
std::wcerr << L"Failed to lookup privilege." << std::endl;
CloseHandle(hToken);
return false;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
std::wcerr << L"Failed to adjust token privileges." << std::endl;
CloseHandle(hToken);
return false;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
std::wcerr << L"The privilege was not assigned." << std::endl;
CloseHandle(hToken);
return false;
}
CloseHandle(hToken);
return true;
}
// 检查是否以管理员权限运行
bool IsRunAsAdmin() {
BOOL isAdmin = FALSE;
PSID administratorsGroup = NULL;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &administratorsGroup)) {
CheckTokenMembership(NULL, administratorsGroup, &isAdmin);
FreeSid(administratorsGroup);
}
return isAdmin == TRUE;
}
// 重新启动并请求管理员权限
bool RelaunchAsAdmin() {
wchar_t szPath[MAX_PATH];
if (!GetModuleFileNameW(NULL, szPath, MAX_PATH)) {
std::wcerr << L"Failed to get module file name." << std::endl;
return false;
}
SHELLEXECUTEINFOW sei = { sizeof(sei) };
sei.lpVerb = L"runas"; // 请求管理员权限
sei.lpFile = szPath;
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;
if (!ShellExecuteExW(&sei)) {
std::wcerr << L"Failed to relaunch as administrator." << std::endl;
return false;
}
return true;
}
// 获取 winlogon 进程的 SYSTEM 令牌
HANDLE GetSystemTokenFromWinlogon() {
HANDLE hToken = NULL;
HANDLE hProcess = NULL;
// 获取 Winlogon 进程的进程ID
DWORD winlogonPid = 0;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnapshot == INVALID_HANDLE_VALUE) {
std::wcerr << L"Failed to create process snapshot!" << std::endl;
return NULL;
}
if (Process32First(hProcessSnapshot, &pe32)) {
do {
if (_wcsicmp(pe32.szExeFile, L"winlogon.exe") == 0) {
winlogonPid = pe32.th32ProcessID;
break;
}
} while (Process32Next(hProcessSnapshot, &pe32));
}
CloseHandle(hProcessSnapshot);
if (winlogonPid == 0) {
std::wcerr << L"Failed to find winlogon.exe process!" << std::endl;
return NULL;
}
// 打开 Winlogon 进程
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, winlogonPid);
if (!hProcess) {
std::wcerr << L"Failed to open winlogon process!" << std::endl;
return NULL;
}
// 打开该进程的令牌
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &hToken)) {
std::wcerr << L"Failed to open process token!" << std::endl;
CloseHandle(hProcess);
return NULL;
}
CloseHandle(hProcess);
return hToken;
}
// 创建具有 SYSTEM 权限的进程
bool CreateSystemProcess(LPCWSTR applicationName, LPCWSTR commandLine) {
HANDLE hToken = GetSystemTokenFromWinlogon();
if (!hToken) {
std::wcerr << L"Failed to get SYSTEM token!" << std::endl;
return false;
}
// 使用 SYSTEM 权限创建进程
STARTUPINFOW si = { sizeof(STARTUPINFOW) };
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessAsUserW(hToken, applicationName, const_cast<LPWSTR>(commandLine), NULL,
NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
std::wcerr << L"Failed to create process as SYSTEM!" << std::endl;
CloseHandle(hToken);
return false;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hToken);
return true;
}
// 检查当前进程是否具有 SYSTEM 权限
bool IsSystem() {
HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
return false;
}
DWORD tokenInfoLength = 0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &tokenInfoLength);
PTOKEN_USER tokenUser = (PTOKEN_USER)malloc(tokenInfoLength);
if (!GetTokenInformation(hToken, TokenUser, tokenUser, tokenInfoLength, &tokenInfoLength)) {
CloseHandle(hToken);
free(tokenUser);
return false;
}
LPWSTR sidString = NULL;
ConvertSidToStringSidW(tokenUser->User.Sid, &sidString);
bool isSystem = (_wcsicmp(sidString, L"S-1-5-18") == 0);
LocalFree(sidString);
CloseHandle(hToken);
free(tokenUser);
return isSystem;
}
// 递归获取窗口层次,使用宽字符 API
void EnumChildWindowsRecursive(HWND hwndParent, std::vector<WindowInfo>& windowList) {
wchar_t className[256];
wchar_t windowTitle[256];
ZeroMemory(className, sizeof(className));
ZeroMemory(windowTitle, sizeof(windowTitle));
// 获取窗口类名和标题
GetClassNameW(hwndParent, className, sizeof(className) / sizeof(wchar_t));
GetWindowTextW(hwndParent, windowTitle, sizeof(windowTitle) / sizeof(wchar_t));
if (className[0] == L'\0') {
wcscpy_s(className, L"(None)");
}
if (windowTitle[0] == L'\0') {
wcscpy_s(windowTitle, L"(None)");
}
WindowInfo windowInfo = { className, windowTitle, hwndParent };
// 枚举子窗口
EnumChildWindows(hwndParent, [](HWND hwnd, LPARAM lParam) -> BOOL {
std::vector<WindowInfo>* children = reinterpret_cast<std::vector<WindowInfo>*>(lParam);
EnumChildWindowsRecursive(hwnd, *children);
return TRUE;
}, reinterpret_cast<LPARAM>(&windowInfo.children));
windowList.push_back(windowInfo);
}
// 获取当前桌面窗口层次,使用宽字符 API
std::vector<WindowInfo> GetWindowHierarchy() {
std::vector<WindowInfo> windows;
EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
std::vector<WindowInfo>* windows = reinterpret_cast<std::vector<WindowInfo>*>(lParam);
EnumChildWindowsRecursive(hwnd, *windows);
return TRUE;
}, reinterpret_cast<LPARAM>(&windows));
return windows;
}
// 格式化时间为宽字符格式
std::wstring FormatTime(const std::time_t& time) {
wchar_t timeBuffer[100];
tm ti = {};
localtime_s(&ti, &time);
std::wcsftime(timeBuffer, sizeof(timeBuffer) / sizeof(wchar_t), L"%Y-%m-%d %H:%M:%S", &ti);
return std::wstring(timeBuffer);
}
// 转换宽字符到 UTF-8
std::string WideToUTF8(const std::wstring& wideStr) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
return conv.to_bytes(wideStr);
}
// 保存窗口层次信息到文件,使用 UTF-8 编码
void SaveWindowHierarchy(const std::vector<WindowInfo>& windows, const std::time_t& changeTime, const std::wstring& filename) {
std::ofstream file(WideToUTF8(filename), std::ios::app | std::ios::binary); // 使用 binary 防止换行符的意外转换
if (!file.is_open()) {
std::wcerr << L"Unable to open file for writing!" << std::endl;
return;
}
// 在文件开头写入 BOM,标识为 UTF-8 编码
static bool bomWritten = false;
if (!bomWritten) {
const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; // UTF-8 BOM
file.write(reinterpret_cast<const char*>(bom), sizeof(bom));
bomWritten = true;
}
// 格式化时间为字符串
wchar_t timeBuffer[100];
tm ti = {};
localtime_s(&ti, &changeTime);
std::wcsftime(timeBuffer, sizeof(timeBuffer) / sizeof(wchar_t), L"%Y-%m-%d %H:%M:%S", &ti);
// 写入时间戳
file << WideToUTF8(std::wstring(timeBuffer)) << "\n";
// 递归写入窗口信息
std::function<void(const std::vector<WindowInfo>&, int)> WriteHierarchy;
WriteHierarchy = [&file, &WriteHierarchy](const std::vector<WindowInfo>& windows, int indent) {
for (const auto& window : windows) {
file << std::string(indent, ' ') // 使用空格进行缩进
<< "Class Name: " << WideToUTF8(window.className)
<< ", Title: " << WideToUTF8(window.windowTitle)
<< ", HWND: " << std::hex << window.hwnd << "\n";
if (!window.children.empty()) {
WriteHierarchy(window.children, indent + 4); // 递归写入子窗口信息
}
}
};
WriteHierarchy(windows, 0);
file << "\n";
file.close();
}
// 获取当前桌面的名称
std::wstring GetDesktopName(HDESK hDesktop) {
wchar_t desktopName[256];
DWORD neededLength = 0;
if (!GetUserObjectInformationW(hDesktop, UOI_NAME, desktopName, sizeof(desktopName), &neededLength)) {
std::wcerr << L"Failed to get desktop name." << std::endl;
return L"";
}
return std::wstring(desktopName);
}
// 切换到指定桌面并枚举窗口,任务结束后恢复到原始桌面
std::vector<WindowInfo> GetWindowsFromDesktop(HDESK hDesktop) {
// 获取原始桌面句柄
HDESK hOriginalDesktop = GetThreadDesktop(GetCurrentThreadId());
// 切换到目标桌面
if (!SetThreadDesktop(hDesktop)) {
std::wcerr << L"Failed to set thread desktop!" << std::endl;
return {};
}
// 切换后获取窗口层次
std::vector<WindowInfo> windows = GetWindowHierarchy();
恢复到原始桌面
//if (!SetThreadDesktop(hOriginalDesktop)) {
// std::wcerr << L"Failed to restore original desktop!" << std::endl;
//}
return windows;
}
// 使用辅助线程进行桌面切换和窗口枚举
DWORD WINAPI MonitorDesktopThread(LPVOID param) {
_wsetlocale(LC_ALL, L"zh-CN");
MYTHREADINFO* threadInfo = static_cast<MYTHREADINFO*>(param);
threadInfo->wndInfo = GetWindowsFromDesktop(threadInfo->hDesktop);
return 0;
}
// 打开窗口工作站并切换到桌面
HDESK OpenDesktopWithWindowStation(LPCWSTR desktopName, HWINSTA hWinsta) {
// 打开桌面
HDESK hDesktop = OpenDesktopW(desktopName, 0, FALSE, GENERIC_ALL);
if (!hDesktop) {
std::wcerr << L"Failed to open desktop! name = " << desktopName << std::endl;
CloseWindowStation(hWinsta);
}
return hDesktop;
}
// 获取当前活动桌面
HDESK GetActiveDesktop() {
return OpenInputDesktop(0, FALSE, GENERIC_ALL);
}
// 监控桌面切换
void MonitorDesktop() {
// 打开窗口工作站
HWINSTA hWinsta = OpenWindowStationW(L"WinSta0", FALSE, GENERIC_READ | GENERIC_WRITE);
if (!hWinsta) {
std::wcerr << L"Failed to open window station!" << std::endl;
return;
}
// 将当前线程关联到工作站
if (!SetProcessWindowStation(hWinsta)) {
std::wcerr << L"Failed to set process window station!" << std::endl;
CloseWindowStation(hWinsta);
return;
}
HDESK hDefaultDesk = OpenDesktopWithWindowStation(L"Default", hWinsta);
HDESK hWinlogonDesk = OpenDesktopWithWindowStation(L"Winlogon", hWinsta);
if (!hDefaultDesk || !hWinlogonDesk) {
std::wcerr << L"Failed to open desktops!" << std::endl;
return;
}
std::wcout << L"Monitoring desktop changes (SYSTEM privileges detected)..." << std::endl;
std::wcout << L"Desktops: Winlogon(" << std::hex << (UINT64)hWinlogonDesk << L"), Default("
<< std::hex << (UINT64)hDefaultDesk << L")." << std::endl;
// 桌面监控代码
std::vector<WindowInfo> windowHistory;
bool monitoring = false;
while (true) {
// 检查指定按键是否被按下(比如 ESC 键,键码 0x1B)
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
std::wcout << L"Escape key pressed. Exiting monitoring..." << std::endl;
break; // 退出循环,结束监控
}
HDESK hCurrentDesk = GetActiveDesktop(); // 获取活动桌面句柄
std::wstring desktopName = GetDesktopName(hCurrentDesk); // 获取桌面名称
//std::wcout << L"Current Desktop: " << desktopName << std::endl;
if (desktopName == L"Winlogon" && !monitoring) {
std::wcout << L"Switched to Winlogon desktop. Start monitoring window hierarchy..." << std::endl;
monitoring = true;
windowHistory.clear(); // 清空之前的记录
// 切换到 winlogon 桌面并枚举窗口
MYTHREADINFO info = { hWinlogonDesk };
HANDLE hThread = CreateThread(NULL, 0, MonitorDesktopThread, &info, 0, NULL);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
windowHistory = info.wndInfo;
}
else if (desktopName == L"Default" && monitoring) {
std::wcout << L"Switched back to Default desktop. Stopping monitoring..." << std::endl;
std::time_t currentTime = std::time(nullptr);
SaveWindowHierarchy(windowHistory, currentTime, L"D:\\window_hierarchy.log");
monitoring = false;
// 显示历史记录
MessageBoxW(NULL, L"The window hierarchy log has been saved. Check window_hierarchy.log for details.",
L"History Saved", MB_OK | MB_ICONINFORMATION | MB_SYSTEMMODAL);
}
if (monitoring) {
// 切换到 winlogon 桌面枚举窗口
MYTHREADINFO info = { hWinlogonDesk };
HANDLE hThread = CreateThread(NULL, 0, MonitorDesktopThread, &info, 0, NULL);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
if (!info.wndInfo.empty()) {
windowHistory = info.wndInfo;
}
}
Sleep(1000); // 每秒检查一次
}
CloseDesktop(hDefaultDesk);
CloseDesktop(hWinlogonDesk);
}
int wmain(int argc, wchar_t* argv[]) {
_wsetlocale(LC_ALL, L"zh-CN");
// 检查是否为管理员权限运行
if (!IsRunAsAdmin()) {
std::wcout << L"Attempting to restart with administrator privileges..." << std::endl;
if (RelaunchAsAdmin()) {
return 0; // 提升后进程将重新启动,当前进程结束
}
else {
std::wcerr << L"Failed to relaunch as administrator." << std::endl;
return 1;
}
}
// 启用 SeDebugPrivilege
if (!EnablePrivilege(SE_DEBUG_NAME)) {
std::wcerr << L"Failed to enable SeDebugPrivilege." << std::endl;
return 1;
}
// 检查 SYSTEM 权限
if (!IsSystem()) {
std::wcout << L"Attempting to restart with SYSTEM privileges..." << std::endl;
// 检查命令行参数,避免无限递归
if (argc < 2 || _wcsicmp(argv[1], L"system") != 0) {
// 重新启动自身并传递 "system" 参数
wchar_t commandLine[MAX_PATH];
swprintf(commandLine, MAX_PATH, L"%s system", argv[0]);
if (CreateSystemProcess(argv[0], commandLine)) {
std::wcout << L"Restarted with SYSTEM privileges." << std::endl;
}
else {
std::wcerr << L"Failed to restart with SYSTEM privileges." << std::endl;
}
return 0;
}
else {
std::wcerr << L"Already tried to elevate privileges but failed." << std::endl;
return 1;
}
}
// 如果当前进程已经是 SYSTEM 权限,则继续执行桌面监控
MonitorDesktop();
return 0;
}
编译时设置监视器高 DPI 识别,和以管理员身份启动。
运行效果如下:
日志记录存在放在 "D:\window_hierarchy.log" 文件中。
Winlogon 桌面窗口层次
Winlogon 桌面默认是没有窗口的,所以是黑色背景。当执行任务时,具有窗口。当在 UAC 界面时,背景窗口是 Credential Dialog Xaml Host 窗口;当在 CAD 或登陆界面时,背景窗口是 LogonUI Logon Window。
备注:由于博主时间有限,更多信息请自行探索。
本文出处链接:https://blog.csdn.net/qq_59075481/article/details/141608316。
本文发布于:2024.08.27;更新于:2024.08.27。