I want to start a non-elevated process from an elevated one. I've accomplished this using SaferComputeTokenFromLevel. It appears to be working,since:
C:\>whoami /groups | findstr BUILTIN\Administrators
BUILTIN\Administrators Alias S-1-5-32-544 Group used for deny only
Deny only is there. So I am not admin.
- 1) Why the (supodsedly) non-admin process Console Host Window shows as "Administrator:" in the title?
I even used SetTokenInformation
to set it to medium integrity. It works but still showing as 'Administrator' even if it's not admin.
Then, if that non-admin process wants to start an elevated process again via ShellExecute
with Verb=RunAs
(with powershell -C Start-Process -Verb RunAs -FilePath CMD.EXE
) the UAC popup doesn't appears, and the process is started but not elevated, not-admin.
- 2) Why does this happens? (linked token maybe?)
- 3) Finally, how can I launch a non-elevated process, that can use the
RunAs
verb to trigger an UAC and elevate succesfully?
Full Repro code (Run as admin!)
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices;
namespace SaferRepro
{
class Program
{
static void Main(string[] args)
{
string appToRun = "CMD.EXE";
string arguments = String.Empty;
bool newWindow = true, hidden = false;
var startupFolder = Environment.CurrentDirectory;
int mediumIntegrity = 8192;
using (var newToken = GetTokenFromSaferApi())
{
AdjustedTokenIntegrity(newToken, mediumIntegrity); // optional, launch as medium integrity process.
var process = StartWithToken(newToken, appToRun, arguments, startupFolder, newWindow, hidden);
GetProcessWaitHandle(process.DangerousGetHandle()).WaitOne();
}
}
private static SafeTokenHandle GetTokenFromSaferApi()
{
IntPtr hSaferLevel;
SafeTokenHandle hToken;
SaferLevels level = SaferLevels.NormalUser;
if (!NativeMethods.SaferCreateLevel(SaferScopes.User, level, 1, out hSaferLevel, IntPtr.Zero))
throw new Win32Exception();
if (!NativeMethods.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, SaferComputeTokenFlags.None, IntPtr.Zero))
throw new Win32Exception();
if (!NativeMethods.SaferCloseLevel(hSaferLevel))
throw new Win32Exception();
return hToken;
}
private static SafeProcessHandle StartWithToken(SafeTokenHandle newToken, string appToRun, string args, string startupFolder, bool newWindow, bool hidden)
{
var si = new STARTUPINFO();
if (newWindow)
{
si.dwFlags = 0x00000001; // STARTF_USESHOWWINDOW
si.wShowWindow = (short)(hidden ? 0 : 1);
}
si.cb = Marshal.SizeOf(si);
var pi = new PROCESS_INFORMATION();
uint dwCreationFlags = newWindow ? (uint)0x00000010 /*CREATE_NEW_CONSOLE*/: 0;
if (!NativeMethods.CreateProcessAsUser(newToken, null, $"{appToRun} {args}",
IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, IntPtr.Zero, startupFolder, ref si,
out pi))
{
throw new Win32Exception();
}
NativeMethods.CloseHandle(pi.hThread);
return new SafeProcessHandle(pi.hProcess, true);
}
private static bool AdjustedTokenIntegrity(SafeTokenHandle newToken, int integrityLevel)
{
string integritySid = "S-1-16-" + (integrityLevel.ToString(CultureInfo.InvariantCulture));
IntPtr pIntegritySid;
if (!NativeMethods.ConvertStringSidToSid(integritySid, out pIntegritySid))
return false;
TOKEN_MANDATORY_LABEL TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pIntegritySid;
var pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
if (!NativeMethods.SetTokenInformation(newToken.DangerousGetHandle(),
TOKEN_INFORMATION_CLASS.TokenIntegrityLevel,
pTIL,
(uint)(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + NativeMethods.GetLengthSid(pIntegritySid))))
return false;
return true;
}
public static System.Threading.AutoResetEvent GetProcessWaitHandle(IntPtr processHandle) =>
new System.Threading.AutoResetEvent(false)
{
SafeWaitHandle = new SafeWaitHandle(processHandle, ownsHandle: false)
};
}
internal class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
internal SafeTokenHandle(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
private SafeTokenHandle() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(base.handle);
}
}
static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CreateProcessAsUser(
SafeTokenHandle hToken,
string applicationName,
string commandLine,
IntPtr pProcessAttributes,
IntPtr pThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr pEnvironment,
string currentDirectory,
ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION processInformation);
#region Safer
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCreateLevel(
SaferScopes dwScopeId,
SaferLevels dwLevelId,
int OpenFlags,
out IntPtr pLevelHandle,
IntPtr lpReserved);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCloseLevel(
IntPtr pLevelHandle);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(
IntPtr levelHandle,
IntPtr inAccessToken,
out SafeTokenHandle outAccessToken,
SaferComputeTokenFlags dwFlags,
IntPtr lpReserved
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid
);
[DllImport("advapi32.dll")]
public static extern int GetLengthSid(IntPtr pSid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
IntPtr TokenInformation, UInt32 TokenInformationLength);
#endregion
}
[Flags]
public enum SaferLevels : uint
{
Disallowed = 0,
Untrusted = 0x1000,
Constrained = 0x10000,
NormalUser = 0x20000,
FullyTrusted = 0x40000
}
[Flags]
public enum SaferComputeTokenFlags : uint
{
None = 0x0,
NullIfEqual = 0x1,
CompareOnly = 0x2,
MakeIntert = 0x4,
WantFlags = 0x8
}
[Flags]
public enum SaferScopes : uint
{
Machine = 1,
User = 2
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_MANDATORY_LABEL
{
public SID_AND_ATTRIBUTES Label;
}
[StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
public IntPtr Sid;
public uint Attributes;
}
// Integrity Levels
public enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1, TokenGroups, TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource, TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids, TokenSessionId, TokenGroupsAndPrivileges, TokenSessionReference, TokenSandBoxInert, TokenAuditPolicy, TokenOrigin, TokenElevationType, TokenLinkedToken, TokenElevation, TokenHasRestrictions, TokenAccessInformation, TokenVirtualizationAllowed, TokenVirtualizationEnabled, TokenIntegrityLevel, TokenUIAccess, TokenMandatoryPolicy, TokenLogonSid, MaxTokenInfoClass
}
}
WhoAmI
not to be trusted. UsingProcess Explorer
the deny only is there as well. – Gerardo GrignoliSeTcbPrivilege
on an elevated process. Wondering if impersonating system would work. – Gerardo Grignoli