Как начать процесс без прав
Мое приложение работает как requestedExecutionLevel
установлен в highestAvailable
,
Как запустить процесс без изменений?
Я попробовал следующее, но это не сработало:
Process.Start(new ProcessStartInfo {FileName = "foo.exe", Verb = "open"})
Я пробовал следующие уровни доверия, чтобы запустить мой процесс с использованием Win32 API, но ни один из них не работает правильно:
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
Если я бегу tskill foo
из моего повышенного уровня приложение перезапускает foo с правильными привилегиями.
Что мне нужно, это решение, в котором мне не нужно указывать уровень доверия. Процесс должен начинаться с правильного уровня доверия автоматически, как tskill
инструмент перезапускается foo.exe
на правильном уровне доверия. Пользователь выбирает и запускает foo.exe
и так может быть что угодно.
Если я могу каким-то образом получить уровень доверия к процессу, я могу сделать это легко, так как foo.exe
запускается, когда мое приложение может зафиксировать уровень доверия.
2 ответа
Функции Win32 Security Management предоставляют возможность создавать ограниченный токен с обычными правами пользователя; с токеном вы можете позвонить CreateProcessAsUser
запустить процесс с этим токеном. Ниже приведено подтверждение концепции, которая запускает cmd.exe как обычный пользователь, независимо от того, выполняется ли процесс в контексте с повышенными правами.
// Initialize variables.
IntPtr hSaferLevel, hToken;
STARTUPINFO si = default(STARTUPINFO);
SECURITY_ATTRIBUTES processAttributes = default(SECURITY_ATTRIBUTES);
SECURITY_ATTRIBUTES threadAttributes = default(SECURITY_ATTRIBUTES);
PROCESS_INFORMATION pi;
si.cb = Marshal.SizeOf(si);
// The process to start (for demonstration, cmd.exe)
string ProcessName = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.System),
"cmd.exe");
// Create the restricted token info
if (!SaferCreateLevel(
SaferScopes.User,
SaferLevels.NormalUser, // Program will execute as a normal user
1, // required
out hSaferLevel,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// From the level create a token
if (!SaferComputeTokenFromLevel(
hSaferLevel,
IntPtr.Zero,
out hToken,
SaferComputeTokenFlags.None,
IntPtr.Zero))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Run the process with the restricted token
if (!CreateProcessAsUser(
hToken,
ProcessName,
null, ref processAttributes, ref threadAttributes,
true, 0, IntPtr.Zero, null,
ref si, out pi))
throw new Win32Exception(Marshal.GetLastWin32Error());
// Cleanup
if (!CloseHandle(pi.hProcess))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!CloseHandle(pi.hThread))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (!SaferCloseLevel(hSaferLevel))
throw new Win32Exception(Marshal.GetLastWin32Error());
Этот подход использует следующие функции Win32:
SaferIdentifyLevel
указать уровень идентичности (ограниченный, нормальный или повышенный). НастройкаlevelId
вSAFER_LEVELID_NORMALUSER
(0x20000) обеспечивает нормальный уровень пользователя.SaferComputeTokenFromLevel
создает токен для заданного уровня. ПереходяNULL
для параметра InAccessToken используется идентификатор текущего потока.CreateProcessAsUser
создает процесс с предоставленным токеном. Поскольку сеанс уже является интерактивным, для большинства параметров можно сохранить значения по умолчанию. (Третий параметр,lpCommandLine
может быть предоставлена в виде строки для указания командной строки.)CloseHandle
(Kernel32) иSaferCloseLevel
освободить выделенную память.
Наконец, код P/Invoke приведен ниже (в основном скопирован с pinvoke.net):
[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)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
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;
}
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[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 IntPtr outAccessToken,
SaferComputeTokenFlags dwFlags,
IntPtr lpReserved
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
У меня были лучшие результаты при клонировании токена Explorer следующим образом:
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();
Альтернативный подход
Первоначально я подошел с отличным ответом drf, но несколько расширил его. Если вышесказанное (клоновый токен Explorer) вам не по вкусу, продолжайте читать, но посмотрите на ошибку в самом конце.
При использовании метода drf, как описано, процесс запускается без административного доступа, но он все еще имеет высокий уровень целостности. Типичный невыполненный процесс имеет средний уровень целостности.
Попробуйте это: используйте Process Hacker, чтобы увидеть свойства процесса, запущенного таким образом; вы увидите, что PH считает процесс повышенным, даже если у него нет административного доступа. Добавьте столбец целостности, и вы увидите, что он "Высокий".
Исправление достаточно простое: после использования SaferComputeTokenFromLevel
нам нужно изменить уровень целостности токена на средний. Код для этого может выглядеть примерно так (преобразовано из примера MSDN):
// 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();
Увы, это все еще не решает проблему полностью. Процесс не будет иметь административного доступа; у него не будет высокой целостности, но у него все равно будет токен, помеченный как "повышенный".
Является ли это проблемой для вас или нет, я не знаю, но, возможно, именно поэтому я в конце концов клонировал токен Explorer, как описано в начале этого ответа.
Вот мой полный исходный код (модифицированный ответ drf) во всей красе P/Invoke:
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();
}
А вот определения P/Invoke, которые вам понадобятся в дополнение к перечисленным в ответе drf:
[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);
Раймонд Чен написал об этом в своем блоге:
Как я могу запустить процесс без повышенных прав из моего процесса с повышенными правами и наоборот?
Выполняя поиск в GitHub версии этого кода для C#, я нашел следующую реализацию в инструментах Microsoft Node.js для репозитория Visual Studio: SystemUtilities.cs (см.ExecuteProcessUnElevated
функция).
На случай, если файл исчезнет, вот его содержимое:
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.NodejsTools.SharedProject
{
/// <summary>
/// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
/// </summary>
internal class SystemUtility
{
/// <summary>
/// We are elevated and should launch the process unelevated. We can't create the
/// process directly without it becoming elevated. So to workaround this, we have
/// explorer do the process creation (explorer is typically running unelevated).
/// </summary>
internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
{
var shellWindows = (IShellWindows)new CShellWindows();
// Get the desktop window
object loc = CSIDL_Desktop;
object unused = new object();
int hwnd;
var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);
// Get the shell browser
var serviceGuid = SID_STopLevelBrowser;
var interfaceGuid = typeof(IShellBrowser).GUID;
var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);
// Get the shell dispatch
var dispatch = typeof(IDispatch).GUID;
var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
var shellDispatch = (IShellDispatch2)folderView.Application;
// Use the dispatch (which is unelevated) to launch the process for us
shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
}
/// <summary>
/// Interop definitions
/// </summary>
private const int CSIDL_Desktop = 0;
private const int SWC_DESKTOP = 8;
private const int SWFO_NEEDDISPATCH = 1;
private const int SW_SHOWNORMAL = 1;
private const int SVGIO_BACKGROUND = 0;
private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");
[ComImport]
[Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
private class CShellWindows
{
}
[ComImport]
[Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellWindows
{
[return: MarshalAs(UnmanagedType.IDispatch)]
object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
}
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
[ComImport]
[Guid("000214E2-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellBrowser
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // InsertMenusSB
void VTableGap04(); // SetMenuSB
void VTableGap05(); // RemoveMenusSB
void VTableGap06(); // SetStatusTextSB
void VTableGap07(); // EnableModelessSB
void VTableGap08(); // TranslateAcceleratorSB
void VTableGap09(); // BrowseObject
void VTableGap10(); // GetViewStateStream
void VTableGap11(); // GetControlWindow
void VTableGap12(); // SendControlMsg
IShellView QueryActiveShellView();
}
[ComImport]
[Guid("000214E3-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellView
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // TranslateAcceleratorA
void VTableGap04(); // EnableModeless
void VTableGap05(); // UIActivate
void VTableGap06(); // Refresh
void VTableGap07(); // CreateViewWindow
void VTableGap08(); // DestroyViewWindow
void VTableGap09(); // GetCurrentInfo
void VTableGap10(); // AddPropertySheetPages
void VTableGap11(); // SaveViewState
void VTableGap12(); // SelectItem
[return: MarshalAs(UnmanagedType.Interface)]
object GetItemObject(UInt32 aspectOfView, ref Guid riid);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IDispatch
{
}
[ComImport]
[Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellFolderViewDual
{
object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
}
[ComImport]
[Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IShellDispatch2
{
void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
}
}
}
Самым простым решением было бы запустить процесс с помощью explorer.exe. Это беспрепятственно запустит любой процесс. Вы можете просто запустить explorer.exe, используя
System.Diagnostics.Process.Start();
Имя файла будет "C:\Windows\explorer.exe", а аргументами будет исполняемый файл, который вы хотите запустить без повышения, в кавычках.
Пример:
Если бы я хотел запустить F:\folder\example.exe без повышенных прав, я бы сделал следующее:
using System.Diagnostics;
namespace example
{
class exampleClass
{
ProcessStartInfo exampleStartInfo = new ProcessStartInfo();
exampleStartInfo.FileName = "C:\\Windows\\explorer.exe";
exampleStartInfo.Arguments = "\"F:\\folder\\example.exe\"";
Process.Start(exampleStartInfo);
}
}
Это может не работать в старых версиях Windows, но, по крайней мере, работает на моем ноутбуке, поэтому, безусловно, работает на Windows 10.