[英]Win32 - Launching a highestAvailable child process as a normal user process
假设您的 Windows 用户帐户在 Admin 组中,启用了 UAC,并且您正在以普通用户权限运行某个程序 A。 A从不要求提升,也从不接受。 现在假设 A 要启动程序 B,它的清单中具有最高可用。
如果 A 调用 CreateProcess(B),这将失败并显示错误 740(“需要提升”)
如果 A 调用 ShellExecuteEx(B),Windows 将显示一个 UAC 框,要求运行 B 提升。 用户可以说是,在这种情况下 B 将运行提升,或者说否,在这种情况下启动将失败。
我的问题是:有没有办法实现第三种选择,我们只需在没有提升的情况下启动 B?
原则上似乎这应该是可能的,因为“highestAvailable”意味着 B更喜欢在高程下运行,但完全能够在普通用户模式下运行。 但我想不出任何方法来实现它。 我已经尝试了各种使用令牌和 CreateProcessAsUser() 的方法,但这一切似乎都归结为:“highestAvailable”似乎不可改变地指的是用户帐户中固有的潜在特权,而不是任何明确表达的实际特权构造的令牌。
我希望实际上有某种方法可以使用 CreateProcessAsUser() 来做到这一点,而我只是错过了正确构造令牌的技巧。
更新 - 已解决:下面的 __COMPAT_LAYER=RunAsInvoker 解决方案效果很好。 不过,有一个警告。 这会强制子进程无条件地“作为调用者”运行:即使被调用的 exe 在其清单中指定了“requireAdministrator”,它也适用。 我认为当 exe 指定“requireAdministrator”时,最初的“需要提升高度”错误通常更可取。 我希望对标有“highestAvailable”的程序使用 RunAsInvoker 行为的全部原因是,此类程序明确表示“我可以在任一模式下正常运行”-因此,当使用管理员模式不方便时,让我们继续以普通用户模式运行。 但是“requireAdministrator”是另一回事:此类程序说“如果没有提升的权限,我将无法正常运行”。 对于此类程序来说,预先失败似乎比强制它们在未提升的情况下运行更好,这可能会使它们遇到权限/访问错误,而它们没有正确编程来处理。 所以我认为这里一个完整的通用解决方案需要检查应用程序清单,并且只有在清单显示“highestAvailable”时才应用 RunAsInvoker 强制。 一个更完整的解决方案是使用其他地方讨论的技术之一,当出现“requireAdministrator”程序时,为调用者提供调用 UAC 的选项,并为用户提供启动它的机会。 我可以想象一个 CreateProcessEx() 带有几个新标志,用于“将进程特权视为最高可用特权”和“如果需要提升则调用 UAC”。 (下面描述的另一种方法,挂钩 NTDLL!RtlQueryElevationFlags() 以告诉 CreateProcess() UAC 不可用,对于 requireAdministrator 程序具有完全相同的警告。)
(这可能说明 Windows 外壳甚至没有提供执行此操作的方法...直接从外壳启动 B 将为您提供 UAC 框,让您可以使用 Admin privs 启动或根本不启动。如果有无论如何,UAC 框可能会提供第三个按钮,无需特权即可启动。但话又说回来,这可能只是 UX 决定,因为第三个选项对平民来说太混乱了。)
(请注意,在 StackOverflow 和 Microsoft 开发支持网站上有很多帖子询问一个非常相似的场景,但不幸的是不适用于这里。这种场景是您有一个正在运行的父程序,并且它想要启动一个非提升的子进程。典型的例子是一个安装程序,像安装程序一样运行提升,它想在正常用户级别启动刚刚安装的程序,就在它退出之前。有很多发布的代码如何做到这一点,我已经基于其中一些技术进行了尝试,但这确实是一个不同的场景,解决方案在我的情况下不起作用。最大的区别是他们试图启动的子程序在这种情况下没有标有最高可用 - 孩子只是一个正常的程序,在正常情况下会在没有任何 UAC 参与的情况下启动。还有另一个区别,那就是在这些情况下,父母已经是运行提升,而在我的场景中,父级以普通用户级别运行; 这稍微改变了一些事情,因为在另一个场景中的父进程可以访问我无法使用的令牌上的一些特权操作,因为 A 本身没有提升。 但据我所知,那些特权令牌操作无论如何也无济于事; 事实上,孩子拥有最高可用标志,这是我的场景的关键元素。)
设置__COMPAT_LAYER
环境变量RunAsInvoker
在你的进程。 我认为这在任何地方都没有正式记录,但它一直工作到 Vista。
您还可以通过在注册表中的AppCompatFlags\\Layers
项下设置它来使其永久化。
可能的黑客解决方案从未提升的管理员用户(受限管理员)调用CreateProcess
以highestAvailable
清单中的highestAvailable
(或来自任何未提升用户的requireAdministrator
exe) - 这是钩子RtlQueryElevationFlags
调用并将返回的标志设置为 0。这是当前工作,但是当然没有任何受赠者可以在下一个版本的 Windows 中工作,如果有什么改变的话。 然而照原样。
对于钩子单次 api 调用 - 我们可以将硬件断点设置为函数地址和VEX 处理程序。 演示工作代码:
NTSTATUS NTAPI hookRtlQueryElevationFlags (DWORD* pFlags)
{
*pFlags = 0;
return 0;
}
PVOID pvRtlQueryElevationFlags;
LONG NTAPI OnVex(::PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP &&
ExceptionInfo->ExceptionRecord->ExceptionAddress == pvRtlQueryElevationFlags)
{
ExceptionInfo->ContextRecord->
#if defined(_X86_)
Eip
#elif defined (_AMD64_)
Rip
#else
#error not implemented
#endif
= (ULONG_PTR)hookRtlQueryElevationFlags;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
ULONG exec(PCWSTR lpApplicationName)
{
ULONG dwError = NOERROR;
if (pvRtlQueryElevationFlags = GetProcAddress(GetModuleHandle(L"ntdll"), "RtlQueryElevationFlags"))
{
if (PVOID pv = AddVectoredExceptionHandler(TRUE, OnVex))
{
::CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
ctx.Dr7 = 0x404;
ctx.Dr1 = (ULONG_PTR)pvRtlQueryElevationFlags;
if (SetThreadContext(GetCurrentThread(), &ctx))
{
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
if (CreateProcessW(lpApplicationName, 0, 0, 0, 0, 0, 0, 0, &si,&pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else
{
dwError = GetLastError();
}
ctx.Dr7 = 0x400;
ctx.Dr1 = 0;
SetThreadContext(GetCurrentThread(), &ctx);
}
else
{
dwError = GetLastError();
}
RemoveVectoredExceptionHandler(pv);
}
else
{
dwError = GetLastError();
}
}
else
{
dwError = GetLastError();
}
return dwError;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.