简体   繁体   English

Win32 - 作为普通用户进程启动一个最高可用的子进程

[英]Win32 - Launching a highestAvailable child process as a normal user process

Suppose your Windows user account is in the Admin group, UAC is enabled, and you're running some program A with normal user privileges.假设您的 Windows 用户帐户在 Admin 组中,启用了 UAC,并且您正在以普通用户权限运行某个程序 A。 A never asks for elevation and never receives it. A从不要求提升,也从不接受。 Now suppose A wants to launch program B, which has highestAvailable in its manifest.现在假设 A 要启动程序 B,它的清单中具有最高可用。

  • If A calls CreateProcess(B), this will fail with error 740 ("elevation required")如果 A 调用 CreateProcess(B),这将失败并显示错误 740(“需要提升”)

  • If A calls ShellExecuteEx(B), Windows will display a UAC box asking to run B elevated.如果 A 调用 ShellExecuteEx(B),Windows 将显示一个 UAC 框,要求运行 B 提升。 The user can say Yes, in which case B will run elevated, or No, in which case the launch will fail.用户可以说是,在这种情况下 B 将运行提升,或者说否,在这种情况下启动将失败。

My question is: is there any way to achieve a third option, where we simply launch B without elevation?我的问题是:有没有办法实现第三种选择,我们只需在没有提升的情况下启动 B?

It seems like this should be possible in principle, since "highestAvailable" means that B prefers to run with elevation but is perfectly capable of running in normal user mode.原则上似乎这应该是可能的,因为“highestAvailable”意味着 B更喜欢在高程下运行,但完全能够在普通用户模式下运行。 But I can't figure out any way to accomplish it.但我想不出任何方法来实现它。 I've tried all sorts of things with tokens and CreateProcessAsUser(), but it all seems to come down to this: "highestAvailable" seems to unalterably refer to the latent privileges inherent in the user account, not the actual privileges expressed in any explicitly constructed token.我已经尝试了各种使用令牌和 CreateProcessAsUser() 的方法,但这一切似乎都归结为:“highestAvailable”似乎不可改变地指的是用户帐户中固有的潜在特权,而不是任何明确表达的实际特权构造的令牌。

I'm hoping that there actually is some way to use CreateProcessAsUser() to do this, and that I'm just missing the trick for properly constructing the token.我希望实际上有某种方法可以使用 CreateProcessAsUser() 来做到这一点,而我只是错过了正确构造令牌的技巧。

Update - solved: the __COMPAT_LAYER=RunAsInvoker solution below works nicely.更新 - 已解决:下面的 __COMPAT_LAYER=RunAsInvoker 解决方案效果很好。 One caveat, though.不过,有一个警告。 This coerces the subprocess to run "as invoker" unconditionally: it applies even if the exe being invoked specifies "requireAdministrator" in its manifest.这会强制子进程无条件地“作为调用者”运行:即使被调用的 exe 在其清单中指定了“requireAdministrator”,它也适用。 I think the original "elevation required" error is generally preferable when the exe specifies "requireAdministrator".我认为当 exe 指定“requireAdministrator”时,最初的“需要提升高度”错误通常更可取。 The whole reason I wanted the RunAsInvoker behavior for programs marked with "highestAvailable" is that such programs are expressly saying "I can function properly in either mode" - so let's go ahead and run in normal user mode when it's inconvenient to use Admin mode.我希望对标有“highestAvailable”的程序使用 RunAsInvoker 行为的全部原因是,此类程序明确表示“我可以在任一模式下正常运行”-因此,当使用管理员模式不方便时,让我们继续以普通用户模式运行。 But "requireAdministrator" is a different matter: such programs are saying "I can't function properly without elevated privileges".但是“requireAdministrator”是另一回事:此类程序说“如果没有提升的权限,我将无法正常运行”。 It seems better to fail up front for such programs than to force them to run un-elevated, which might make them encounter privilege/access errors that they're not properly programmed to handle.对于此类程序来说,预先失败似乎比强制它们在未提升的情况下运行更好,这可能会使它们遇到权限/访问错误,而它们没有正确编程来处理。 So I think a complete, general-purpose solution here would require checking the application manifest, and only applying the RunAsInvoker coercion if the manifest says "highestAvailable".所以我认为这里一个完整的通用解决方案需要检查应用程序清单,并且只有在清单显示“highestAvailable”时才应用 RunAsInvoker 强制。 An even completer solution would be to use one of the techniques discussed elsewhere to give the caller an option to invoke UAC when presented with a "requireAdministrator" program and offer the user a chance to launch it elevated.一个更完整的解决方案是使用其他地方讨论的技术之一,当出现“requireAdministrator”程序时,为调用者提供调用 UAC 的选项,并为用户提供启动它的机会。 I can imagine a CreateProcessEx() cover with a couple of new flags for "treat process privileges as highest available privileges" and "invoke UAC if elevation is required".我可以想象一个 CreateProcessEx() 带有几个新标志,用于“将进程特权视为最高可用特权”和“如果需要提升则调用 UAC”。 (The other approach described below, hooking NTDLL!RtlQueryElevationFlags() to tell CreateProcess() that UAC is unavailable, has exactly this same caveat with respect to requireAdministrator programs.) (下面描述的另一种方法,挂钩 NTDLL!RtlQueryElevationFlags() 以告诉 CreateProcess() UAC 不可用,对于 requireAdministrator 程序具有完全相同的警告。)

