Win32 - 作为普通用户进程启动highestAvailable子进程

时间:2018-05-11 19:04:35

标签: winapi createprocess elevation createprocessasuser

假设您的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"它也适用。在它的清单中。我认为原来的#34;需要升高"当exe指定" requireAdministrator"时,错误通常是可取的。我希望RunAsInvoker行为的全部原因是标有" highestAvailable"是这样的程序明确地说"我可以在任何一种模式下正常运作" - 因此,当使用管理员模式不方便时,让我们继续并以正常用户模式运行。但是"要求管理员"是一个不同的问题:这样的程序说"我没有提升权限就能正常运作"。对于这些程序而言,最好先让它们失败,而不是强迫它们运行不升级,这可能会使它们遇到特权/访问错误,而这些错误是他们没有正确编程来处理的。所以我认为这里一个完整的,通用的解决方案需要检查应用程序清单,并且只有在清单显示" highestAvailable"时才应用RunAsInvoker强制。一个更完整的解决方案是使用其他地方讨论的技术之一,为调用者提供一个选项来调用UAC,当提供一个" requireAdministrator"程序并为用户提供升级的机会。我可以想象一个CreateProcessEx()封面带有几个新标志,用于"将进程权限视为最高可用权限"和#34;如果需要提升,则调用UAC"。 (下面描述的另一种方法,挂钩NTDLL!RtlQueryElevationFlags()告诉CreateProcess()UAC不可用,对于requireAdministrator程序也有同样的警告。)

(它可能告诉Windows shell甚至没有提供这样做的方法......直接从shell启动B会给你UAC框,让你可以使用Admin privs启动或者根本没有启动。如果有任何方法可以完成它,UAC盒子可能会提供第三个没有特权的启动按钮。但是,这可能只是一个用户体验决定,第三个选项对平民来说太混乱了。)< / p>

(请注意,StackOverflow上有很多帖子,微软开发人员支持网站询问一个非常类似的场景,遗憾的是这里不适用。那个场景是你有一个父节目&# 39; s运行升级,它想要启动一个非提升的子进程。规范的例子是一个安装程序,安装程序倾向于提升运行,它想要在正常用户级别之前启动它刚安装的程序它已退出了。有很多关于如何做到这一点的已发布代码,并且我已根据其中一些技术进行了尝试,但这实际上是一个不同的场景,而且这些解决方案不适用于我的情况。最大的区别是,他们在这种情况下尝试启动的子程序不是标记为highestAvailable - 这个孩子只是一个普通的程序,无需任何启动UAC在正常情况下参与。还有另一个不同之处,那就是在这些情况下,父进程已经提升,而在我的方案中,父进程正在以正常用户级别运行;这会稍微改变一些事情,因为在这个其他场景中的父进程可以访问我无法使用的令牌上的一些特权操作,因为A本身并没有提升。但据我所知,这些特权令牌操作无论如何都无济于事;事实上,孩子拥有最高的可用标志,这是我的情景的关键要素。)

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:0)

来自未提升的管理员用户(限制管理员)的可能的黑客解决方案调用CreateProcess,用于清单中的highestAvailable的exe(或来自requireAdministrator exe的任何非提升用户) - 这是hook RtlQueryElevationFlags调用并将返回的标志设置为0。 这是目前的工作,但当然没有任何受让人将在下一版本的Windows中工作,如果有什么变化。不过原样。

for hook single time api call - 我们可以将硬件断点设置为函数地址和VEX handler。演示工作代码:

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;
}