Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
444 views
in Technique[技术] by (71.8m points)

c# - How to Start a Process Unelevated

My app runs as requestedExecutionLevel set to highestAvailable.

How do I run a process unelevated?

I tried the following but it didn't work:

Process.Start(new ProcessStartInfo {FileName = "foo.exe", Verb = "open"})

I have tried the following trust levels to start my process using Win32 API but none of them work correctly:

0
1260: This program is blocked by group policy. For more information, contact your system administrator.

0x1000
The application was unable to start correctly (0xc0000142). Click OK to close the application. 

0x10000
Process starts then hangs

0x20000
All options are not available

0x40000
Runs as admin

If I run tskill foo from my elevated app, it restarts foo with correct privileges.

What I need is a solution in which I don't have to specify the trust level. The process should start with the correct trust level automatically just like the tskill tool restarts foo.exe in the correct trust level. The user selects and runs foo.exe and so it can be anything.

If I can get the trust level of a process somehow, I can do this easily since foo.exe runs when my app can capture its trust level.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I had best results by cloning Explorer's token as follows:

var shellWnd = WinAPI.GetShellWindow();
if (shellWnd == IntPtr.Zero)
    throw new Exception("Could not find shell window");

uint shellProcessId;
WinAPI.GetWindowThreadProcessId(shellWnd, out shellProcessId);

var hShellProcess = WinAPI.OpenProcess(0x00000400 /* QueryInformation */, false, shellProcessId);

var hShellToken = IntPtr.Zero;
if (!WinAPI.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
    throw new Win32Exception();

uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var hToken = IntPtr.Zero;
WinAPI.DuplicateTokenEx(hShellToken, tokenAccess, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken);

var pi = new WinAPI.PROCESS_INFORMATION();
var si = new WinAPI.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
if (!WinAPI.CreateProcessWithTokenW(hToken, 0, null, cmdArgs, 0, IntPtr.Zero, null, ref si, out pi))
    throw new Win32Exception();

Alternative approach

Originally I went with drf's excellent answer, but expanded it somewhat. If the above (clone Explorer's token) is not to your liking, keep reading but see a gotcha at the very end.

When using drf's method as described, the process is started without administrative access, but it still has a high integrity level. A typical un-elevated process has a medium integrity level.

Try this: use Process Hacker to see the properties of the process started this way; you will see that PH considers the process to be elevated even though it doesn't have administrative access. Add an Integrity column and you'll see it's "High".

The fix is reasonably simple: after using SaferComputeTokenFromLevel, we need to change the token integrity level to Medium. The code to do this might look something like this (converted from MSDN sample):

// Get the Medium Integrity SID
if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
    throw new Win32Exception();

// Construct a structure describing the token integrity level
var TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pMediumIntegritySid;
pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);

// Modify the token
if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL,
                                (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>()
                                + WinAPI.GetLengthSid(pMediumIntegritySid)))
    throw new Win32Exception();

Alas, this still doesn't really solve the problem completely. The process won't have administrative access; it won't have a high integrity, but it will still have a token that's marked as "elevated".

Whether this is a problem for you or not I don't know, but it may have been why I ended up cloning Explorer's token in the end, as described at the start of this answer.


Here is my full source code (modified drf's answer), in all its P/Invoke glory:

var hSaferLevel = IntPtr.Zero;
var hToken = IntPtr.Zero;
var pMediumIntegritySid = IntPtr.Zero;
var pTIL = IntPtr.Zero;
var pi = new WinAPI.PROCESS_INFORMATION();
try
{
    var si = new WinAPI.STARTUPINFO();
    si.cb = Marshal.SizeOf(si);
    var processAttributes = new WinAPI.SECURITY_ATTRIBUTES();
    var threadAttributes = new WinAPI.SECURITY_ATTRIBUTES();
    var args = CommandRunner.ArgsToCommandLine(Args);

    if (!WinAPI.SaferCreateLevel(WinAPI.SaferScopes.User, WinAPI.SaferLevels.NormalUser, 1, out hSaferLevel, IntPtr.Zero))
        throw new Win32Exception();

    if (!WinAPI.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, WinAPI.SaferComputeTokenFlags.None, IntPtr.Zero))
        throw new Win32Exception();

    if (!WinAPI.ConvertStringSidToSid("S-1-16-8192", out pMediumIntegritySid))
        throw new Win32Exception();
    var TIL = new TOKEN_MANDATORY_LABEL();
    TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
    TIL.Label.Sid = pMediumIntegritySid;
    pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
    Marshal.StructureToPtr(TIL, pTIL, false);
    if (!WinAPI.SetTokenInformation(hToken, 25 /* TokenIntegrityLevel */, pTIL, (uint) Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + WinAPI.GetLengthSid(pMediumIntegritySid)))
        throw new Win32Exception();

    if (!WinAPI.CreateProcessAsUser(hToken, null, commandLine, ref processAttributes, ref threadAttributes, true, 0, IntPtr.Zero, null, ref si, out pi))
        throw new Win32Exception();
}
finally
{
    if (hToken != IntPtr.Zero && !WinAPI.CloseHandle(hToken))
        throw new Win32Exception();
    if (pMediumIntegritySid != IntPtr.Zero && WinAPI.LocalFree(pMediumIntegritySid) != IntPtr.Zero)
        throw new Win32Exception();
    if (pTIL != IntPtr.Zero)
        Marshal.FreeHGlobal(pTIL);
    if (pi.hProcess != IntPtr.Zero && !WinAPI.CloseHandle(pi.hProcess))
        throw new Win32Exception();
    if (pi.hThread != IntPtr.Zero && !WinAPI.CloseHandle(pi.hThread))
        throw new Win32Exception();
}

And here are the P/Invoke definitions you'll need in addition to those listed in drf's answer:

[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, int TokenInformationClass,
    IntPtr TokenInformation, UInt32 TokenInformationLength);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);

[DllImport("advapi32.dll")]
public static extern uint GetLengthSid(IntPtr pSid);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
    string StringSid,
    out IntPtr ptrSid);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LocalFree(IntPtr hMem);

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...