对于 XP 上的控制台 windows,SetProp(...) 失败

SetProp(…) fails for console windows on XP

我试图在 Windows API 中使用 SetProp 函数,但在我的 XP-32 位 SP3 系统上它总是失败并显示 ERROR_ACCESS_DENIED (5 ) 当给控制台 window 句柄时,而在 Windows 7 上它似乎工作正常。

这个 C 程序演示了问题:

#include <stdio.h>
#include <windows.h>
void main() {
    {
        HWND Wnd = GetConsoleWindow();
        SetLastError(ERROR_SUCCESS);
        int Success = SetPropW(Wnd, L"asdf", (HANDLE) 0xdeadbeef);
        int Error = GetLastError();

        printf("console (%x)\n", Wnd);
        printf("\tsuccess: %u\n", Success); // 0 - failed
        printf("\terror: %u\n", Error); // 5 - ERROR_ACCESS_DENIED
        printf("\tprop: %x\n\n", (unsigned) GetPropW(Wnd, L"asdf"));
    };

    {
        HWND Wnd = GetDesktopWindow();
        SetLastError(ERROR_SUCCESS);
        int Success = SetPropW(Wnd, L"asdf", (HANDLE) 0xdeadbeef);
        int Error = GetLastError();

        printf("desktop (%x)\n", Wnd);
        printf("\tsuccess: %u\n", Success); // 1 - succeeded
        printf("\terror: %u\n", Error); // 0 - ERROR_SUCCESS
        printf("\tprop: %x\n\n", (unsigned) GetPropW(Wnd, L"asdf"));
    };
};

它似乎确实影响了与当前进程无关的控制台 windows,而且我还没有发现任何导致此问题的可见非控制台 windows。

什么给? SetProp 的文档对此唯一说明的是

When UIPI [User Interface Privilege Isolation] blocks property changes, GetLastError will return 5

但是 UIPI 在 XP 上不存在,它在 Windows 7 上工作正常,它有 UIPI。

我在 Wine 和 ReactOS 实现中检查了该函数的源代码,但它们似乎没有任何方式可以像这样运行。实际的 Microsoft 实现似乎主要只是一个系统调用(我的 OS 上的 0x1213),所以我无法分析它。 (更新:win32k.sys NtUserSetProp disassembly

如果有人能想到解决方法,我会很想听听。

附加代码:http://pastebin.com/RmJHxRPF - tests every window on the current desktop


供参考,我遇到此问题的场景是:我正在编写桌面 shell,并希望将元数据存储在 windows 上,只要 window 存在(即使我的 shell 进程停止 运行)。

设置window属性的系统调用是NtUserSetProp%windir%\system32\win32k.sys中。这是它的大致作用(在 pseudo-C 中,基于 the disassembly):

NtUserSetProp(hwnd, atom, value) {

    WND* esi = ValidateHwnd(hwnd); // call win32k!ValidateHwnd; mov esi,eax
    if (!esi) {return;}; // test esi,esi; jz win32k!NtUserSetProp+0x95

    // check if the window is the current thread's desktop's main window (THREADINFO.rpdesk.pDeskInfo.spwnd)
    THREADINFO* eax = gptiCurrent; // mov eax,[win32k!gptiCurrent]
    DESKTOP* eax = eax.rpdesk; // mov eax,[eax+0x3c]
    DESKTOPINFO* eax = eax.pDeskInfo; // mov eax,[eax+0x4]
    if (eax.spwnd == esi) {// cmp [eax+0x8],esi
        // SetPropW(GetDesktopWindow(), ...) takes this path
        goto SetTheProperty; // jz SetTheProperty
    };

    // check if the process associated with the window (WND.pti.ppi) is NOT in the same session as the current process
    EPROCESS* eax = PsGetCurrentProcess(); // call win32k!_imp__PsGetCurrentProcess
    PROCESSINFO* eax = PsGetProcessWin32Process(eax); // call win32k!_imp__PsGetProcessWin32Process
    THREADINFO* ecx = esi.pti; // mov ecx,[esi+0x8]
    PROCESSINFO* ecx = ecx.ppi; // mov ecx,[ecx+0x2c]
    DWORD eax = eax.luidSession.LowPart; // mov eax,[eax+0x160]
    if (eax != ecx.luidSession.LowPart) {// cmp eax,[ecx+0x160]
        // SetPropW(GetConsoleWindow(), ...) takes this path
        goto Fail; // jnz win32k!NtUserSetProp+0x69;
    };
    //repeat for luidSession.HighPart

    SetTheProperty : {
        return InternalSetProp(esi, atom, value); // call win32k!InternalSetProp
    };

    Fail : {
        UserSetLastError(5); // call win32k!UserSetLastError
        return; // jmp win32k!NtUserSetProp+0x93
    };
};

所有实际工作都由 InternalSetProp 完成。 NtUserSetProp 的唯一目的是检查与 window 关联的进程是否 运行ning 在与调用功能。 XP 上的控制台 windows 由 csrss.exe 服务创建,该服务 运行 在与用户不同的 session 上,尽管windows 实际上在用户的 session 中打开,所以 NtUserSetProp 失败。它在用户session的桌面window(GetDesktopWindow())上设置属性是一个例外,它也是由csrss创建的。

在 Windows 7 及更高版本上,csrss 不再负责控制台管理 — conhost.exe 担任此角色,运行在用户session中,所以NtUserSetProp没有投诉。

解决方案

我选择简单地修补这个函数,这样它就永远不会失败,事实证明这很容易。我将我的 win32k.sys 作为数据文件加载到 OllyDbg 中,并将 window 切换到 'disassemble' 视图。最难的部分可能是定位函数——在我的 win32k.sys (crc32: edb43324) 中,NtUserSetProp 从偏移量 0x282F4 开始。

然后我删除(替换为 nops)luidSession 比较后的跳转(一个用于 luidSession.LowPart,一个用于 luidSession.HighPart),如下图所示:

完成补丁还需要一个额外的步骤:Windows 拒绝加载其 PE header 中不包含匹配校验和的内核模块,因此校验和必须更正以匹配我们修改后的文件。 ARTeam CheckSum Fixer 可以帮我们解决这个问题:

我们完成了。备份您的原始 win32k.sys 并将其替换为修补后的副本。

据我所知,这个补丁运行完美。我不确定等效的 'production-quality' 解决方案是什么——也许使用另一个 driver 来修补函数 in-memory,或者添加一个直接进入 InternalSetProp 的新系统调用];我当然愿意接受建议。