标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-3145] 作者: 大猪 发表于: [2022-07-22]
本文共 [608] 位读者顶过
Koh: The Token StealerEdit 07/13/22: After an awesome back and forth with and @SteveSyfuhs on Twitter on the effects of “TokenLeakDetectDelaySecs” and “Protected Users” for mitigating token theft, I’ve updated the Koh README to reflect these mitigations.Years ago I was chatting with a few experienced red teamers and one was lamenting token abuse. Specifically, they wanted to be able to automatically “harvest” tokens on a host as people connected, keeping the tokens usable for operators even after the associated account logged off. I knew very little about Windows authentication at the time, so when the other red teamer investigated the idea and told us it wasn’t possible, I left it at that. Before September 2016 they were (probably? maybe?) correct. Since then, however, it’s a different story. In this post I will introduce a toolkit called Koh that can indefinitely (at least I think? someone smarter please let me know if this isn’t the case) harvest and reuse tokens for accounts that connect to a machine you have administrative rights on. I’ll go over the motivation for this approach, the technical background of why it’s possible and what changed in 2016, and briefly show what Koh can do. MotivationLSASS, oh St. LSASS, the eternal bestower of credentials. Attackers want access. This naturally leads to the desire to pillage credentials from a host for lateral movement, persistence, and other reasons, and LSASS has been a great source of plaintext credentials and hashes for years. The most notorious of the credential dumpers is the (in)famous Mimikatz, built by the venerable Benjamin Delpy (to teach himself C, yes, the legends are true). When vendors state “We stop Mimikatz” more often than not they’re referring to protections against LSASS credential extraction. (Not) surprisingly, the advertising isn’t always 100% accurate: Point being that the cat and mouse game for LSASS abuse is certainly afoot, and while various offensive workarounds have publicly surfaced over the past several years (MiniDumpWriteDump, Syscall methods, PssCaptureSnapShot, reusing existing LSASS handles, memory shenanigans, offensive drivers, yes-I-know-I’m-missing-other-methods-and-this-list-isn’t-complete) as well as private methods I’m sure exist on internal teams, it’s hard to deny that defensive EDRs have increasingly pushed the offensive industry into more of a corner in this regard. Since credential dumping, in particular LSASS abuse, is such a common component of many attack chains it makes reasonable sense for the defensive industry to focus on this as a choke point. However I would caution any defenders against believing that an EDR will save you in every case, despite whatever the marketing tells you. There are a lot of ways to get credentials off a host beyond just LSASS dumping. We’ve personally been big fans of offensive DPAPI abuse as well as Kerberos ticket extraction, two approaches we’ve been very public about and have built multiple open source tools to facilitate (and of course it’s always been possible in Mimikatz ; ) Due to this defensive pressure, attackers are always on the lookout for new ways to abuse access on a host. Traditionally this has involved various methods to retrieve plaintext passwords, hashes, or Kerberos keys/tickets. More historically, “back in the day” (think 2008) this was tokens, with Luke Jennings’ original release of Incognito being a game changer. A while ago I was thinking about this cat-and-mouse-game of credential extraction and wanted to revisit this idea of token harvesting. A tool that allows the long-running harvest and reuse of tokens on a host could prove really useful on many engagements, with the obvious caveat that a host rebooting will reset what’s been gathered. Remember: as offensive operators what we really care about is access. Credentials are just a means to that end, but there are other ways to get there. Technical BackgroundNote: I’m going to do my best to explain why I believe this works, with the big caveat that I’m not a great Windows internals person. If I have any mistakes here please let me know and I’ll update the post! First, a bit of background on tokens, logon sessions, authentication packages, and credentials. Understanding how these all work together is important in order to understand why this attack technique is possible. I shall explain this in slide form: If this is still a bit confusing, check out Joseph Bialek’s OG blog on tokens/auth packages, Elastic’s excellent “Introduction to Windows tokens for security practitioners” post, the Incognito whitepaper, or join us at our Adversary Tactics: Red Team Operations course (where these slides are from) that we will be teaching in person at Black Hat USA this year. Tokens act as our Windows-approved bridge to utilize impersonated credentials. However, for many years we offensive operators have had a problem: when we stole/cloned a newly created token for an account that logged onto a host we control, the logon sessions linked to the stolen token would sometimes be destroyed at some point after the user logged off the system. This isn’t ideal offensively, and was one of the many reasons (in my opinion at least) many operators deprioritized token abuse in favor of LSASS credential extraction in the early 2010s. Note: I didn’t confirm this exact cleanup behavior as I didn’t want to go through the process of setting up an old enough Windows VM, and the old behavior doesn’t really matter after 2016 anyway (which I’ll explain shortly). I also wasn’t able to track down exactly how long logon sessions would originally stick around after a user logged off but a linked token was still leaked — if anyone knows what the old process was or if I’m misunderstanding something please let me know. This all changed with MS16–111. Ryan Ries published an amazing post in 2017 titled “Using Debugging Tools to Find Token and Session Leaks”. In it he describes one of the effects of MS16–111 (emphasize mine):
This means that if an application doesn’t properly release a token with CloseHandle(), the associated logon session (and therefore any associated cached credentials) should remain on the host indefinitely. Specifically, when a new logon session is established on a system, a new token for the logon session is created by LSASS using the NtCreateToken() API call and returned to the caller of LsaLogonUser(). This increases the ReferenceCount field of the logon session kernel structure. When this ReferenceCount reaches 0, the logon session is destroyed. Because of MS16–111 as described above, Windows now will NOT release a logon session if a token handle is still linked to it and therefore the reference count != 0. Again, this is as I understand it, but I can’t guarantee this explanation is correct. Offensively, instead of relying upon sloppy code leaking tokens/logon sessions, why don’t we intentionally cause these leaks for the purpose of offensive credential preservation? If we can open a token handle linked to a newly created logon session, we can keep that logon session open and later impersonate that token to utilize any cached credentials it contains. And as the MS16–111 patch was applied to Windows 7/10 and Server 2008/2012, this technique should apply for pretty much all modern Windows systems we encounter. ApproachesOur goal is to open a token handle linked to a logon session for a user account we want to preserve access for. Previously, the approach for token abuse was often as follows, credit to Joseph Bialek’s OG “PowerShell and Token Impersonation” post for the exact description:
There are two big downsides I see to this approach. First, we’re calling OpenProcess() against every process on the host. This is something that can trigger some EDRs. Second, there can be tokens linked to existing LogonSessions that aren’t actively used by any specific threads, which this approach would miss. That is, an application can have a handle to a token linked to a live logon session, but not be actively using it for impersonation. Instead, we can easily enumerate all current logon sessions on a host from an elevated context through the use of the LsaEnumerateLogonSessions() Win32 API. Then the tricky part is taking a specific logon session identifier (LUID) and somehow getting a usable token linked to that session. My coworker Lee and I brainstormed a number of possible approaches for this (if there are other approaches, let me know!):
Our ApproachThe SSPI AcquireCredentialsHandle() function has a pvLogonID field which states: 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: In order to utilize a logon session LUID with AcquireCredentialsHandle() you need SE_TCB_NAME (SeTcbPrivilege, e.g., running as SYSTEM) , however this is generally easier to get than SeCreateTokenPrivilege. Using this call while specifying a logon session ID (LUID) appears to increase the ReferenceCount for the logon session structure, preventing it (and linked credentials) from being released. However, we’re not presented with another problem: given the LUID of a purposely “leaked” logon session, how do we get a usable token from it? WTSQueryUserToken() only works with desktop sessions, and there’s no userland API that we could find that lets you map a LUID to a usable token. However we can pass the acquired credential handle to two additional SSPI functions, InitializeSecurityContext() and AcceptSecurityContext(), in order to act as client and server to ourselves. This lets us negotiate a new security context that we can supply to the QuerySecurityContextToken() function to get a usable token. This was documented in KB180548 (mirrored by PKISolutions here) for the purposes of credential validation and is a similar approach to Internal-Monologue, except we are completing the entire handshake process, producing a token, and then holding that for later use. Filtering can then be done on the token itself, via CheckTokenMembership() or GetTokenInformation(). For example, we could release any tokens except for ones belonging to Domain Admins, or other specific groups we want to target. Advantages
Disadvantages
Koh: The Token StealerOur tool that implements this, Koh, has two parts: a capture server to negotiate captured tokens for new logon sessions, and a client to utilize the captured tokens. The capture server is currently written in C# and the client is a Cobalt Strike BOF (big surprise, I know) but this approach would be relatively simple to implement in pretty much any language. Here we can see interacting with Koh from the published BOF, listing the current logon sessions. Note that THESHIRE\localadmin has no processes running as it was previously captured: And now, assuming SeImpersonatePrivilege (which we do have in our SYSTEM context), we can impersonate a captured token for a privileged user and reuse the access: ConclusionAs offensive operators, what we usually care about is access. Credentials are just a means to that end, but there are many ways to get there. By getting back to our token abusing roots, we can generalize token theft to its logical next step and harvest access for anyone who connects. We sure as hell didn’t invent token abuse, and most of these pieces existed publicly in various states, however we feel like this approach is an elegant amalgamation of various parts. We hope you find it useful! We need to experiment more with this in the field to see its effectiveness and operational concerns, so if you have any feedback or problems please let us know in the repo issues. Note: there is one BIG remaining issue with running the the Koh.exe assembly inline using something like InlineExecute-Assembly or Inject-Assembly. For details check out the README details on the “The Inline Shenanigans Bug”. Please help us figure this out! References
|