我尝试在Windows API中使用SetProp功能,但在我的XP-32bit SP3系统上,当给定控制台的句柄时,它始终以ERROR_ACCESS_DENIED
(5)失败窗口,而在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"));
};
};
它确实会影响与当前进程无关的控制台窗口,而且我还没有发现任何可见的非控制台窗口导致此问题。
是什么给出的? SetProp的文档唯一说明的是
当UIPI [用户界面权限隔离]阻止属性更改时,GetLastError将返回5
但是UIPI在XP上并不存在,并且在具有UIPI的Windows 7上运行良好。
我在Wine和ReactOS实现中检查了函数的源代码,但似乎没有任何方式可以像这样。实际的Microsoft实现似乎主要只是系统调用(我的操作系统上的0x1213),所以我无法分析它。 (更新:win32k.sys NtUserSetProp disassembly)
如果有人能想到一个解决方法,我会非常有兴趣听到它。
额外代码:http://pastebin.com/RmJHxRPF - tests every window on the current desktop
作为参考,我面临这个问题的场景是:我正在编写一个桌面shell,并希望在窗口存在时存储元数据,只要窗口存在(即使我的shell)进程停止运行)。
答案 0 :(得分:0)
NtUserSetProp
中设置窗口属性的系统调用为%windir%\system32\win32k.sys
。这大致是它的作用(在伪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
的唯一目的是检查与窗口关联的进程是否在与调用该函数的进程相同的会话(PROCESSINFO.luidSession
)中运行。 XP上的控制台窗口由 csrss.exe 服务创建,该服务在与用户不同的会话上运行,尽管窗口实际上在用户会话中打开,因此NtUserSetProp
失败。它在用户会话的桌面窗口(GetDesktopWindow()
)上设置属性时例外,该窗口也是由csrss创建的。
在Windows 7及更高版本中,csrss不再负责控制台管理 - conhost.exe 填充此角色,该角色确实在用户的会话中运行,因此NtUserSetProp
没有投诉。
我选择简单地修补功能,因此它永远不会失败,实际上结果非常简单。我将我的win32k.sys加载到OllyDbg作为数据文件,并将窗口切换到“反汇编”视图。最困难的部分可能是定位函数 - 在我的win32k.sys(crc32:edb43324)中,NtUserSetProp
从偏移量0x282F4开始。
然后我删除了{替换为nop
s} luidSession
比较后的跳转(一个用于luidSession.LowPart
,一个用于luidSession.HighPart
),如下图所示:
完成修补程序还需要一个步骤:Windows拒绝加载其PE头中不包含匹配校验和的内核模块,因此必须更正校验和以匹配我们修改的文件。 ARTeam CheckSum Fixer可以为我们排序:
我们已经完成了。备份原始的win32k.sys并将其替换为修补后的副本。
据我所知,这个补丁正在完美运行。我不确定同样的“生产质量”解决方案是什么 - 可能使用另一个驱动程序来修补内存中的功能,或添加一个直接进入InternalSetProp
的新系统调用;我当然乐于接受建议。