1 using Microsoft.Win32.SafeHandles;
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Runtime.ConstrainedExecution;
8 using System.Runtime.InteropServices;
9 using System.Security.AccessControl;
10 using System.Security.Principal;
11 using System.Text;
12 using Ansible.AccessToken;
13 using Ansible.Process;
14 
15 namespace Ansible.Become
16 {
17     internal class NativeHelpers
18     {
19         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
20         public struct KERB_S4U_LOGON
21         {
22             public UInt32 MessageType;
23             public UInt32 Flags;
24             public LSA_UNICODE_STRING ClientUpn;
25             public LSA_UNICODE_STRING ClientRealm;
26         }
27 
28         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
29         public struct LSA_STRING
30         {
31             public UInt16 Length;
32             public UInt16 MaximumLength;
33             [MarshalAs(UnmanagedType.LPStr)] public string Buffer;
34 
operator stringAnsible.Become.NativeHelpers.LSA_STRING35             public static implicit operator string(LSA_STRING s)
36             {
37                 return s.Buffer;
38             }
39 
operator LSA_STRINGAnsible.Become.NativeHelpers.LSA_STRING40             public static implicit operator LSA_STRING(string s)
41             {
42                 if (s == null)
43                     s = "";
44 
45                 LSA_STRING lsaStr = new LSA_STRING
46                 {
47                     Buffer = s,
48                     Length = (UInt16)s.Length,
49                     MaximumLength = (UInt16)(s.Length + 1),
50                 };
51                 return lsaStr;
52             }
53         }
54 
55         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
56         public struct LSA_UNICODE_STRING
57         {
58             public UInt16 Length;
59             public UInt16 MaximumLength;
60             public IntPtr Buffer;
61         }
62 
63         [StructLayout(LayoutKind.Sequential)]
64         public struct SECURITY_LOGON_SESSION_DATA
65         {
66             public UInt32 Size;
67             public Luid LogonId;
68             public LSA_UNICODE_STRING UserName;
69             public LSA_UNICODE_STRING LogonDomain;
70             public LSA_UNICODE_STRING AuthenticationPackage;
71             public SECURITY_LOGON_TYPE LogonType;
72         }
73 
74         [StructLayout(LayoutKind.Sequential)]
75         public struct TOKEN_SOURCE
76         {
77             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public char[] SourceName;
78             public Luid SourceIdentifier;
79         }
80 
81         public enum SECURITY_LOGON_TYPE
82         {
83             System = 0, // Used only by the Sytem account
84             Interactive = 2,
85             Network,
86             Batch,
87             Service,
88             Proxy,
89             Unlock,
90             NetworkCleartext,
91             NewCredentials,
92             RemoteInteractive,
93             CachedInteractive,
94             CachedRemoteInteractive,
95             CachedUnlock
96         }
97     }
98 
99     internal class NativeMethods
100     {
101         [DllImport("advapi32.dll", SetLastError = true)]
AllocateLocallyUniqueId( out Luid Luid)102         public static extern bool AllocateLocallyUniqueId(
103             out Luid Luid);
104 
105         [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
CreateProcessWithTokenW( SafeNativeHandle hToken, LogonFlags dwLogonFlags, [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, StringBuilder lpCommandLine, Process.NativeHelpers.ProcessCreationFlags dwCreationFlags, Process.SafeMemoryBuffer lpEnvironment, [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory, Process.NativeHelpers.STARTUPINFOEX lpStartupInfo, out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation)106         public static extern bool CreateProcessWithTokenW(
107             SafeNativeHandle hToken,
108             LogonFlags dwLogonFlags,
109             [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
110             StringBuilder lpCommandLine,
111             Process.NativeHelpers.ProcessCreationFlags dwCreationFlags,
112             Process.SafeMemoryBuffer lpEnvironment,
113             [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
114             Process.NativeHelpers.STARTUPINFOEX lpStartupInfo,
115             out Process.NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
116 
117         [DllImport("kernel32.dll")]
GetCurrentThreadId()118         public static extern UInt32 GetCurrentThreadId();
119 
120         [DllImport("user32.dll", SetLastError = true)]
GetProcessWindowStation()121         public static extern NoopSafeHandle GetProcessWindowStation();
122 
123         [DllImport("user32.dll", SetLastError = true)]
GetThreadDesktop( UInt32 dwThreadId)124         public static extern NoopSafeHandle GetThreadDesktop(
125             UInt32 dwThreadId);
126 
127         [DllImport("secur32.dll", SetLastError = true)]
LsaDeregisterLogonProcess( IntPtr LsaHandle)128         public static extern UInt32 LsaDeregisterLogonProcess(
129             IntPtr LsaHandle);
130 
131         [DllImport("secur32.dll", SetLastError = true)]
LsaFreeReturnBuffer( IntPtr Buffer)132         public static extern UInt32 LsaFreeReturnBuffer(
133             IntPtr Buffer);
134 
135         [DllImport("secur32.dll", SetLastError = true)]
LsaGetLogonSessionData( ref Luid LogonId, out SafeLsaMemoryBuffer ppLogonSessionData)136         public static extern UInt32 LsaGetLogonSessionData(
137             ref Luid LogonId,
138             out SafeLsaMemoryBuffer ppLogonSessionData);
139 
140         [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
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 Luid LogonId, out SafeNativeHandle Token, out IntPtr Quotas, out UInt32 SubStatus)141         public static extern UInt32 LsaLogonUser(
142             SafeLsaHandle LsaHandle,
143             NativeHelpers.LSA_STRING OriginName,
144             LogonType LogonType,
145             UInt32 AuthenticationPackage,
146             IntPtr AuthenticationInformation,
147             UInt32 AuthenticationInformationLength,
148             IntPtr LocalGroups,
149             NativeHelpers.TOKEN_SOURCE SourceContext,
150             out SafeLsaMemoryBuffer ProfileBuffer,
151             out UInt32 ProfileBufferLength,
152             out Luid LogonId,
153             out SafeNativeHandle Token,
154             out IntPtr Quotas,
155             out UInt32 SubStatus);
156 
157         [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
LsaLookupAuthenticationPackage( SafeLsaHandle LsaHandle, NativeHelpers.LSA_STRING PackageName, out UInt32 AuthenticationPackage)158         public static extern UInt32 LsaLookupAuthenticationPackage(
159             SafeLsaHandle LsaHandle,
160             NativeHelpers.LSA_STRING PackageName,
161             out UInt32 AuthenticationPackage);
162 
163         [DllImport("advapi32.dll")]
LsaNtStatusToWinError( UInt32 Status)164         public static extern UInt32 LsaNtStatusToWinError(
165             UInt32 Status);
166 
167         [DllImport("secur32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
LsaRegisterLogonProcess( NativeHelpers.LSA_STRING LogonProcessName, out SafeLsaHandle LsaHandle, out IntPtr SecurityMode)168         public static extern UInt32 LsaRegisterLogonProcess(
169             NativeHelpers.LSA_STRING LogonProcessName,
170             out SafeLsaHandle LsaHandle,
171             out IntPtr SecurityMode);
172     }
173 
174     internal class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
175     {
SafeLsaHandle()176         public SafeLsaHandle() : base(true) { }
177 
178         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
ReleaseHandle()179         protected override bool ReleaseHandle()
180         {
181             UInt32 res = NativeMethods.LsaDeregisterLogonProcess(handle);
182             return res == 0;
183         }
184     }
185 
186     internal class SafeLsaMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
187     {
SafeLsaMemoryBuffer()188         public SafeLsaMemoryBuffer() : base(true) { }
189 
190         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
ReleaseHandle()191         protected override bool ReleaseHandle()
192         {
193             UInt32 res = NativeMethods.LsaFreeReturnBuffer(handle);
194             return res == 0;
195         }
196     }
197 
198     internal class NoopSafeHandle : SafeHandle
199     {
NoopSafeHandle()200         public NoopSafeHandle() : base(IntPtr.Zero, false) { }
201         public override bool IsInvalid { get { return false; } }
202 
203         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
ReleaseHandle()204         protected override bool ReleaseHandle() { return true; }
205     }
206 
207     [Flags]
208     public enum LogonFlags
209     {
210         WithProfile = 0x00000001,
211         NetcredentialsOnly = 0x00000002
212     }
213 
214     public class BecomeUtil
215     {
216         private static List<string> SERVICE_SIDS = new List<string>()
217         {
218             "S-1-5-18", // NT AUTHORITY\SYSTEM
219             "S-1-5-19", // NT AUTHORITY\LocalService
220             "S-1-5-20"  // NT AUTHORITY\NetworkService
221         };
222         private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F;
223         private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF;
224 
CreateProcessAsUser(string username, string password, string command)225         public static Result CreateProcessAsUser(string username, string password, string command)
226         {
227             return CreateProcessAsUser(username, password, LogonFlags.WithProfile, LogonType.Interactive,
228                  null, command, null, null, "");
229         }
230 
CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType, string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, string stdin)231         public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType,
232             string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment,
233             string stdin)
234         {
235             byte[] stdinBytes;
236             if (String.IsNullOrEmpty(stdin))
237                 stdinBytes = new byte[0];
238             else
239             {
240                 if (!stdin.EndsWith(Environment.NewLine))
241                     stdin += Environment.NewLine;
242                 stdinBytes = new UTF8Encoding(false).GetBytes(stdin);
243             }
244             return CreateProcessAsUser(username, password, logonFlags, logonType, lpApplicationName, lpCommandLine,
245                 lpCurrentDirectory, environment, stdinBytes);
246         }
247 
248         /// <summary>
249         /// Creates a process as another user account. This method will attempt to run as another user with the
250         /// highest possible permissions available. The main privilege required is the SeDebugPrivilege, without
251         /// this privilege you can only run as a local or domain user if the username and password is specified.
252         /// </summary>
253         /// <param name="username">The username of the runas user</param>
254         /// <param name="password">The password of the runas user</param>
255         /// <param name="logonFlags">LogonFlags to control how to logon a user when the password is specified</param>
256         /// <param name="logonType">Controls what type of logon is used, this only applies when the password is specified</param>
257         /// <param name="lpApplicationName">The name of the executable or batch file to executable</param>
258         /// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
259         /// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
260         /// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
261         /// <param name="stdin">Bytes sent to the stdin pipe</param>
262         /// <returns>Ansible.Process.Result object that contains the command output and return code</returns>
CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType, string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, byte[] stdin)263         public static Result CreateProcessAsUser(string username, string password, LogonFlags logonFlags, LogonType logonType,
264             string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, IDictionary environment, byte[] stdin)
265         {
266             // While we use STARTUPINFOEX having EXTENDED_STARTUPINFO_PRESENT causes a parameter validation error
267             Process.NativeHelpers.ProcessCreationFlags creationFlags = Process.NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT;
268             Process.NativeHelpers.PROCESS_INFORMATION pi = new Process.NativeHelpers.PROCESS_INFORMATION();
269             Process.NativeHelpers.STARTUPINFOEX si = new Process.NativeHelpers.STARTUPINFOEX();
270             si.startupInfo.dwFlags = Process.NativeHelpers.StartupInfoFlags.USESTDHANDLES;
271 
272             SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite;
273             ProcessUtil.CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite,
274                 out stdinRead, out stdinWrite);
275             FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write);
276 
277             // $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
278             // make sense for these parameters
279             if (lpApplicationName == "")
280                 lpApplicationName = null;
281 
282             if (lpCurrentDirectory == "")
283                 lpCurrentDirectory = null;
284 
285             // A user may have 2 tokens, 1 limited and 1 elevated. GetUserTokens will return both token to ensure
286             // we don't close one of the pairs while the process is still running. If the process tries to retrieve
287             // one of the pairs and the token handle is closed then it will fail with ERROR_NO_SUCH_LOGON_SESSION.
288             List<SafeNativeHandle> userTokens = GetUserTokens(username, password, logonType);
289             try
290             {
291                 using (Process.SafeMemoryBuffer lpEnvironment = ProcessUtil.CreateEnvironmentPointer(environment))
292                 {
293                     bool launchSuccess = false;
294                     StringBuilder commandLine = new StringBuilder(lpCommandLine);
295                     foreach (SafeNativeHandle token in userTokens)
296                     {
297                         // GetUserTokens could return null if an elevated token could not be retrieved.
298                         if (token == null)
299                             continue;
300 
301                         if (NativeMethods.CreateProcessWithTokenW(token, logonFlags, lpApplicationName,
302                                 commandLine, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
303                         {
304                             launchSuccess = true;
305                             break;
306                         }
307                     }
308 
309                     if (!launchSuccess)
310                         throw new Process.Win32Exception("CreateProcessWithTokenW() failed");
311                 }
312                 return ProcessUtil.WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin,
313                     pi.hProcess);
314             }
315             finally
316             {
317                 userTokens.Where(t => t != null).ToList().ForEach(t => t.Dispose());
318             }
319         }
320 
GetUserTokens(string username, string password, LogonType logonType)321         private static List<SafeNativeHandle> GetUserTokens(string username, string password, LogonType logonType)
322         {
323             List<SafeNativeHandle> userTokens = new List<SafeNativeHandle>();
324 
325             SafeNativeHandle systemToken = null;
326             bool impersonated = false;
327             string becomeSid = username;
328             if (logonType != LogonType.NewCredentials)
329             {
330                 // If prefixed with .\, we are becoming a local account, strip the prefix
331                 if (username.StartsWith(".\\"))
332                     username = username.Substring(2);
333 
334                 NTAccount account = new NTAccount(username);
335                 becomeSid = ((SecurityIdentifier)account.Translate(typeof(SecurityIdentifier))).Value;
336 
337                 // Grant access to the current Windows Station and Desktop to the become user
338                 GrantAccessToWindowStationAndDesktop(account);
339 
340                 // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service
341                 // account or have administrative rights on the become access token.
342                 // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available.
343                 // https://github.com/ansible/ansible/issues/71453
344                 bool mostPrivileges = becomeSid == "S-1-5-18";
345                 systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"),
346                     new List<string>() { "SeTcbPrivilege" }, mostPrivileges);
347                 if (systemToken != null)
348                 {
349                     try
350                     {
351                         TokenUtil.ImpersonateToken(systemToken);
352                         impersonated = true;
353                     }
354                     catch (Process.Win32Exception) { }  // We tried, just rely on current user's permissions.
355                 }
356             }
357 
358             // We require impersonation if becoming a service sid or becoming a user without a password
359             if (!impersonated && (SERVICE_SIDS.Contains(becomeSid) || String.IsNullOrEmpty(password)))
360                 throw new Exception("Failed to get token for NT AUTHORITY\\SYSTEM required for become as a service account or an account without a password");
361 
362             try
363             {
364                 if (becomeSid == "S-1-5-18")
365                     userTokens.Add(systemToken);
366                 // Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
367                 // We only use S4U if no password was defined or it was null
368                 else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
369                 {
370                     // If no password was specified, try and duplicate an existing token for that user or use S4U to
371                     // generate one without network credentials
372                     SecurityIdentifier sid = new SecurityIdentifier(becomeSid);
373                     SafeNativeHandle becomeToken = GetPrimaryTokenForUser(sid);
374                     if (becomeToken != null)
375                     {
376                         userTokens.Add(GetElevatedToken(becomeToken));
377                         userTokens.Add(becomeToken);
378                     }
379                     else
380                     {
381                         becomeToken = GetS4UTokenForUser(sid, logonType);
382                         userTokens.Add(null);
383                         userTokens.Add(becomeToken);
384                     }
385                 }
386                 else
387                 {
388                     string domain = null;
389                     switch (becomeSid)
390                     {
391                         case "S-1-5-19":
392                             logonType = LogonType.Service;
393                             domain = "NT AUTHORITY";
394                             username = "LocalService";
395                             break;
396                         case "S-1-5-20":
397                             logonType = LogonType.Service;
398                             domain = "NT AUTHORITY";
399                             username = "NetworkService";
400                             break;
401                         default:
402                             // Trying to become a local or domain account
403                             if (username.Contains(@"\"))
404                             {
405                                 string[] userSplit = username.Split(new char[1] { '\\' }, 2);
406                                 domain = userSplit[0];
407                                 username = userSplit[1];
408                             }
409                             else if (!username.Contains("@"))
410                                 domain = ".";
411                             break;
412                     }
413 
414                     SafeNativeHandle hToken = TokenUtil.LogonUser(username, domain, password, logonType,
415                         LogonProvider.Default);
416 
417                     // Get the elevated token for a local/domain accounts only
418                     if (!SERVICE_SIDS.Contains(becomeSid))
419                         userTokens.Add(GetElevatedToken(hToken));
420                     userTokens.Add(hToken);
421                 }
422             }
423             finally
424             {
425                 if (impersonated)
426                     TokenUtil.RevertToSelf();
427             }
428 
429             return userTokens;
430         }
431 
GetPrimaryTokenForUser(SecurityIdentifier sid, List<string> requiredPrivileges = null, bool mostPrivileges = false)432         private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid,
433             List<string> requiredPrivileges = null, bool mostPrivileges = false)
434         {
435             // According to CreateProcessWithTokenW we require a token with
436             //  TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
437             // Also add in TOKEN_IMPERSONATE so we can get an impersonated token
438             TokenAccessLevels dwAccess = TokenAccessLevels.Query |
439                 TokenAccessLevels.Duplicate |
440                 TokenAccessLevels.AssignPrimary |
441                 TokenAccessLevels.Impersonate;
442 
443             SafeNativeHandle userToken = null;
444             int privilegeCount = 0;
445 
446             foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
447             {
448                 // Filter out any Network logon tokens, using become with that is useless when S4U
449                 // can give us a Batch logon
450                 NativeHelpers.SECURITY_LOGON_TYPE tokenLogonType = GetTokenLogonType(hToken);
451                 if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network)
452                     continue;
453 
454                 List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();
455 
456                 // If the token has less or the same number of privileges than the current token, skip it.
457                 if (mostPrivileges && privilegeCount >= actualPrivileges.Count)
458                     continue;
459 
460                 // Check that the required privileges are on the token
461                 if (requiredPrivileges != null)
462                 {
463                     int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count();
464                     if (missing > 0)
465                         continue;
466                 }
467 
468                 // Duplicate the token to convert it to a primary token with the access level required.
469                 try
470                 {
471                     userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
472                         SecurityImpersonationLevel.Anonymous, TokenType.Primary);
473                     privilegeCount = actualPrivileges.Count;
474                 }
475                 catch (Process.Win32Exception)
476                 {
477                     continue;
478                 }
479 
480                 // If we don't care about getting the token with the most privileges, escape the loop as we already
481                 // have a token.
482                 if (!mostPrivileges)
483                     break;
484             }
485 
486             return userToken;
487         }
488 
GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)489         private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)
490         {
491             NTAccount becomeAccount = (NTAccount)sid.Translate(typeof(NTAccount));
492             string[] userSplit = becomeAccount.Value.Split(new char[1] { '\\' }, 2);
493             string domainName = userSplit[0];
494             string username = userSplit[1];
495             bool domainUser = domainName.ToLowerInvariant() != Environment.MachineName.ToLowerInvariant();
496 
497             NativeHelpers.LSA_STRING logonProcessName = "ansible";
498             SafeLsaHandle lsaHandle;
499             IntPtr securityMode;
500             UInt32 res = NativeMethods.LsaRegisterLogonProcess(logonProcessName, out lsaHandle, out securityMode);
501             if (res != 0)
502                 throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res), "LsaRegisterLogonProcess() failed");
503 
504             using (lsaHandle)
505             {
506                 NativeHelpers.LSA_STRING packageName = domainUser ? "Kerberos" : "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0";
507                 UInt32 authPackage;
508                 res = NativeMethods.LsaLookupAuthenticationPackage(lsaHandle, packageName, out authPackage);
509                 if (res != 0)
510                     throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
511                         String.Format("LsaLookupAuthenticationPackage({0}) failed", (string)packageName));
512 
513                 int usernameLength = username.Length * sizeof(char);
514                 int domainLength = domainName.Length * sizeof(char);
515                 int authInfoLength = (Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)) + usernameLength + domainLength);
516                 IntPtr authInfo = Marshal.AllocHGlobal((int)authInfoLength);
517                 try
518                 {
519                     IntPtr usernamePtr = IntPtr.Add(authInfo, Marshal.SizeOf(typeof(NativeHelpers.KERB_S4U_LOGON)));
520                     IntPtr domainPtr = IntPtr.Add(usernamePtr, usernameLength);
521 
522                     // KERB_S4U_LOGON has the same structure as MSV1_0_S4U_LOGON (local accounts)
523                     NativeHelpers.KERB_S4U_LOGON s4uLogon = new NativeHelpers.KERB_S4U_LOGON
524                     {
525                         MessageType = 12,  // KerbS4ULogon
526                         Flags = 0,
527                         ClientUpn = new NativeHelpers.LSA_UNICODE_STRING
528                         {
529                             Length = (UInt16)usernameLength,
530                             MaximumLength = (UInt16)usernameLength,
531                             Buffer = usernamePtr,
532                         },
533                         ClientRealm = new NativeHelpers.LSA_UNICODE_STRING
534                         {
535                             Length = (UInt16)domainLength,
536                             MaximumLength = (UInt16)domainLength,
537                             Buffer = domainPtr,
538                         },
539                     };
540                     Marshal.StructureToPtr(s4uLogon, authInfo, false);
541                     Marshal.Copy(username.ToCharArray(), 0, usernamePtr, username.Length);
542                     Marshal.Copy(domainName.ToCharArray(), 0, domainPtr, domainName.Length);
543 
544                     Luid sourceLuid;
545                     if (!NativeMethods.AllocateLocallyUniqueId(out sourceLuid))
546                         throw new Process.Win32Exception("AllocateLocallyUniqueId() failed");
547 
548                     NativeHelpers.TOKEN_SOURCE tokenSource = new NativeHelpers.TOKEN_SOURCE
549                     {
550                         SourceName = "ansible\0".ToCharArray(),
551                         SourceIdentifier = sourceLuid,
552                     };
553 
554                     // Only Batch or Network will work with S4U, prefer Batch but use Network if asked
555                     LogonType lsaLogonType = logonType == LogonType.Network
556                         ? LogonType.Network
557                         : LogonType.Batch;
558                     SafeLsaMemoryBuffer profileBuffer;
559                     UInt32 profileBufferLength;
560                     Luid logonId;
561                     SafeNativeHandle hToken;
562                     IntPtr quotas;
563                     UInt32 subStatus;
564 
565                     res = NativeMethods.LsaLogonUser(lsaHandle, logonProcessName, lsaLogonType, authPackage,
566                         authInfo, (UInt32)authInfoLength, IntPtr.Zero, tokenSource, out profileBuffer, out profileBufferLength,
567                         out logonId, out hToken, out quotas, out subStatus);
568                     if (res != 0)
569                         throw new Process.Win32Exception((int)NativeMethods.LsaNtStatusToWinError(res),
570                             String.Format("LsaLogonUser() failed with substatus {0}", subStatus));
571 
572                     profileBuffer.Dispose();
573                     return hToken;
574                 }
575                 finally
576                 {
577                     Marshal.FreeHGlobal(authInfo);
578                 }
579             }
580         }
581 
GetElevatedToken(SafeNativeHandle hToken)582         private static SafeNativeHandle GetElevatedToken(SafeNativeHandle hToken)
583         {
584             TokenElevationType tet = TokenUtil.GetTokenElevationType(hToken);
585             // We already have the best token we can get, no linked token is really available.
586             if (tet != TokenElevationType.Limited)
587                 return null;
588 
589             SafeNativeHandle linkedToken = TokenUtil.GetTokenLinkedToken(hToken);
590             TokenStatistics tokenStats = TokenUtil.GetTokenStatistics(linkedToken);
591 
592             // We can only use a token if it's a primary one (we had the SeTcbPrivilege set)
593             if (tokenStats.TokenType == TokenType.Primary)
594                 return linkedToken;
595             else
596                 return null;
597         }
598 
GetTokenLogonType(SafeNativeHandle hToken)599         private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
600         {
601             TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);
602 
603             SafeLsaMemoryBuffer sessionDataPtr;
604             UInt32 res = NativeMethods.LsaGetLogonSessionData(ref stats.AuthenticationId, out sessionDataPtr);
605             if (res != 0)
606                 // Default to Network, if we weren't able to get the actual type treat it as an error and assume
607                 // we don't want to run a process with the token
608                 return NativeHelpers.SECURITY_LOGON_TYPE.Network;
609 
610             using (sessionDataPtr)
611             {
612                 NativeHelpers.SECURITY_LOGON_SESSION_DATA sessionData = (NativeHelpers.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(
613                     sessionDataPtr.DangerousGetHandle(), typeof(NativeHelpers.SECURITY_LOGON_SESSION_DATA));
614                 return sessionData.LogonType;
615             }
616         }
617 
GrantAccessToWindowStationAndDesktop(IdentityReference account)618         private static void GrantAccessToWindowStationAndDesktop(IdentityReference account)
619         {
620             GrantAccess(account, NativeMethods.GetProcessWindowStation(), WINDOWS_STATION_ALL_ACCESS);
621             GrantAccess(account, NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId()), DESKTOP_RIGHTS_ALL_ACCESS);
622         }
623 
GrantAccess(IdentityReference account, NoopSafeHandle handle, int accessMask)624         private static void GrantAccess(IdentityReference account, NoopSafeHandle handle, int accessMask)
625         {
626             GenericSecurity security = new GenericSecurity(false, ResourceType.WindowObject, handle, AccessControlSections.Access);
627             security.AddAccessRule(new GenericAccessRule(account, accessMask, AccessControlType.Allow));
628             security.Persist(handle, AccessControlSections.Access);
629         }
630 
631         private class GenericSecurity : NativeObjectSecurity
632         {
GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)633             public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)
634                 : base(isContainer, resType, objectHandle, sectionsRequested) { }
Persist(SafeHandle handle, AccessControlSections includeSections)635             public new void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); }
AddAccessRule(AccessRule rule)636             public new void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); }
637             public override Type AccessRightType { get { throw new NotImplementedException(); } }
AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)638             public override AccessRule AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
639                 InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
640             { throw new NotImplementedException(); }
641             public override Type AccessRuleType { get { return typeof(AccessRule); } }
AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)642             public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
643                 InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)
644             { throw new NotImplementedException(); }
645             public override Type AuditRuleType { get { return typeof(AuditRule); } }
646         }
647 
648         private class GenericAccessRule : AccessRule
649         {
GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type)650             public GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type) :
651                 base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type)
652             { }
653         }
654     }
655 }
656