2018-10-02 22:55:53 +00:00
using Microsoft.Win32.SafeHandles ;
using System ;
2018-12-13 01:15:25 +00:00
using System.Collections ;
2018-10-02 22:55:53 +00:00
using System.Collections.Generic ;
using System.IO ;
2018-12-13 01:15:25 +00:00
using System.Runtime.ConstrainedExecution ;
2018-10-02 22:55:53 +00:00
using System.Runtime.InteropServices ;
using System.Security.AccessControl ;
using System.Security.Principal ;
using System.Text ;
2018-12-13 01:15:25 +00:00
using Ansible.Process ;
using System.Linq ;
2018-10-02 22:55:53 +00:00
namespace Ansible.Become
{
2018-12-13 01:15:25 +00:00
internal class NativeHelpers
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct KERB_S4U_LOGON
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
public UInt32 MessageType ;
public UInt32 Flags ;
public LSA_UNICODE_STRING ClientUpn ;
public LSA_UNICODE_STRING ClientRealm ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct LSA_STRING
{
public UInt16 Length ;
public UInt16 MaximumLength ;
[MarshalAs(UnmanagedType.LPStr)] public string Buffer ;
public static implicit operator string ( LSA_STRING s )
{
return s . Buffer ;
}
public static implicit operator LSA_STRING ( string s )
{
if ( s = = null )
s = "" ;
LSA_STRING lsaStr = new LSA_STRING
{
Buffer = s ,
Length = ( UInt16 ) s . Length ,
MaximumLength = ( UInt16 ) ( s . Length + 1 ) ,
} ;
return lsaStr ;
}
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LSA_UNICODE_STRING
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
public UInt16 Length ;
public UInt16 MaximumLength ;
public IntPtr Buffer ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart ;
public Int32 HighPart ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public static explicit operator UInt64 ( LUID l )
{
return ( UInt64 ) ( ( UInt64 ) l . HighPart < < 32 ) | ( UInt64 ) l . LowPart ;
}
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid ;
public UInt32 Attributes ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_LOGON_SESSION_DATA
{
public UInt32 Size ;
public LUID LogonId ;
public LSA_UNICODE_STRING UserName ;
public LSA_UNICODE_STRING LogonDomain ;
public LSA_UNICODE_STRING AuthenticationPackage ;
public SECURITY_LOGON_TYPE LogonType ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
public IntPtr Sid ;
public int Attributes ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount ;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES [ ] Privileges ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_SOURCE
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char [ ] SourceName ;
public LUID SourceIdentifier ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_STATISTICS
{
public LUID TokenId ;
public LUID AuthenticationId ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_USER
{
public SID_AND_ATTRIBUTES User ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0 ,
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[Flags]
public enum ProcessAccessFlags : uint
{
PROCESS_QUERY_INFORMATION = 0x00000400 ,
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityImpersonation ,
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public enum SECURITY_LOGON_TYPE
{
System = 0 , // Used only by the Sytem account
Interactive = 2 ,
Network ,
Batch ,
Service ,
Proxy ,
Unlock ,
NetworkCleartext ,
NewCredentials ,
RemoteInteractive ,
CachedInteractive ,
CachedRemoteInteractive ,
CachedUnlock
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public enum TOKEN_TYPE
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
TokenPrimary = 1 ,
TokenImpersonation
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
public enum TokenElevationType
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
TokenElevationTypeDefault = 1 ,
TokenElevationTypeFull ,
TokenElevationTypeLimited
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
public enum TokenInformationClass
{
TokenUser = 1 ,
TokenPrivileges = 3 ,
TokenStatistics = 10 ,
TokenElevationType = 18 ,
TokenLinkedToken = 19 ,
}
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
internal class NativeMethods
2018-10-02 22:55:53 +00:00
{
[DllImport("advapi32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern bool AllocateLocallyUniqueId (
out NativeHelpers . LUID Luid ) ;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle (
IntPtr hObject ) ;
2018-10-02 22:55:53 +00:00
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
2018-12-13 01:15:25 +00:00
public static extern bool CreateProcessWithTokenW (
SafeNativeHandle hToken ,
2018-10-02 22:55:53 +00:00
LogonFlags dwLogonFlags ,
2018-12-13 01:15:25 +00:00
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName ,
2018-10-02 22:55:53 +00:00
StringBuilder lpCommandLine ,
2018-12-13 01:15:25 +00:00
Process . NativeHelpers . ProcessCreationFlags dwCreationFlags ,
SafeMemoryBuffer lpEnvironment ,
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory ,
Process . NativeHelpers . STARTUPINFOEX lpStartupInfo ,
out Process . NativeHelpers . PROCESS_INFORMATION lpProcessInformation ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateTokenEx (
SafeNativeHandle hExistingToken ,
TokenAccessLevels dwDesiredAccess ,
IntPtr lpTokenAttributes ,
NativeHelpers . SECURITY_IMPERSONATION_LEVEL ImpersonationLevel ,
NativeHelpers . TOKEN_TYPE TokenType ,
out SafeNativeHandle phNewToken ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[DllImport("kernel32.dll")]
public static extern UInt32 GetCurrentThreadId ( ) ;
2018-10-02 22:55:53 +00:00
[DllImport("user32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern NoopSafeHandle GetProcessWindowStation ( ) ;
2018-10-02 22:55:53 +00:00
[DllImport("user32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern NoopSafeHandle GetThreadDesktop (
UInt32 dwThreadId ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool GetTokenInformation (
SafeNativeHandle TokenHandle ,
NativeHelpers . TokenInformationClass TokenInformationClass ,
SafeMemoryBuffer TokenInformation ,
UInt32 TokenInformationLength ,
out UInt32 ReturnLength ) ;
2018-10-02 22:55:53 +00:00
[DllImport("advapi32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern bool ImpersonateLoggedOnUser (
SafeNativeHandle hToken ) ;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUserW (
string lpszUsername ,
string lpszDomain ,
string lpszPassword ,
LogonType dwLogonType ,
NativeHelpers . LogonProvider dwLogonProvider ,
out SafeNativeHandle phToken ) ;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LookupPrivilegeNameW (
string lpSystemName ,
ref NativeHelpers . LUID lpLuid ,
StringBuilder lpName ,
ref UInt32 cchName ) ;
[DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaDeregisterLogonProcess (
IntPtr LsaHandle ) ;
[DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaEnumerateLogonSessions (
out UInt32 LogonSessionCount ,
out SafeLsaMemoryBuffer LogonSessionList ) ;
[DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaFreeReturnBuffer (
IntPtr Buffer ) ;
[DllImport("secur32.dll", SetLastError = true)]
public static extern UInt32 LsaGetLogonSessionData (
IntPtr LogonId ,
out SafeLsaMemoryBuffer ppLogonSessionData ) ;
[DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern UInt32 LsaLogonUser (
SafeLsaHandle LsaHandle ,
NativeHelpers . LSA_STRING OriginName ,
LogonType LogonType ,
UInt32 AuthenticationPackage ,
IntPtr AuthenticationInformation ,
UInt32 AuthenticationInformationLength ,
IntPtr LocalGroups ,
NativeHelpers . TOKEN_SOURCE SourceContext ,
out SafeLsaMemoryBuffer ProfileBuffer ,
out UInt32 ProfileBufferLength ,
out NativeHelpers . LUID LogonId ,
out SafeNativeHandle Token ,
out IntPtr Quotas ,
out UInt32 SubStatus ) ;
[DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern UInt32 LsaLookupAuthenticationPackage (
SafeLsaHandle LsaHandle ,
NativeHelpers . LSA_STRING PackageName ,
out UInt32 AuthenticationPackage ) ;
[DllImport("advapi32.dll")]
public static extern UInt32 LsaNtStatusToWinError (
UInt32 Status ) ;
[DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern UInt32 LsaRegisterLogonProcess (
NativeHelpers . LSA_STRING LogonProcessName ,
out SafeLsaHandle LsaHandle ,
out IntPtr SecurityMode ) ;
2018-10-02 22:55:53 +00:00
[DllImport("kernel32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern SafeNativeHandle OpenProcess (
NativeHelpers . ProcessAccessFlags dwDesiredAccess ,
2018-10-02 22:55:53 +00:00
bool bInheritHandle ,
2018-12-13 01:15:25 +00:00
UInt32 dwProcessId ) ;
2018-10-02 22:55:53 +00:00
[DllImport("advapi32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern bool OpenProcessToken (
SafeNativeHandle ProcessHandle ,
2018-10-02 22:55:53 +00:00
TokenAccessLevels DesiredAccess ,
2018-12-13 01:15:25 +00:00
out SafeNativeHandle TokenHandle ) ;
2018-10-02 22:55:53 +00:00
[DllImport("advapi32.dll", SetLastError = true)]
2018-12-13 01:15:25 +00:00
public static extern bool RevertToSelf ( ) ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
internal class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeLsaHandle ( ) : base ( true ) { }
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle ( )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
UInt32 res = NativeMethods . LsaDeregisterLogonProcess ( handle ) ;
return res = = 0 ;
}
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeLsaMemoryBuffer ( ) : base ( true ) { }
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle ( )
{
UInt32 res = NativeMethods . LsaFreeReturnBuffer ( handle ) ;
return res = = 0 ;
}
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeNativeHandle ( ) : base ( true ) { }
public SafeNativeHandle ( IntPtr handle ) : base ( true ) { this . handle = handle ; }
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle ( )
{
return NativeMethods . CloseHandle ( handle ) ;
}
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
internal class NoopSafeHandle : SafeHandle
{
public NoopSafeHandle ( ) : base ( IntPtr . Zero , false ) { }
public override bool IsInvalid { get { return false ; } }
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle ( ) { return true ; }
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
[Flags]
public enum LogonFlags
{
LOGON_WITH_PROFILE = 0x00000001 ,
LOGON_NETCREDENTIALS_ONLY = 0x00000002
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2 ,
LOGON32_LOGON_NETWORK = 3 ,
LOGON32_LOGON_BATCH = 4 ,
LOGON32_LOGON_SERVICE = 5 ,
LOGON32_LOGON_UNLOCK = 7 ,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8 ,
LOGON32_LOGON_NEW_CREDENTIALS = 9
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public class BecomeUtil
{
private static List < string > SERVICE_SIDS = new List < string > ( )
{
"S-1-5-18" , // NT AUTHORITY\SYSTEM
"S-1-5-19" , // NT AUTHORITY\LocalService
"S-1-5-20" // NT AUTHORITY\NetworkService
} ;
private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F ;
private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF ;
public static Result CreateProcessAsUser ( string username , string password , string command )
{
return CreateProcessAsUser ( username , password , LogonFlags . LOGON_WITH_PROFILE , LogonType . LOGON32_LOGON_INTERACTIVE ,
null , command , null , null , "" ) ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
public static Result CreateProcessAsUser ( string username , string password , LogonFlags logonFlags , LogonType logonType ,
string lpApplicationName , string lpCommandLine , string lpCurrentDirectory , IDictionary environment ,
string stdin )
{
byte [ ] stdinBytes ;
if ( String . IsNullOrEmpty ( stdin ) )
stdinBytes = new byte [ 0 ] ;
else
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
if ( ! stdin . EndsWith ( Environment . NewLine ) )
stdin + = Environment . NewLine ;
stdinBytes = new UTF8Encoding ( false ) . GetBytes ( stdin ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
return CreateProcessAsUser ( username , password , logonFlags , logonType , lpApplicationName , lpCommandLine ,
lpCurrentDirectory , environment , stdinBytes ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
/// <summary>
/// Creates a process as another user account. This method will attempt to run as another user with the
/// highest possible permissions available. The main privilege required is the SeDebugPrivilege, without
/// this privilege you can only run as a local or domain user if the username and password is specified.
/// </summary>
/// <param name="username">The username of the runas user</param>
/// <param name="password">The password of the runas user</param>
/// <param name="logonFlags">LogonFlags to control how to logon a user when the password is specified</param>
/// <param name="logonType">Controls what type of logon is used, this only applies when the password is specified</param>
/// <param name="lpApplicationName">The name of the executable or batch file to executable</param>
/// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
/// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
/// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
/// <param name="stdin">Bytes sent to the stdin pipe</param>
/// <returns>Ansible.Process.Result object that contains the command output and return code</returns>
public static Result CreateProcessAsUser ( string username , string password , LogonFlags logonFlags , LogonType logonType ,
string lpApplicationName , string lpCommandLine , string lpCurrentDirectory , IDictionary environment , byte [ ] stdin )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
// While we use STARTUPINFOEX having EXTENDED_STARTUPINFO_PRESENT causes a parameter validation error
Process . NativeHelpers . ProcessCreationFlags creationFlags = Process . NativeHelpers . ProcessCreationFlags . CREATE_UNICODE_ENVIRONMENT ;
Process . NativeHelpers . PROCESS_INFORMATION pi = new Process . NativeHelpers . PROCESS_INFORMATION ( ) ;
Process . NativeHelpers . STARTUPINFOEX si = new Process . NativeHelpers . STARTUPINFOEX ( ) ;
si . startupInfo . dwFlags = Process . NativeHelpers . StartupInfoFlags . USESTDHANDLES ;
SafeFileHandle stdoutRead , stdoutWrite , stderrRead , stderrWrite , stdinRead , stdinWrite ;
ProcessUtil . CreateStdioPipes ( si , out stdoutRead , out stdoutWrite , out stderrRead , out stderrWrite ,
out stdinRead , out stdinWrite ) ;
FileStream stdinStream = new FileStream ( stdinWrite , FileAccess . Write ) ;
// $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
// make sense for these parameters
if ( lpApplicationName = = "" )
lpApplicationName = null ;
if ( lpCurrentDirectory = = "" )
lpCurrentDirectory = null ;
using ( SafeMemoryBuffer lpEnvironment = ProcessUtil . CreateEnvironmentPointer ( environment ) )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
// A user may have 2 tokens, 1 limited and 1 elevated. GetUserToken will try and get both but we will
// only find out if the elevated token is valid when running here.
List < SafeNativeHandle > userTokens = GetUserTokens ( username , password , logonType ) ;
bool launchSuccess = false ;
StringBuilder commandLine = new StringBuilder ( lpCommandLine ) ;
foreach ( SafeNativeHandle token in userTokens )
{
if ( NativeMethods . CreateProcessWithTokenW ( token , logonFlags , lpApplicationName , commandLine ,
creationFlags , lpEnvironment , lpCurrentDirectory , si , out pi ) )
{
launchSuccess = true ;
break ;
}
}
if ( ! launchSuccess )
throw new Win32Exception ( "CreateProcessWithTokenW() failed" ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
return ProcessUtil . WaitProcess ( stdoutRead , stdoutWrite , stderrRead , stderrWrite , stdinStream , stdin , pi . hProcess ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static List < SafeNativeHandle > GetUserTokens ( string username , string password , LogonType logonType )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
List < SafeNativeHandle > userTokens = new List < SafeNativeHandle > ( ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
SafeNativeHandle systemToken = null ;
bool impersonated = false ;
string becomeSid = username ;
2018-10-02 22:55:53 +00:00
if ( logonType ! = LogonType . LOGON32_LOGON_NEW_CREDENTIALS )
{
2018-12-13 01:15:25 +00:00
// If prefixed with .\, we are becoming a local account, strip the prefix
if ( username . StartsWith ( ".\\" ) )
username = username . Substring ( 2 ) ;
NTAccount account = new NTAccount ( username ) ;
becomeSid = ( ( SecurityIdentifier ) account . Translate ( typeof ( SecurityIdentifier ) ) ) . Value ;
// Grant access to the current Windows Station and Desktop to the become user
2018-10-02 22:55:53 +00:00
GrantAccessToWindowStationAndDesktop ( account ) ;
2018-12-13 01:15:25 +00:00
// Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service
// account or have administrative rights on the become access token.
systemToken = GetPrimaryTokenForUser ( new SecurityIdentifier ( "S-1-5-18" ) , new List < string > ( ) { "SeTcbPrivilege" } ) ;
if ( systemToken ! = null )
impersonated = NativeMethods . ImpersonateLoggedOnUser ( systemToken ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
// We require impersonation if becoming a service sid or becoming a user without a password
if ( ! impersonated & & ( SERVICE_SIDS . Contains ( becomeSid ) | | String . IsNullOrEmpty ( password ) ) )
throw new Exception ( "Failed to get token for NT AUTHORITY\\SYSTEM required for become as a service account or an account without a password" ) ;
2018-10-02 22:55:53 +00:00
try
{
2018-12-13 01:15:25 +00:00
if ( becomeSid = = "S-1-5-18" )
userTokens . Add ( systemToken ) ;
// Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
// We only use S4U if no password was defined or it was null
else if ( ! SERVICE_SIDS . Contains ( becomeSid ) & & password = = null & & logonType ! = LogonType . LOGON32_LOGON_NEW_CREDENTIALS )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
// If no password was specified, try and duplicate an existing token for that user or use S4U to
// generate one without network credentials
SecurityIdentifier sid = new SecurityIdentifier ( becomeSid ) ;
SafeNativeHandle becomeToken = GetPrimaryTokenForUser ( sid ) ;
if ( becomeToken ! = null )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
userTokens . Add ( GetElevatedToken ( becomeToken ) ) ;
userTokens . Add ( becomeToken ) ;
}
else
{
becomeToken = GetS4UTokenForUser ( sid , logonType ) ;
userTokens . Add ( becomeToken ) ;
2018-10-02 22:55:53 +00:00
}
}
2018-12-13 01:15:25 +00:00
else
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
string domain = null ;
switch ( becomeSid )
2018-10-02 22:55:53 +00:00
{
case "S-1-5-19" :
2018-12-13 01:15:25 +00:00
logonType = LogonType . LOGON32_LOGON_SERVICE ;
domain = "NT AUTHORITY" ;
2018-10-02 22:55:53 +00:00
username = "LocalService" ;
break ;
case "S-1-5-20" :
2018-12-13 01:15:25 +00:00
logonType = LogonType . LOGON32_LOGON_SERVICE ;
domain = "NT AUTHORITY" ;
2018-10-02 22:55:53 +00:00
username = "NetworkService" ;
break ;
2018-12-13 01:15:25 +00:00
default :
// Trying to become a local or domain account
if ( username . Contains ( @"\" ) )
{
string [ ] userSplit = username . Split ( new char [ 1 ] { '\\' } , 2 ) ;
domain = userSplit [ 0 ] ;
username = userSplit [ 1 ] ;
}
else if ( ! username . Contains ( "@" ) )
domain = "." ;
break ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
SafeNativeHandle hToken ;
if ( ! NativeMethods . LogonUserW ( username , domain , password , logonType ,
NativeHelpers . LogonProvider . LOGON32_PROVIDER_DEFAULT , out hToken ) )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
throw new Win32Exception ( "LogonUserW() failed" ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
// Get the elevated token for a local/domain accounts only
if ( ! SERVICE_SIDS . Contains ( becomeSid ) )
userTokens . Add ( GetElevatedToken ( hToken ) ) ;
userTokens . Add ( hToken ) ;
2018-10-02 22:55:53 +00:00
}
}
finally
{
if ( impersonated )
2018-12-13 01:15:25 +00:00
NativeMethods . RevertToSelf ( ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
return userTokens ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static SafeNativeHandle GetPrimaryTokenForUser ( SecurityIdentifier sid , List < string > requiredPrivileges = null )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
NativeHelpers . ProcessAccessFlags accessFlags = NativeHelpers . ProcessAccessFlags . PROCESS_QUERY_INFORMATION ;
// According to CreateProcessWithTokenW we require a token with
// TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
// Also add in TOKEN_IMPERSONATE so we can get an impersonated token
TokenAccessLevels dwAccess = TokenAccessLevels . Query |
TokenAccessLevels . Duplicate |
TokenAccessLevels . AssignPrimary |
TokenAccessLevels . Impersonate ;
foreach ( System . Diagnostics . Process process in System . Diagnostics . Process . GetProcesses ( ) )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
using ( process )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
using ( SafeNativeHandle hProcess = NativeMethods . OpenProcess ( accessFlags , false , ( UInt32 ) process . Id ) )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
if ( hProcess . IsInvalid )
continue ;
SafeNativeHandle hToken ;
NativeMethods . OpenProcessToken ( hProcess , dwAccess , out hToken ) ;
if ( hToken . IsInvalid )
continue ;
using ( hToken )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
if ( ! sid . Equals ( GetTokenUserSID ( hToken ) ) )
continue ;
// Filter out any Network logon tokens, using become with that is useless when S4U
// can give us a Batch logon
NativeHelpers . SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType ( hToken ) ;
if ( tokenLogonType = = NativeHelpers . SECURITY_LOGON_TYPE . Network )
continue ;
// Check that the required privileges are on the token
if ( requiredPrivileges ! = null )
{
List < string > actualPrivileges = GetTokenPrivileges ( hToken ) ;
int missing = requiredPrivileges . Where ( x = > ! actualPrivileges . Contains ( x ) ) . Count ( ) ;
if ( missing > 0 )
continue ;
}
SafeNativeHandle dupToken ;
if ( ! NativeMethods . DuplicateTokenEx ( hToken , TokenAccessLevels . MaximumAllowed ,
IntPtr . Zero , NativeHelpers . SECURITY_IMPERSONATION_LEVEL . SecurityImpersonation ,
NativeHelpers . TOKEN_TYPE . TokenPrimary , out dupToken ) )
{
continue ;
}
return dupToken ;
2018-10-02 22:55:53 +00:00
}
}
}
}
2018-12-13 01:15:25 +00:00
return null ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static SafeNativeHandle GetS4UTokenForUser ( SecurityIdentifier sid , LogonType logonType )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
NTAccount becomeAccount = ( NTAccount ) sid . Translate ( typeof ( NTAccount ) ) ;
string [ ] userSplit = becomeAccount . Value . Split ( new char [ 1 ] { '\\' } , 2 ) ;
string domainName = userSplit [ 0 ] ;
string username = userSplit [ 1 ] ;
bool domainUser = domainName . ToLowerInvariant ( ) ! = Environment . MachineName . ToLowerInvariant ( ) ;
NativeHelpers . LSA_STRING logonProcessName = "ansible" ;
SafeLsaHandle lsaHandle ;
IntPtr securityMode ;
UInt32 res = NativeMethods . LsaRegisterLogonProcess ( logonProcessName , out lsaHandle , out securityMode ) ;
if ( res ! = 0 )
throw new Win32Exception ( ( int ) NativeMethods . LsaNtStatusToWinError ( res ) , "LsaRegisterLogonProcess() failed" ) ;
using ( lsaHandle )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
NativeHelpers . LSA_STRING packageName = domainUser ? "Kerberos" : "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0" ;
UInt32 authPackage ;
res = NativeMethods . LsaLookupAuthenticationPackage ( lsaHandle , packageName , out authPackage ) ;
if ( res ! = 0 )
throw new Win32Exception ( ( int ) NativeMethods . LsaNtStatusToWinError ( res ) ,
String . Format ( "LsaLookupAuthenticationPackage({0}) failed" , ( string ) packageName ) ) ;
int usernameLength = username . Length * sizeof ( char ) ;
int domainLength = domainName . Length * sizeof ( char ) ;
int authInfoLength = ( Marshal . SizeOf ( typeof ( NativeHelpers . KERB_S4U_LOGON ) ) + usernameLength + domainLength ) ;
IntPtr authInfo = Marshal . AllocHGlobal ( ( int ) authInfoLength ) ;
try
{
IntPtr usernamePtr = IntPtr . Add ( authInfo , Marshal . SizeOf ( typeof ( NativeHelpers . KERB_S4U_LOGON ) ) ) ;
IntPtr domainPtr = IntPtr . Add ( usernamePtr , usernameLength ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
// KERB_S4U_LOGON has the same structure as MSV1_0_S4U_LOGON (local accounts)
NativeHelpers . KERB_S4U_LOGON s4uLogon = new NativeHelpers . KERB_S4U_LOGON
{
MessageType = 12 , // KerbS4ULogon
Flags = 0 ,
ClientUpn = new NativeHelpers . LSA_UNICODE_STRING
{
Length = ( UInt16 ) usernameLength ,
MaximumLength = ( UInt16 ) usernameLength ,
Buffer = usernamePtr ,
} ,
ClientRealm = new NativeHelpers . LSA_UNICODE_STRING
{
Length = ( UInt16 ) domainLength ,
MaximumLength = ( UInt16 ) domainLength ,
Buffer = domainPtr ,
} ,
} ;
Marshal . StructureToPtr ( s4uLogon , authInfo , false ) ;
Marshal . Copy ( username . ToCharArray ( ) , 0 , usernamePtr , username . Length ) ;
Marshal . Copy ( domainName . ToCharArray ( ) , 0 , domainPtr , domainName . Length ) ;
NativeHelpers . LUID sourceLuid ;
if ( ! NativeMethods . AllocateLocallyUniqueId ( out sourceLuid ) )
throw new Win32Exception ( "AllocateLocallyUniqueId() failed" ) ;
NativeHelpers . TOKEN_SOURCE tokenSource = new NativeHelpers . TOKEN_SOURCE
{
SourceName = "ansible\0" . ToCharArray ( ) ,
SourceIdentifier = sourceLuid ,
} ;
// Only Batch or Network will work with S4U, prefer Batch but use Network if asked
LogonType lsaLogonType = logonType = = LogonType . LOGON32_LOGON_NETWORK
? LogonType . LOGON32_LOGON_NETWORK
: LogonType . LOGON32_LOGON_BATCH ;
SafeLsaMemoryBuffer profileBuffer ;
UInt32 profileBufferLength ;
NativeHelpers . LUID logonId ;
SafeNativeHandle hToken ;
IntPtr quotas ;
UInt32 subStatus ;
res = NativeMethods . LsaLogonUser ( lsaHandle , logonProcessName , lsaLogonType , authPackage ,
authInfo , ( UInt32 ) authInfoLength , IntPtr . Zero , tokenSource , out profileBuffer , out profileBufferLength ,
out logonId , out hToken , out quotas , out subStatus ) ;
if ( res ! = 0 )
throw new Win32Exception ( ( int ) NativeMethods . LsaNtStatusToWinError ( res ) ,
String . Format ( "LsaLogonUser() failed with substatus {0}" , subStatus ) ) ;
profileBuffer . Dispose ( ) ;
return hToken ;
}
finally
{
Marshal . FreeHGlobal ( authInfo ) ;
}
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
}
private static SafeNativeHandle GetElevatedToken ( SafeNativeHandle hToken )
{
// First determine if the current token is a limited token
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , NativeHelpers . TokenInformationClass . TokenElevationType ) )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
NativeHelpers . TokenElevationType tet = ( NativeHelpers . TokenElevationType ) Marshal . ReadInt32 ( tokenInfo . DangerousGetHandle ( ) ) ;
// We already have the best token we can get, just use it
if ( tet ! = NativeHelpers . TokenElevationType . TokenElevationTypeLimited )
return hToken ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
// We have a limited token, get the linked elevated token
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , NativeHelpers . TokenInformationClass . TokenLinkedToken ) )
return new SafeNativeHandle ( Marshal . ReadIntPtr ( tokenInfo . DangerousGetHandle ( ) ) ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static List < string > GetTokenPrivileges ( SafeNativeHandle hToken )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , NativeHelpers . TokenInformationClass . TokenPrivileges ) )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
NativeHelpers . TOKEN_PRIVILEGES tokenPrivileges = ( NativeHelpers . TOKEN_PRIVILEGES ) Marshal . PtrToStructure (
tokenInfo . DangerousGetHandle ( ) , typeof ( NativeHelpers . TOKEN_PRIVILEGES ) ) ;
NativeHelpers . LUID_AND_ATTRIBUTES [ ] luidAndAttributes = new NativeHelpers . LUID_AND_ATTRIBUTES [ tokenPrivileges . PrivilegeCount ] ;
PtrToStructureArray ( luidAndAttributes , IntPtr . Add ( tokenInfo . DangerousGetHandle ( ) , Marshal . SizeOf ( tokenPrivileges . PrivilegeCount ) ) ) ;
return luidAndAttributes . Select ( x = > GetPrivilegeName ( x . Luid ) ) . ToList ( ) ;
}
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static SecurityIdentifier GetTokenUserSID ( SafeNativeHandle hToken )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , NativeHelpers . TokenInformationClass . TokenUser ) )
{
NativeHelpers . TOKEN_USER tokenUser = ( NativeHelpers . TOKEN_USER ) Marshal . PtrToStructure ( tokenInfo . DangerousGetHandle ( ) ,
typeof ( NativeHelpers . TOKEN_USER ) ) ;
return new SecurityIdentifier ( tokenUser . User . Sid ) ;
}
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static NativeHelpers . SECURITY_LOGON_TYPE GetTokenLogonType ( SafeNativeHandle hToken )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
UInt64 tokenLuidId ;
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , NativeHelpers . TokenInformationClass . TokenStatistics ) )
{
NativeHelpers . TOKEN_STATISTICS stats = ( NativeHelpers . TOKEN_STATISTICS ) Marshal . PtrToStructure (
tokenInfo . DangerousGetHandle ( ) , typeof ( NativeHelpers . TOKEN_STATISTICS ) ) ;
tokenLuidId = ( UInt64 ) stats . AuthenticationId ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
// Default to Network, if we weren't able to get the actual type treat it as an error and assume
// we don't want to run a process with the token
NativeHelpers . SECURITY_LOGON_TYPE logonType = NativeHelpers . SECURITY_LOGON_TYPE . Network ;
UInt32 sessionCount ;
SafeLsaMemoryBuffer sessionPtr ;
UInt32 res = NativeMethods . LsaEnumerateLogonSessions ( out sessionCount , out sessionPtr ) ;
if ( res ! = 0 )
throw new Win32Exception ( ( int ) NativeMethods . LsaNtStatusToWinError ( res ) , "LsaEnumerateLogonSession() failed" ) ;
using ( sessionPtr )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
for ( IntPtr p = sessionPtr . DangerousGetHandle ( ) ;
2019-01-20 22:17:19 +00:00
p ! = IntPtr . Add ( sessionPtr . DangerousGetHandle ( ) , ( int ) ( Marshal . SizeOf ( typeof ( NativeHelpers . LUID ) ) * sessionCount ) ) ;
2018-12-13 01:15:25 +00:00
p = IntPtr . Add ( p , Marshal . SizeOf ( typeof ( NativeHelpers . LUID ) ) ) )
{
SafeLsaMemoryBuffer sessionDataPtr ;
res = NativeMethods . LsaGetLogonSessionData ( p , out sessionDataPtr ) ;
if ( res ! = 0 )
continue ;
using ( sessionDataPtr )
{
NativeHelpers . SECURITY_LOGON_SESSION_DATA sessionData = ( NativeHelpers . SECURITY_LOGON_SESSION_DATA ) Marshal . PtrToStructure (
sessionDataPtr . DangerousGetHandle ( ) , typeof ( NativeHelpers . SECURITY_LOGON_SESSION_DATA ) ) ;
UInt64 sessionId = ( UInt64 ) sessionData . LogonId ;
if ( sessionId = = tokenLuidId )
{
logonType = sessionData . LogonType ;
break ;
}
}
}
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
return logonType ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
private static SafeMemoryBuffer GetTokenInformation ( SafeNativeHandle hToken , NativeHelpers . TokenInformationClass tokenClass )
{
UInt32 tokenLength ;
bool res = NativeMethods . GetTokenInformation ( hToken , tokenClass , new SafeMemoryBuffer ( IntPtr . Zero ) , 0 , out tokenLength ) ;
if ( ! res & & tokenLength = = 0 ) // res will be false due to insufficient buffer size, we ignore if we got the buffer length
throw new Win32Exception ( String . Format ( "GetTokenInformation({0}) failed to get buffer length" , tokenClass . ToString ( ) ) ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer ( ( int ) tokenLength ) ;
if ( ! NativeMethods . GetTokenInformation ( hToken , tokenClass , tokenInfo , tokenLength , out tokenLength ) )
throw new Win32Exception ( String . Format ( "GetTokenInformation({0}) failed" , tokenClass . ToString ( ) ) ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
return tokenInfo ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
private static string GetPrivilegeName ( NativeHelpers . LUID luid )
{
UInt32 nameLen = 0 ;
NativeMethods . LookupPrivilegeNameW ( null , ref luid , null , ref nameLen ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
StringBuilder name = new StringBuilder ( ( int ) ( nameLen + 1 ) ) ;
if ( ! NativeMethods . LookupPrivilegeNameW ( null , ref luid , name , ref nameLen ) )
throw new Win32Exception ( "LookupPrivilegeNameW() failed" ) ;
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
return name . ToString ( ) ;
}
2018-10-02 22:55:53 +00:00
2018-12-13 01:15:25 +00:00
private static void PtrToStructureArray < T > ( T [ ] array , IntPtr ptr )
{
IntPtr ptrOffset = ptr ;
for ( int i = 0 ; i < array . Length ; i + + , ptrOffset = IntPtr . Add ( ptrOffset , Marshal . SizeOf ( typeof ( T ) ) ) )
array [ i ] = ( T ) Marshal . PtrToStructure ( ptrOffset , typeof ( T ) ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static void GrantAccessToWindowStationAndDesktop ( IdentityReference account )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
GrantAccess ( account , NativeMethods . GetProcessWindowStation ( ) , WINDOWS_STATION_ALL_ACCESS ) ;
GrantAccess ( account , NativeMethods . GetThreadDesktop ( NativeMethods . GetCurrentThreadId ( ) ) , DESKTOP_RIGHTS_ALL_ACCESS ) ;
2018-10-02 22:55:53 +00:00
}
2018-12-13 01:15:25 +00:00
private static void GrantAccess ( IdentityReference account , NoopSafeHandle handle , int accessMask )
2018-10-02 22:55:53 +00:00
{
2018-12-13 01:15:25 +00:00
GenericSecurity security = new GenericSecurity ( false , ResourceType . WindowObject , handle , AccessControlSections . Access ) ;
security . AddAccessRule ( new GenericAccessRule ( account , accessMask , AccessControlType . Allow ) ) ;
security . Persist ( handle , AccessControlSections . Access ) ;
2018-10-02 22:55:53 +00:00
}
private class GenericSecurity : NativeObjectSecurity
{
public GenericSecurity ( bool isContainer , ResourceType resType , SafeHandle objectHandle , AccessControlSections sectionsRequested )
: base ( isContainer , resType , objectHandle , sectionsRequested ) { }
public new void Persist ( SafeHandle handle , AccessControlSections includeSections ) { base . Persist ( handle , includeSections ) ; }
public new void AddAccessRule ( AccessRule rule ) { base . AddAccessRule ( rule ) ; }
public override Type AccessRightType { get { throw new NotImplementedException ( ) ; } }
public override AccessRule AccessRuleFactory ( System . Security . Principal . IdentityReference identityReference , int accessMask , bool isInherited ,
InheritanceFlags inheritanceFlags , PropagationFlags propagationFlags , AccessControlType type )
{ throw new NotImplementedException ( ) ; }
public override Type AccessRuleType { get { return typeof ( AccessRule ) ; } }
public override AuditRule AuditRuleFactory ( System . Security . Principal . IdentityReference identityReference , int accessMask , bool isInherited ,
InheritanceFlags inheritanceFlags , PropagationFlags propagationFlags , AuditFlags flags )
{ throw new NotImplementedException ( ) ; }
public override Type AuditRuleType { get { return typeof ( AuditRule ) ; } }
}
private class GenericAccessRule : AccessRule
{
public GenericAccessRule ( IdentityReference identity , int accessMask , AccessControlType type ) :
base ( identity , accessMask , false , InheritanceFlags . None , PropagationFlags . None , type )
{ }
}
}
}