问题
I am creating an automatic updater for an application. The application is started by the user, and runs without administrator privileges. The autoupdater is started with administrator privileges, and kills the application before it downloads the new files.
The problem comes when I want to start the updated application after the autoupdater is finished. If I use regular System.Diagnostics.Process.Start(file), the application starts with administrator privileges too, and it has to run on the current user to work as intended.
So, how can I make the autoupdater start the application as the current user instead of the administrator?
I have tried using the following:
var pSI = new ProcessStartInfo() {
UseShellExecute = false,
UserName = Environment.UserName,
FileName = file
};
System.Diagnostics.Process.Start(pSI);
But this throws the error \"Invalid user name or password\". I have checked that the username is correct, and I understand that the password probably is invalid, as I have not included it. But it is not an option to ask the user to input his/her password, since the entire reason to start the application automatically is to make it easier for the user.
Any suggestions?
回答1:
What you are trying to achieve cannot be done very easily and is not supported. However, it is possible using a modicum of hacking. Aaron Margosis wrote an article describing one technique.
To quote the pertinent section, you will need to carry out these steps:
- Enable the SeIncreaseQuotaPrivilege in your current token
- Get an HWND representing the desktop shell (GetShellWindow)
- Get the Process ID (PID) of the process associated with that window (GetWindowThreadProcessId)
- Open that process (OpenProcess)
- Get the access token from that process (OpenProcessToken)
- Make a primary token with that token (DuplicateTokenEx)
- Start the new process with that primary token (CreateProcessWithTokenW)
The article contains a download link for some demo C++ source from which it should be simple enough to translate to C#.
回答2:
C# code for Aaron Margosis article:
private static void RunAsDesktopUser(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName));
// To start process as shell user you will need to carry out these steps:
// 1. Enable the SeIncreaseQuotaPrivilege in your current token
// 2. Get an HWND representing the desktop shell (GetShellWindow)
// 3. Get the Process ID(PID) of the process associated with that window(GetWindowThreadProcessId)
// 4. Open that process(OpenProcess)
// 5. Get the access token from that process (OpenProcessToken)
// 6. Make a primary token with that token(DuplicateTokenEx)
// 7. Start the new process with that primary token(CreateProcessWithTokenW)
var hProcessToken = IntPtr.Zero;
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.)
try
{
var process = GetCurrentProcess();
if (!OpenProcessToken(process, 0x0020, ref hProcessToken))
return;
var tkp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new LUID_AND_ATTRIBUTES[1]
};
if (!LookupPrivilegeValue(null, "SeIncreaseQuotaPrivilege", ref tkp.Privileges[0].Luid))
return;
tkp.Privileges[0].Attributes = 0x00000002;
if (!AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero))
return;
}
finally
{
CloseHandle(hProcessToken);
}
// Get an HWND representing the desktop shell.
// CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and
// restarted elevated.
var hwnd = GetShellWindow();
if (hwnd == IntPtr.Zero)
return;
var hShellProcess = IntPtr.Zero;
var hShellProcessToken = IntPtr.Zero;
var hPrimaryToken = IntPtr.Zero;
try
{
// Get the PID of the desktop shell process.
uint dwPID;
if (GetWindowThreadProcessId(hwnd, out dwPID) == 0)
return;
// Open the desktop shell process in order to query it (get the token)
hShellProcess = OpenProcess(ProcessAccessFlags.QueryInformation, false, dwPID);
if (hShellProcess == IntPtr.Zero)
return;
// Get the process token of the desktop shell.
if (!OpenProcessToken(hShellProcess, 0x0002, ref hShellProcessToken))
return;
var dwTokenRights = 395U;
// Duplicate the shell's process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
return;
// Start the target process with the new token.
var si = new STARTUPINFO();
var pi = new PROCESS_INFORMATION();
if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", 0, IntPtr.Zero, Path.GetDirectoryName(fileName), ref si, out pi))
return;
}
finally
{
CloseHandle(hShellProcessToken);
CloseHandle(hPrimaryToken);
CloseHandle(hShellProcess);
}
}
#region Interop
private struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[Flags]
private enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
private enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
private enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private 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;
}
[DllImport("kernel32.dll", ExactSpelling = true)]
private static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LookupPrivilegeValue(string host, string name, ref LUID pluid);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TOKEN_PRIVILEGES newst, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("user32.dll")]
private static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr phNewToken);
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcessWithTokenW(IntPtr hToken, int dwLogonFlags, string lpApplicationName, string lpCommandLine, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
#endregion
回答3:
Presuming that you are signalling the application to shut down cleanly rather than terminating it, and if you are still able to make changes to the application before releasing your updater, one simple solution would be to have the application launch an interim process before exiting. You could create the executable for the interim process in a temporary location. When the update is finished, signal the interim process to relaunch the application and exit. That way, everything happens naturally and you don't have to mess about.
Another option would be to use OpenProcess, OpenProcessToken, and DuplicateToken to get a copy of the application's security token before killing it. You can then use CreateProcessAsUser to relaunch the application in the original context.
Both of these approaches should work even if the updater is running under a different account and/or in a different session to the application.
回答4:
This is a really old question that I saw years ago and have revisited now. Since its the first google search result... I'll post my answer here.
All the solutions that I have found are extremely complex and ridiculous. Over the years I have stumbled upon a solution that I have not seen documented anywhere, and haven't really shared until now.
The code is very simple... Essentially we are writing a batch file with the name/path of the executable of the process you want to run, with whatever arguments you want. Then we spawn an explorer.exe process with the path to the batch file...
File.WriteAllText(@"C:\test.bat", @"C:\test.exe -randomArgs");
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = @"C:\test.bat",
UseShellExecute = true,
Verb = "runas",
WindowStyle = ProcessWindowStyle.Hidden
}
};
proc.Start();
The explorer process that we spawn is immediately killed off by the operating system, However! The root explorer.exe process running runs the batch file! You can give explorer.exe the name of your executable and it will do the same thing, however that method does not support arguments.
For all I know this is a bug or undocumented feature. However I cannot imagine how it could be used maliciously, as it allows de-elevation of permissions... This works in windows 7/8/8.1/10.
回答5:
There is a project on codeplex called User Account Control Helpers.
The project provides a library for interaction with the UAC mechanism.
In the library you will find a class called UserAccountControl
. The class
has a static method called CreateProcessAsStandardUser
to start a
process from an elevated process with standard user rights.
In short, the functions opens the process token of the desktop shell process. Then, it duplicates that token to get a primary token. This token is then used to start the new process under the logged on user.
For more information please read the following blog post from Aaron Margosis.
回答6:
I faced the similar issue.In the processstartinfo there is a password field,the catch is that you have to supply the password as a secure string.So the code would be like:
System.Security.SecureString password = new System.Security.SecureString();
password.AppendChar('c1');
//append the all characters of your password, you could probably use a loop and then,
Process p =new Process();
p.UseShellExecute = false;
p.UserName = Environment.UserName;
p.FileName = file ;
p.Sassword=password;
p.Start();
回答7:
Practical VB.NET code to start a process with default shell permissions from elevated parent process
#Region "References"
Imports System.Runtime.InteropServices
#End Region
Public Class LaunchProcess
#Region "Native Methods"
<DllImport("User32.dll", SetLastError:=True)> Private Shared Function GetShellWindow() As IntPtr
End Function
<DllImport("advapi32.dll", SetLastError:=True)> Private Shared Function OpenProcessToken(ByVal ProcessHandle As IntPtr, ByVal DesiredAccess As Integer, ByRef TokenHandle As IntPtr) As Boolean
End Function
<DllImport("user32.dll", SetLastError:=True)> Private Shared Function GetWindowThreadProcessId(ByVal hwnd As IntPtr, ByRef lpdwProcessId As IntPtr) As Integer
End Function
<DllImport("kernel32.dll")> Private Shared Function OpenProcess(ByVal dwDesiredAccess As UInteger, <MarshalAs(UnmanagedType.Bool)> ByVal bInheritHandle As Boolean, ByVal dwProcessId As IntPtr) As IntPtr
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function DuplicateTokenEx( _
ByVal ExistingTokenHandle As IntPtr, _
ByVal dwDesiredAccess As UInt32, _
ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
ByVal ImpersonationLevel As Integer, _
ByVal TokenType As Integer, _
ByRef DuplicateTokenHandle As System.IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> Private Shared Function LookupPrivilegeValue(lpSystemName As String, lpName As String, ByRef lpLuid As LUID) As Boolean
End Function
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function AdjustTokenPrivileges( _
ByVal TokenHandle As IntPtr, _
ByVal DisableAllPrivileges As Boolean, _
ByRef NewState As TOKEN_PRIVILEGES, _
ByVal BufferLengthInBytes As Integer, _
ByRef PreviousState As TOKEN_PRIVILEGES, _
ByRef ReturnLengthInBytes As Integer _
) As Boolean
End Function
<DllImport("advapi32", SetLastError:=True, CharSet:=CharSet.Unicode)> Private Shared Function CreateProcessWithTokenW(hToken As IntPtr, dwLogonFlags As Integer, lpApplicationName As String, lpCommandLine As String, dwCreationFlags As Integer, lpEnvironment As IntPtr, lpCurrentDirectory As IntPtr, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
End Function
#End Region
#Region "Structures"
<StructLayout(LayoutKind.Sequential)> Private Structure SECURITY_ATTRIBUTES
Friend nLength As Integer
Friend lpSecurityDescriptor As IntPtr
Friend bInheritHandle As Integer
End Structure
Private Structure TOKEN_PRIVILEGES
Friend PrivilegeCount As Integer
Friend TheLuid As LUID
Friend Attributes As Integer
End Structure
Private Structure LUID
Friend LowPart As UInt32
Friend HighPart As UInt32
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> Private Structure STARTUPINFO
Friend cb As Integer
Friend lpReserved As String
Friend lpDesktop As String
Friend lpTitle As String
Friend dwX As Integer
Friend dwY As Integer
Friend dwXSize As Integer
Friend dwYSize As Integer
Friend dwXCountChars As Integer
Friend dwYCountChars As Integer
Friend dwFillAttribute As Integer
Friend dwFlags As Integer
Friend wShowWindow As Short
Friend cbReserved2 As Short
Friend lpReserved2 As Integer
Friend hStdInput As Integer
Friend hStdOutput As Integer
Friend hStdError As Integer
End Structure
Private Structure PROCESS_INFORMATION
Friend hProcess As IntPtr
Friend hThread As IntPtr
Friend dwProcessId As Integer
Friend dwThreadId As Integer
End Structure
#End Region
#Region "Enumerations"
Private 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
End Enum
#End Region
#Region "Constants"
Private Const SE_PRIVILEGE_ENABLED = &H2L
Private Const PROCESS_QUERY_INFORMATION = &H400
Private Const TOKEN_ASSIGN_PRIMARY = &H1
Private Const TOKEN_DUPLICATE = &H2
Private Const TOKEN_IMPERSONATE = &H4
Private Const TOKEN_QUERY = &H8
Private Const TOKEN_QUERY_SOURCE = &H10
Private Const TOKEN_ADJUST_PRIVILEGES = &H20
Private Const TOKEN_ADJUST_GROUPS = &H40
Private Const TOKEN_ADJUST_DEFAULT = &H80
Private Const TOKEN_ADJUST_SESSIONID = &H100
Private Const SecurityImpersonation = 2
Private Const TokenPrimary = 1
Private Const SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"
#End Region
#Region "Methods"
Friend Shared Sub LaunchFile(ByVal FilePath As String, ByVal IsWaitToFinish As Boolean)
Try
'Enable the SeIncreaseQuotaPrivilege in current token
Dim HPrcsToken As IntPtr = Nothing
OpenProcessToken(Process.GetCurrentProcess.Handle, TOKEN_ADJUST_PRIVILEGES, HPrcsToken)
Dim TokenPrvlgs As TOKEN_PRIVILEGES
TokenPrvlgs.PrivilegeCount = 1
LookupPrivilegeValue(Nothing, SE_INCREASE_QUOTA_NAME, TokenPrvlgs.TheLuid)
TokenPrvlgs.Attributes = SE_PRIVILEGE_ENABLED
AdjustTokenPrivileges(HPrcsToken, False, TokenPrvlgs, 0, Nothing, Nothing)
'Get window handle representing the desktop shell
Dim HShellWnd As IntPtr = GetShellWindow()
'Get the ID of the desktop shell process
Dim ShellPID As IntPtr
GetWindowThreadProcessId(HShellWnd, ShellPID)
'Open the desktop shell process in order to get the process token
Dim HShellPrcs As IntPtr = OpenProcess(PROCESS_QUERY_INFORMATION, False, ShellPID)
Dim HShellPrcSToken As IntPtr = Nothing
Dim HPrimaryToken As IntPtr = Nothing
'Get the process token of the desktop shell
OpenProcessToken(HShellPrcs, TOKEN_DUPLICATE, HShellPrcSToken)
'Duplicate the shell's process token to get a primary token
Dim TokenRights As UInteger = TOKEN_QUERY Or TOKEN_ASSIGN_PRIMARY Or TOKEN_DUPLICATE Or TOKEN_ADJUST_DEFAULT Or TOKEN_ADJUST_SESSIONID
DuplicateTokenEx(HShellPrcSToken, TokenRights, Nothing, SecurityImpersonation, TokenPrimary, HPrimaryToken)
Dim StartInfo As STARTUPINFO = Nothing
Dim PrcsInfo As PROCESS_INFORMATION = Nothing
StartInfo.cb = Marshal.SizeOf(StartInfo)
Dim IsSuccessed As Boolean = CreateProcessWithTokenW(HPrimaryToken, 1, FilePath, "", 0, Nothing, Nothing, StartInfo, PrcsInfo)
If IsSuccessed = True Then
If IsWaitToFinish = True Then
Try
Dim Prcs As Process = Process.GetProcessById(PrcsInfo.dwProcessId)
Prcs.WaitForExit()
Catch ex As Exception
End Try
End If
Else
'Could not launch process with shell token may be the process needs admin rights to launch, we will try to launch it with default parent process permissions.
Dim Prcs As New Process
Prcs.StartInfo.FileName = FilePath
Prcs.Start()
If IsWaitToFinish = True Then Prcs.WaitForExit()
End If
Catch ex As Exception
End Try
End Sub
#End Region
End Class
LaunchProcess class usage
LaunchProcess.LaunchFile("C:\Program Files\Test\text.exe", False)
You may use Telerik code converter to convert code to C# http://converter.telerik.com/
回答8:
You can use CreateProcessWithLogonW function or similar CreateProcessAsUser and CreateProcessWithTokenW.
回答9:
As the writer describe this problem doesn't solved . I have the same problem now I wanna describe how I solved this.
As you know it is not that easy but the best solution is that you force computer to start the "*.exe" file by another non-admin process . What I do was creating a task on Task Scheduler without Highest Privilege
Parameters, It means making a time or run this task manually.
It seems stupid but it seems there is no way.
You can see this link that describe how to create a new Task on windows task scheduler/
来源:https://stackoverflow.com/questions/11169431/how-to-start-a-new-process-without-administrator-privileges-from-a-process-with