(It's probably telling that the Windows shell doesn't even offer a way to do this... launching B directly from the shell would give you the UAC box that lets you either launch with Admin privs or not launch at all. If there were any way to accomplish it, the UAC box might offer a third button to launch without privileges. But then again that could just be a UX decision that the third option is too confusing for civilians.) (这可能说明 Windows 外壳甚至没有提供执行此操作的方法...直接从外壳启动 B 将为您提供 UAC 框,让您可以使用 Admin privs 启动或根本不启动。如果有无论如何,UAC 框可能会提供第三个按钮,无需特权即可启动。但话又说回来,这可能只是 UX 决定,因为第三个选项对平民来说太混乱了。)

(Note that there are quite a lot of posts on StackOverflow and the Microsoft dev support sites asking about a very similar-seeming scenario that unfortunately doesn't apply here. That scenario is where you have a parent program that's running elevated, and it wants to launch a non-elevated child process. The canonical example is an installer, running elevated as installers tend to do, that wants to launch the program it just installed, at normal user level, just before it exits. There's lots of posted code about how to do that, and I've based my attempts on some of those techniques, but this is really a different scenario and the solutions don't work in my situation. The big difference is that the child program they're attempting to launch in this case isn't marked with highestAvailable - the child is just a normal program that would launch without any UAC involvement under normal circumstances. There's another difference as well, which is that in those scenarios, the parent is already (请注意,在 StackOverflow 和 Microsoft 开发支持网站上有很多帖子询问一个非常相似的场景,但不幸的是不适用于这里。这种场景是您有一个正在运行的父程序,并且它想要启动一个非提升的子进程。典型的例子是一个安装程序,像安装程序一样运行提升,它想在正常用户级别启动刚刚安装的程序,就在它退出之前。有很多发布的代码如何做到这一点,我已经基于其中一些技术进行了尝试,但这确实是一个不同的场景,解决方案在我的情况下不起作用。最大的区别是他们试图启动的子程序在这种情况下没有标有最高可用 - 孩子只是一个正常的程序,在正常情况下会在没有任何 UAC 参与的情况下启动。还有另一个区别,那就是在这些情况下,父母已经是 running elevated, whereas in my scenario the parent is running as normal user level;运行提升,而在我的场景中,父级以普通用户级别运行; that changes things slightly because the parent process in this other scenario has access to a few privileged operations on tokens that I can't use because A isn't itself elevated.这稍微改变了一些事情,因为在另一个场景中的父进程可以访问我无法使用的令牌上的一些特权操作,因为 A 本身没有提升。 But as far as I can tell those privileged token operations wouldn't help anyway;但据我所知,那些特权令牌操作无论如何也无济于事; it's the fact that the child has the highestAvailable flag that's the key element of my scenario.)事实上,孩子拥有最高可用标志,这是我的场景的关键元素。)

Set the __COMPAT_LAYER environment variable toRunAsInvoker in your process.设置__COMPAT_LAYER环境变量RunAsInvoker在你的进程。 I don't think this is formally documented anywhere but it works all the way back to Vista.我认为这在任何地方都没有正式记录,但它一直工作到 Vista。

You can also make it permanent by setting the it under the AppCompatFlags\\Layers key in the registry.您还可以通过在注册表中的AppCompatFlags\\Layers项下设置它来使其永久化。

the possible hack solution call CreateProcess from not elevated admin user (restricted admin) for exe with highestAvailable in manifest (or from any not elevated user for requireAdministrator exe) - this is hook RtlQueryElevationFlags call and set returned flags to 0. this is currently work, but of course no any grantee that will be work in next versions of windows, if something changed.可能的黑客解决方案从未提升的管理员用户(受限管理员)调用CreateProcesshighestAvailable清单中的highestAvailable (或来自任何未提升用户的requireAdministrator exe) - 这是钩子RtlQueryElevationFlags调用并将返回的标志设置为 0。这是当前工作,但是当然没有任何受赠者可以在下一个版本的 Windows 中工作,如果有什么改变的话。 however as is.然而照原样。

for hook single time api call - we can set hardware breakpoint to function address and VEX handler .对于钩子单次 api 调用 - 我们可以将硬件断点设置为函数地址和VEX 处理程序 demo working code:演示工作代码:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM