@ -211,6 +211,7 @@ using System.Collections.Generic;
using System . Diagnostics ;
using System . Diagnostics ;
using System . IO ;
using System . IO ;
using System . Linq ;
using System . Linq ;
using System . Runtime . ConstrainedExecution ;
using System . Runtime . InteropServices ;
using System . Runtime . InteropServices ;
using System . Security . AccessControl ;
using System . Security . AccessControl ;
using System . Security . Principal ;
using System . Security . Principal ;
@ -265,6 +266,25 @@ namespace AnsibleBecome
}
}
}
}
[ StructLayout ( LayoutKind . Sequential ) ]
public struct LUID
{
public UInt32 LowPart ;
public Int32 HighPart ;
public static explicit operator UInt64 ( LUID l )
{
return ( UInt64 ) ( ( UInt64 ) l . HighPart << 32 ) | ( UInt64 ) l . LowPart ;
}
}
[ StructLayout ( LayoutKind . Sequential ) ]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid ;
public UInt32 Attributes ;
}
[ StructLayout ( LayoutKind . Sequential ) ]
[ StructLayout ( LayoutKind . Sequential ) ]
public struct PROCESS_INFORMATION
public struct PROCESS_INFORMATION
{
{
@ -281,6 +301,14 @@ namespace AnsibleBecome
public int Attributes ;
public int Attributes ;
}
}
[ StructLayout ( LayoutKind . Sequential ) ]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount ;
[ MarshalAs ( UnmanagedType . ByValArray , SizeConst = 1 ) ]
public LUID_AND_ATTRIBUTES [ ] Privileges ;
}
public struct TOKEN_USER
public struct TOKEN_USER
{
{
public SID_AND_ATTRIBUTES User ;
public SID_AND_ATTRIBUTES User ;
@ -335,6 +363,7 @@ namespace AnsibleBecome
public enum TokenInformationClass
public enum TokenInformationClass
{
{
TokenUser = 1 ,
TokenUser = 1 ,
TokenPrivileges = 3 ,
TokenType = 8 ,
TokenType = 8 ,
TokenImpersonationLevel = 9 ,
TokenImpersonationLevel = 9 ,
TokenElevationType = 18 ,
TokenElevationType = 18 ,
@ -373,6 +402,26 @@ namespace AnsibleBecome
}
}
}
}
class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeMemoryBuffer ( ) : base ( true ) { }
public SafeMemoryBuffer ( int cb ) : base ( true )
{
base . SetHandle ( Marshal . AllocHGlobal ( cb ) ) ;
}
public SafeMemoryBuffer ( IntPtr handle ) : base ( true )
{
base . SetHandle ( handle ) ;
}
[ ReliabilityContract ( Consistency . WillNotCorruptState , Cer . MayFail ) ]
protected override bool ReleaseHandle ( )
{
Marshal . FreeHGlobal ( handle ) ;
return true ;
}
}
public class Win32Exception : System . ComponentModel . Win32Exception
public class Win32Exception : System . ComponentModel . Win32Exception
{
{
private string _msg ;
private string _msg ;
@ -453,23 +502,22 @@ namespace AnsibleBecome
private static extern bool GetTokenInformation (
private static extern bool GetTokenInformation (
IntPtr TokenHandle ,
IntPtr TokenHandle ,
TokenInformationClass TokenInformationClass ,
TokenInformationClass TokenInformationClass ,
IntPt r TokenInformation ,
SafeMemoryBuffe r TokenInformation ,
uint TokenInformationLength ,
uint TokenInformationLength ,
out uint ReturnLength ) ;
out uint ReturnLength ) ;
[ DllImport ( " psapi.dll " , SetLastError = true ) ]
[ DllImport ( " advapi32.dll " , SetLastError = true , CharSet = CharSet . Unicode ) ]
private static extern bool EnumProcesses (
public static extern bool LookupPrivilegeNameW (
[ MarshalAs ( UnmanagedType . LPArray , ArraySubType = UnmanagedType . U4 ) ]
string lpSystemName ,
[ In ] [ Out ] IntPtr [ ] processIds ,
ref LUID lpLuid ,
uint cb ,
StringBuilder lpName ,
[ MarshalAs ( UnmanagedType . U4 ) ]
ref UInt32 cchName ) ;
out uint pBytesReturned ) ;
[ DllImport ( " kernel32.dll " , SetLastError = true ) ]
[ DllImport ( " kernel32.dll " , SetLastError = true ) ]
private static extern IntPtr OpenProcess (
private static extern IntPtr OpenProcess (
ProcessAccessFlags processAccess ,
ProcessAccessFlags processAccess ,
bool bInheritHandle ,
bool bInheritHandle ,
IntPtr processId ) ;
UInt32 processId ) ;
[ DllImport ( " advapi32.dll " , SetLastError = true ) ]
[ DllImport ( " advapi32.dll " , SetLastError = true ) ]
private static extern bool OpenProcessToken (
private static extern bool OpenProcessToken (
@ -477,12 +525,6 @@ namespace AnsibleBecome
TokenAccessLevels DesiredAccess ,
TokenAccessLevels DesiredAccess ,
out IntPtr TokenHandle ) ;
out IntPtr TokenHandle ) ;
[ DllImport ( " advapi32.dll " , SetLastError = true ) ]
private static extern bool ConvertSidToStringSidW (
IntPtr pSID ,
[ MarshalAs ( UnmanagedType . LPTStr ) ]
out string StringSid ) ;
[ DllImport ( " advapi32 " , SetLastError = true ) ]
[ DllImport ( " advapi32 " , SetLastError = true ) ]
private static extern bool DuplicateTokenEx (
private static extern bool DuplicateTokenEx (
IntPtr hExistingToken ,
IntPtr hExistingToken ,
@ -633,7 +675,6 @@ namespace AnsibleBecome
try
try
{
{
IntPtr hSystemTokenDup = IntPtr . Zero ;
if ( hSystemToken == IntPtr . Zero & & service_sids . Contains ( account_sid ) )
if ( hSystemToken == IntPtr . Zero & & service_sids . Contains ( account_sid ) )
{
{
/ / We need the SYSTEM token if we want to become one of those accounts , fail here
/ / We need the SYSTEM token if we want to become one of those accounts , fail here
@ -641,28 +682,13 @@ namespace AnsibleBecome
}
}
else if ( hSystemToken != IntPtr . Zero )
else if ( hSystemToken != IntPtr . Zero )
{
{
/ / We have the token , need to duplicate and impersonate
bool dupResult = DuplicateTokenEx (
hSystemToken ,
TokenAccessLevels . MaximumAllowed ,
IntPtr . Zero ,
SECURITY_IMPERSONATION_LEVEL . SecurityImpersonation ,
TOKEN_TYPE . TokenPrimary ,
out hSystemTokenDup ) ;
int lastError = Marshal . GetLastWin32Error ( ) ;
CloseHandle ( hSystemToken ) ;
if ( ! dupResult & & service_sids . Contains ( account_sid ) )
throw new Win32Exception ( lastError , " Failed to duplicate token for NT AUTHORITY \\ SYSTEM " ) ;
else if ( dupResult & & account_sid != " S-1-5-18 " )
{
if ( ImpersonateLoggedOnUser ( hSystemTokenDup ) )
impersonated = true ;
else if ( service_sids . Contains ( account_sid ) )
throw new Win32Exception ( " Failed to impersonate as SYSTEM account " ) ;
}
/ / If SYSTEM impersonation failed but we ' re trying to become a regular user, just proceed;
/ / If SYSTEM impersonation failed but we ' re trying to become a regular user, just proceed;
/ / might get a limited token in UAC - enabled cases , but better than nothing . . .
/ / might get a limited token in UAC - enabled cases , but better than nothing . . .
if ( ImpersonateLoggedOnUser ( hSystemToken ) )
impersonated = true ;
else if ( service_sids . Contains ( account_sid ) )
throw new Win32Exception ( " Failed to impersonate as SYSTEM account " ) ;
}
}
string domain = null ;
string domain = null ;
@ -676,7 +702,7 @@ namespace AnsibleBecome
switch ( account_sid )
switch ( account_sid )
{
{
case " S-1-5-18 " :
case " S-1-5-18 " :
tokens . Add ( hSystemToken Dup ) ;
tokens . Add ( hSystemToken ) ;
return tokens ;
return tokens ;
case " S-1-5-19 " :
case " S-1-5-19 " :
username = " LocalService " ;
username = " LocalService " ;
@ -734,44 +760,59 @@ namespace AnsibleBecome
private static IntPtr GetSystemUserHandle ( )
private static IntPtr GetSystemUserHandle ( )
{
{
uint array_byte_size = 1024 * sizeof ( uint ) ;
/ / According to CreateProcessWithTokenW we require a token with
IntPtr [ ] pids = new IntPtr [ 1024 ] ;
/ / TOKEN_QUERY , TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
uint bytes_copied ;
/ / Also add in TOKEN_IMPERSONATE so we can get an impersontated token
TokenAccessLevels desired_access = TokenAccessLevels . Query |
if ( ! EnumProcesses ( pids , array_byte_size , out bytes_copied ) )
TokenAccessLevels . Duplicate |
TokenAccessLevels . AssignPrimary |
TokenAccessLevels . Impersonate ;
foreach ( System . Diagnostics . Process process in System . Diagnostics . Process . GetProcesses ( ) )
{
{
throw new Win32Exception ( " Failed to enumerate processes " ) ;
using ( process )
}
/ / TODO : Handle if bytes_copied is larger than the array size and rerun EnumProcesses with larger array
uint num_processes = bytes_copied / sizeof ( uint ) ;
for ( uint i = 0 ; i < num_processes ; i + + )
{
IntPtr hProcess = OpenProcess ( ProcessAccessFlags . PROCESS_QUERY_INFORMATION , false , pids [ i ] ) ;
if ( hProcess != IntPtr . Zero )
{
{
IntPtr hToken = IntPtr . Zero ;
IntPtr hProcess = OpenProcess ( ProcessAccessFlags . PROCESS_QUERY_INFORMATION , false , ( UInt32 ) process . Id ) ;
/ / According to CreateProcessWithTokenW we require a token with
if ( hProcess == IntPtr . Zero )
/ / TOKEN_QUERY , TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
continue ;
/ / Also add in TOKEN_IMPERSONATE so we can get an impersontated token
TokenAccessLevels desired_access = TokenAccessLevels . Query |
try
TokenAccessLevels . Duplicate |
TokenAccessLevels . AssignPrimary |
TokenAccessLevels . Impersonate ;
if ( OpenProcessToken ( hProcess , desired_access , out hToken ) )
{
{
string sid = GetTokenUserSID ( hToken ) ;
IntPtr hToken = IntPtr . Zero ;
if ( sid == " S-1-5-18 " )
if ( ! OpenProcessToken ( hProcess , desired_access , out hToken ) )
continue ;
try
{
{
CloseHandle ( hProcess ) ;
string sid = GetTokenUserSID ( hToken ) ;
return hToken ;
if ( sid != " S-1-5-18 " )
continue ;
/ / Make sure the SYSTEM token we are checking contains the SeTcbPrivilege required for
/ / escalation . Some SYSTEM tokens have this privilege stripped out .
List < string > actualPrivileges = GetTokenPrivileges ( hToken ) ;
if ( ! actualPrivileges . Contains ( " SeTcbPrivilege " ) )
continue ;
IntPtr dupToken = IntPtr . Zero ;
if ( ! DuplicateTokenEx ( hToken , TokenAccessLevels . MaximumAllowed , IntPtr . Zero ,
SECURITY_IMPERSONATION_LEVEL . SecurityImpersonation , TOKEN_TYPE . TokenPrimary ,
out dupToken ) )
{
continue ;
}
return dupToken ;
}
finally
{
CloseHandle ( hToken ) ;
}
}
}
}
finally
CloseHandle ( hToken ) ;
{
CloseHandle ( hProcess ) ;
}
}
}
CloseHandle ( hProcess ) ;
}
}
return IntPtr . Zero ;
return IntPtr . Zero ;
@ -779,33 +820,12 @@ namespace AnsibleBecome
private static string GetTokenUserSID ( IntPtr hToken )
private static string GetTokenUserSID ( IntPtr hToken )
{
{
uint token_length ;
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , TokenInformationClass . TokenUser ) )
string sid ;
if ( ! GetTokenInformation ( hToken , TokenInformationClass . TokenUser , IntPtr . Zero , 0 , out token_length ) )
{
{
int last_err = Marshal . GetLastWin32Error ( ) ;
TOKEN_USER tokenUser = ( TOKEN_USER ) Marshal . PtrToStructure ( tokenInfo . DangerousGetHandle ( ) ,
if ( last_err != 122 ) / / ERROR_INSUFFICIENT_BUFFER
typeof ( TOKEN_USER ) ) ;
throw new Win32Exception ( last_err , " Failed to get TokenUser length " ) ;
return new SecurityIdentifier ( tokenUser . User . Sid ) . Value ;
}
}
IntPtr token_information = Marshal . AllocHGlobal ( ( int ) token_length ) ;
try
{
if ( ! GetTokenInformation ( hToken , TokenInformationClass . TokenUser , token_information , token_length , out token_length ) )
throw new Win32Exception ( " Failed to get TokenUser information " ) ;
TOKEN_USER token_user = ( TOKEN_USER ) Marshal . PtrToStructure ( token_information , typeof ( TOKEN_USER ) ) ;
if ( ! ConvertSidToStringSidW ( token_user . User . Sid , out sid ) )
throw new Win32Exception ( " Failed to get user SID " ) ;
}
finally
{
Marshal . FreeHGlobal ( token_information ) ;
}
return sid ;
}
}
private static void GetProcessOutput ( StreamReader stdoutStream , StreamReader stderrStream , out string stdout , out string stderr )
private static void GetProcessOutput ( StreamReader stdoutStream , StreamReader stderrStream , out string stdout , out string stderr )
@ -840,38 +860,65 @@ namespace AnsibleBecome
private static IntPtr GetElevatedToken ( IntPtr hToken )
private static IntPtr GetElevatedToken ( IntPtr hToken )
{
{
uint requestedLength ;
/ / First determine if the current token is a limited token
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , TokenInformationClass . TokenElevationType ) )
{
TokenElevationType tet = ( TokenElevationType ) Marshal . ReadInt32 ( tokenInfo . DangerousGetHandle ( ) ) ;
/ / We already have the best token we can get , just use it
if ( tet != TokenElevationType . TokenElevationTypeLimited )
return hToken ;
}
IntPtr pTokenInfo = Marshal . AllocHGlobal ( sizeof ( int ) ) ;
/ / We have a limited token , get the linked elevated token
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , TokenInformationClass . TokenLinkedToken ) )
return Marshal . ReadIntPtr ( tokenInfo . DangerousGetHandle ( ) ) ;
}
try
private static List < string > GetTokenPrivileges ( IntPtr hToken )
{
using ( SafeMemoryBuffer tokenInfo = GetTokenInformation ( hToken , TokenInformationClass . TokenPrivileges ) )
{
{
if ( ! GetTokenInformation ( hToken , TokenInformationClass . TokenElevationType , pTokenInfo , sizeof ( int ) , out requestedLength ) )
TOKEN_PRIVILEGES tokenPrivileges = ( TOKEN_PRIVILEGES ) Marshal . PtrToStructure (
throw new Win32Exception ( " Unable to get TokenElevationType " ) ;
t okenInfo. DangerousGetHandle ( ) , typeof ( TOKEN_PRIVILEGES ) ) ;
var tet = ( TokenElevationType ) Marshal . ReadInt32 ( pTokenInfo ) ;
LUID_AND_ATTRIBUTES [ ] luidAndAttributes = new LUID_AND_ATTRIBUTES [ tokenPrivileges . PrivilegeCount ] ;
PtrToStructureArray ( luidAndAttributes , IntPtr . Add ( tokenInfo . DangerousGetHandle ( ) , Marshal . SizeOf ( tokenPrivileges . PrivilegeCount ) ) ) ;
/ / we already have the best token we can get , just use it
return luidAndAttributes . Select ( x = > GetPrivilegeName ( x . Luid ) ) . ToList ( ) ;
if ( tet != TokenElevationType . TokenElevationTypeLimited )
}
return hToken ;
}
private static SafeMemoryBuffer GetTokenInformation ( IntPtr hToken , TokenInformationClass tokenClass )
{
UInt32 tokenLength ;
bool res = 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 ( ) ) ) ;
GetTokenInformation ( hToken , TokenInformationClass . TokenLinkedToken , IntPtr . Zero , 0 , out requestedLength ) ;
SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer ( ( int ) tokenLength ) ;
if ( ! GetTokenInformation ( hToken , tokenClass , tokenInfo , tokenLength , out tokenLength ) )
throw new Win32Exception ( String . Format ( " GetTokenInformation( {0} ) failed " , tokenClass . ToString ( ) ) ) ;
IntPtr pLinkedToken = Marshal . AllocHGlobal ( ( int ) requestedLength ) ;
return tokenInfo ;
}
if ( ! GetTokenInformation ( hToken , TokenInformationClass . TokenLinkedToken , pLinkedToken , requestedLength , out requestedLength ) )
private static string GetPrivilegeName ( LUID luid )
throw new Win32Exception ( " Unable to get linked token " ) ;
{
UInt32 nameLen = 0 ;
LookupPrivilegeNameW ( null , ref luid , null , ref nameLen ) ;
IntPtr linkedToken = Marshal . ReadIntPtr ( pLinkedToken ) ;
StringBuilder name = new StringBuilder ( ( int ) ( nameLen + 1 ) ) ;
if ( ! LookupPrivilegeNameW ( null , ref luid , name , ref nameLen ) )
throw new Win32Exception ( " LookupPrivilegeNameW() failed " ) ;
Marshal . FreeHGlobal ( pLinkedToken ) ;
return name . ToString ( ) ;
}
return linkedToken ;
private static void PtrToStructureArray < T > ( T [ ] array , IntPtr ptr )
}
{
finally
IntPtr ptrOffset = ptr ;
{
for ( int i = 0 ; i < array . Length ; i + + , ptrOffset = IntPtr . Add ( ptrOffset , Marshal . SizeOf ( typeof ( T ) ) ) )
Marshal . FreeHGlobal ( pTokenInfo ) ;
array [ i ] = ( T ) Marshal . PtrToStructure ( ptrOffset , typeof ( T ) ) ;
}
}
}
private static void GrantAccessToWindowStationAndDesktop ( SecurityIdentifier account )
private static void GrantAccessToWindowStationAndDesktop ( SecurityIdentifier account )