0

假设您的 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”。

(这可能表明 Windows shell 甚至不提供执行此操作的方法......直接从 shell 启动 B 会给您一个 UAC 框,让您可以使用 Admin privs 启动或根本不启动。如果有任何方式来实现它,UAC 框可能会提供第三个按钮来启动没有特权。但话又说回来,这可能只是一个 UX 决定,第三个选项对平民来说太混乱了。)

(请注意,在 StackOverflow 和 Microsoft 开发支持网站上有很多帖子询问一个看起来非常相似的场景,但不幸的是,这并不适用于此。这种场景是您有一个正在运行的父程序,并且它想要启动一个非提升的子进程.典型的例子是一个安装程序,像安装程序倾向于做的那样运行提升,它想要在正常用户级别启动它刚刚安装的程序,就在它退出之前.有很多关于发布的代码如何做到这一点,我的尝试基于其中一些技术,但这确实是一个不同的场景,解决方案在我的情况下不起作用.最大的区别是他们试图启动的子程序在这种情况下不是标有highestAvailable - child 只是一个正常程序,在正常情况下无需任何UAC 参与即可启动。还有另一个区别,那就是在这些场景中,父级已经在提升,而在我的场景中,父级以普通用户级别运行;这会稍微改变一些事情,因为在其他场景中的父进程可以访问我无法使用的令牌上的一些特权操作,因为 A 本身并没有提升。但据我所知,这些特权令牌操作无论如何都无济于事。事实上,孩子拥有最高可用标志,这是我的场景的关键元素。)

4

2 回答 2

1

__COMPAT_LAYER环境变量设置为RunAsInvoker在您的进程中。我认为这在任何地方都没有正式记录,但它一直可以追溯到 Vista。

AppCompatFlags\Layers您还可以通过在注册表中的键下设置它来使其永久化。

于 2018-05-11T21:56:02.890 回答
0

CreateProcess来自未提升的管理员用户(受限制的管理员)对highestAvailable清单中的 exe(或来自任何未提升的用户的exe)的可能的黑客解决方案调用requireAdministrator- 这是挂钩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;
}
于 2018-05-11T20:30:28.807 回答