Bypassing UAC with SSPI Datagram Contexts

Bypassing UAC with SSPI Datagram Contexts

Recently i had the opportunity to read through some of my old repos because i wanted to reuse some code i used for a UIPI bypass in the past, aiming to adapt it to a new hidden feature of the task manager for a sneaky and for-fun UAC bypass.
最近,我有机会通读了一些旧的存储库,因为我想重用过去用于 UIPIbypass 的一些代码,旨在使其适应任务管理器的新隐藏功能,以实现偷偷摸摸和有趣的 UAC 绕过。

Luckily, i stumbled upon another UAC related project (a 2 years old project) in which i tried to implement an idea to bypass UAC through some particular SSPI configurations, but i failed miserably that time.
幸运的是,我偶然发现了另一个与 UAC 相关的项目(一个 2 年前的项目),在这个项目中,我试图实现一个通过一些特定的 SSPI 配置绕过 UAC 的想法,但那次我惨败了。

Upon re-reading the code, a light bulb came to my mind so i tried a different exploitation path and it ended up with a new cool UAC bypass! So let’s jump straight to it 👇
重新阅读代码后,我想到了一个灯泡,所以我尝试了不同的利用路径,最终得到了一个新的很酷的 UAC 绕过!所以让我们直接跳到它 👇

UAC: User Account Control (formerly LUA - Limited User Account)
UAC:用户帐户控制(以前称为 LUA - 受限用户帐户)

To provide some context, UAC (User Account Control) is an elevation mechanism within Windows designed to trigger a consent prompt when an action requires administrative privileges. This consent prompt is intended to enforce privilege separation by requiring administrator approval.
为了提供一些上下文,UAC(用户帐户控制)是 Windows 中的一种提升机制,旨在当操作需要管理权限时触发同意提示。此同意提示旨在通过要求管理员批准来强制实施权限分离。

While designed to add an extra layer of security against unauthorized OS changes, it has been proven to be a full of holes design.

There are many known ways to bypass UAC and perform actions with elevated privileges without any prompt or consent provided interactively by the user.
有许多已知的方法可以绕过 UAC 并使用提升的权限执行操作,而无需用户以交互方式提供任何提示或同意。

You can consult UACMe for a curated list and related source code of known UAC bypasses (fixed and unfixed 🙈). 
您可以查阅 UACMe 以获取已知 UAC 绕过(固定和未固定🙈)的精选列表和相关源代码。


I bet you have encountered this screen at some point. Yep, that's the UAC consent prompt for you:
我敢打赌你在某个时候遇到过这个屏幕。是的,这是为您提供的 UAC 同意提示:

Bypassing UAC with SSPI Datagram Contexts

In this post i’m not going to detail the internal working of UAC, but if you are interested in knowing more there is already a lot of research about it. You can find some comprehensive talks and blog posts in the References section.
在这篇文章中,我不打算详细介绍 UAC 的内部工作,但如果您有兴趣了解更多信息,已经有很多关于它的研究。您可以在“参考”部分找到一些全面的谈话和博客文章。

An interesting behavior in NTLM authentications
NTLM 身份验证中一个有趣的行为

In Windows exists the fantastic concept of “type your password once and authenticate everywhere”. This is the same basic concept as any Single Sign-On system but integrated directly into the operating system.

In order for this to work someone has to store your passwords and that’s where the LSA comes into play. It provides a layer of abstraction for any related authentication happening on your system.


Without going into the deep details, what you need to know is that the LSA (implemented in lsass.exe) loads authentication packages DLLs by using configuration information stored in the registry. Loading multiple authentication packages permits the LSA to support multiple security protocols, e.g. NTLM, Kerberos and so on… 
在不深入细节的情况下,您需要知道的是 LSA(在 lsass.exe 中实现)通过使用存储在注册表中的配置信息加载身份验证包 DLL。加载多个身份验证包允许 LSA 支持多种安全协议,例如 NTLM、Kerberos 等。


When you log on interactively, the LSA creates a new logon session, associates it with your credentials, and creates a token for your process that references this newly created logon session.
以交互方式登录时,LSA 会创建一个新的登录会话,将其与您的凭据相关联,并为引用此新创建的登录会话的进程创建令牌。

In this way, when your process tries to access a remote resource, let’s say \\SHARE-SERVER\share1\file.txt, your process can invoke the SSPI functions to retrieve the security buffers to send over the network wire and the authentication is abstracted from the application logic without the needs of providing explicit credentials.
这样,当您的进程尝试访问远程资源(例如 \\SHARE-SERVER\share1\file.txt)时,您的进程可以调用 SSPI 函数来检索要通过网络线路发送的安全缓冲区,并且身份验证是从应用程序逻辑中抽象出来的,而无需提供显式凭据。
What happens under the hood is that when your application invokes the SSPI functions, it communicates with lsass.exe, which in turn will inspect your process (or thread if impersonating) token and will be able to associate your right credentials and derive the proper authentication buffers that your process can use for the authentication.
底层发生的情况是,当应用程序调用 SSPI 函数时,它会与 lsass.exe 通信,后者反过来将检查进程(或线程,如果模拟)令牌,并能够关联正确的凭据并派生进程可用于身份验证的正确身份验证缓冲区。

This is an oversimplified explanation, but hopefully you got the point.


When network authentication takes place, UAC restrictions don't affect the generated token.
进行网络身份验证时,UAC 限制不会影响生成的令牌。

There are 2 exceptions to this rule:
此规则有 2 个例外:

  • if you're authenticating to a remote machine using a shared local administrator account (except built-in Administrator);
  • if you are doing a loopback authentication without SPPI and using a local administrator user. You need to know the password or at least the hash of the user.
    如果您在没有 SPPI 的情况下执行环回身份验证并使用本地管理员用户。 您需要知道密码或至少知道用户的哈希值。

Only in these cases UAC Remote restrictions kick in.
只有在这些情况下,UAC 远程限制才会启动。
These restrictions will limit also the token generated by the network authentication on the server end if LocalAccountTokenFilterPolicy is set to 0, which is the default configuration.
如果 LocalAccountTokenFilterPolicy 设置为 0(这是默认配置),则这些限制还将限制服务器端的网络身份验证生成的令牌。

Instead, if you use a domain user which is also an administrator of the machine, UAC won’t get in the way:
相反,如果您使用同时也是计算机管理员的域用户,UAC 不会妨碍:

Bypassing UAC with SSPI Datagram Contexts

UAC Remote restrictions for domain users
域用户的 UAC 远程限制


The main mechanism that is preventing anyone from getting around UAC locally through SSPI is Local Authentication.
阻止任何人通过 SSPI 在本地绕过 UAC 的主要机制是本地身份验证。
To understand it, let’s take out of the equation the local authentication with Kerberos and focus on NTLM. (NOTE: James Forshaw already demonstrated how UAC restrictions over Kerberos can be bypassed locally in this blogpost)
为了理解它,让我们从使用 Kerberos 的本地身份验证等式中取出,并专注于 NTLM。(注意:James Forshaw 已经在本博文中演示了如何在本地绕过对 Kerberos 的 UAC 限制)


If you are familiar with NTLM authentications, you can identify a local authentication by observing these details in the messages exchange:
如果您熟悉 NTLM 身份验证,则可以通过在消息交换中观察以下详细信息来标识本地身份验证:

  • The server sets the “Negotiate Local Call” flag in the Challenge message (Type 2);
    服务器在质询消息中设置“协商本地呼叫”标志(类型 2);
  • The “Reserved” field in the Challenge message is not 0 and contains a number referencing the server context handle;
    质询消息中的“保留”字段不为 0,并且包含引用服务器上下文句柄的数字;
  • The generated Authenticate message (Type 3) by the client contains empty security buffers;
    客户端生成的身份验证消息(类型 3)包含空的安全缓冲区;

When this occurs, LSASS is able to associate the calling process's actual token with the server application's security context. As a result, any UAC restrictions on the client side become visible to the server application.
发生这种情况时,LSASS 能够将调用进程的实际令牌与服务器应用程序的安全上下文相关联。因此,客户端上的任何 UAC 限制对服务器应用程序都是可见的。


Ok, enough theory. Let’s see the differences in the token when doing a local vs remote NTLM authentication through SSPI:
好的,理论够多了。让我们看看通过 SSPI 执行本地与远程 NTLM 身份验证时令牌的差异:

Bypassing UAC with SSPI Datagram Contexts

System Informer token views of local auth (left) vs remote auth (right)


The result is that the token returned from the local authentication has the UAC limitations, in fact you can see the IL levelis Medium and the Administrators SID is disabled.
结果是从本地身份验证返回的令牌具有 UAC 限制,实际上可以看到 IL 级别为“中”,管理员 SID 已禁用。

Instead, the remote authentication occurred without UAC limitations and the resulting elevated token is set in High IL.
相反,远程身份验证在没有 UAC 限制的情况下进行,生成的提升令牌设置为高 IL。

One important difference here is in the logon type SID present in the token, on the filtered token there is the INTERACTIVE SID while in the elevated token there is the NETWORK SID.
此处的一个重要区别是令牌中存在的登录类型 SID,在筛选的令牌上有交互式 SID,而在提升的令牌中有网络 SID。


So the 1 million $ question is: can we fake a network authentication locally with NTLM through SSPI?

The unexpected bit flag 意外的位标志

If we want to trick LSASS during local authentications, first we need to understand when and how this decision takes place in the code.
如果我们想在本地身份验证期间欺骗 LSASS,首先我们需要了解此决定何时以及如何在代码中发生。

Let’s reverse msv1_0.dll and search for the function which sets the flag 0x4000 (NTLMSSP_NEGOTIATE_LOCAL_CALL):
让我们反转msv1_0.dll并搜索设置标志0x4000 (NTLMSSP_NEGOTIATE_LOCAL_CALL) 的函数:


SsprHandleNegotiateMessage reversed code that sets the “Negotiate Local Call” flag
SsprHandleNegotiateMessage 反转了设置“协商本地调用”标志的代码

Bypassing UAC with SSPI Datagram Contexts

Without surprise we landed to the function SsprHandleNegotiateMessage. What this function does is to handle the Negotiate message received by the client and generate the proper Challenge. From the code perspective we land here in the first server call to AcceptSecurityContext.
不出所料,我们找到了函数SsprHandleNegotiateMessage。此函数的作用是处理客户端收到的协商消息并生成适当的质询。从代码的角度来看,我们在这里对 AcceptSecurityContext 的第一个服务器调用。
The logic of this code for detecting a local authentication is pretty straightforward: if the domain name and machine name provided by the client in the Negotiate message matches with the local machine name and domain, then this is a local authentication case.
此代码用于检测本地身份验证的逻辑非常简单:如果客户端在 Negotiate 消息中提供的域名和计算机名称与本地计算机名称和域匹配,则这是一个本地身份验证案例。


But how we get into this part in the code? Let’s cross reference the if above that branch:
但是我们如何进入代码中的这一部分呢?让我们交叉引用该分支上方的 if:

Bypassing UAC with SSPI Datagram Contexts

SsprHandleNegotiateMessage reversed code that checks Negotiate flags
SsprHandleNegotiateMessage 反向检查 Negotiate 标志的代码

So the function is checking the Negotiate flags supplied by the client and specifically checks if NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED and NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED are set, which is always true if you use SSPI in the latest Windows versions.

However, what in the hell is the other checked flag NTLMSSP_NEGOTIATE_DATAGRAM ?
Googling around brought me to Datagram Contexts.
I still haven’t understood what is the intended behavior usage for this feature, but all i needed to know is that i can set this “mode” from the client by using the flag ISC_REQ_DATAGRAM in the first InitializeSecurityContext client call. Hopefully, by doing so, i would have forced the intended network auth i was aiming for.
我仍然不明白此功能的预期行为用法是什么,但我需要知道的是,我可以通过使用第一个 InitializeSecurityContext 客户端调用中的标志ISC_REQ_DATAGRAM从客户端设置此“模式”。希望通过这样做,我会强制我想要的预期网络身份验证。

The only thing to take into consideration is that mode is using connection-less context semantics and could be problematic to synchronize with external services. But… for our case we can run the server and client within the same process and we should be good. Even if it sounds very weird, it’s what we need… In the end we just need to trick LSASS to forge the token for us.

Let’s sort out all of the code and check how the generated security buffers appears while using Datagram Contexts:

Bypassing UAC with SSPI Datagram Contexts

NTLM message exchanges with Datagram Contexts
与数据报上下文的 NTLM 消息交换

Observing the security buffers exchanged, we can see that the “Negotiate Local Flag” is not set and that the “Reserved” bytes are 0, so no context handle has been passed by the server. Moreover, the client also sent the NTLMv2 Response in the Authenticate message. It definitely looks like the client and server are not negotiating a local authentication.
观察交换的安全缓冲区,我们可以看到未设置“协商本地标志”,并且“保留”字节为 0,因此服务器没有传递上下文句柄。此外,客户端还在身份验证消息中发送了 NTLMv2 响应。看起来客户端和服务器肯定没有协商本地身份验证。
Note that the Negotiate message (Type 1) generated in Datagram-style authentication is empty and this is one of the significant differences compared to “normal” connection-oriented authentications.
请注意,在数据报样式身份验证中生成的协商消息(类型 1)为空,这是与“普通”面向连接的身份验证相比的显著差异之一。

Let’s inspect the token generated by this authentication and specifically if it contains the magic NETWORK SID logon type:
让我们检查此身份验证生成的令牌,特别是它是否包含魔术网络 SID 登录类型:

Bypassing UAC with SSPI Datagram Contexts

TokenViewer view of the generated token from Datagram-style authentication

The good news is that the NETWORK SID has been added in our token, so mission accomplished.
好消息是,网络 SID 已添加到我们的令牌中,因此任务已完成。

The very bad news is that somehow the token has been filtered by UAC. As you can see, the IL of the token is Mediumand is not even Elevated.
非常坏的消息是令牌以某种方式被 UAC 过滤了。如您所见,令牌的 IL 为“中”,甚至没有提升。
My assumption that Local Authentication is the only mechanism to filter tokens is wrong. Probably, LSASS has additional checks in place, which i don’t plan to discover. 

GAME OVER. 游戏结束。

Sharing a logon session a little too much, part 2
共享登录会话有点太多,第 2 部分

After almost 2 years from my last defeat against UAC, i decided to look again into this abandoned idea.

This time instead, i recalled the blog post “Sharing a Logon Session a Little Too Much” by James Forshaw and it inspired me for a different exploitation path.
这一次,我想起了 James Forshaw 的博客文章“共享登录会话有点太多”,它启发了我不同的开发路径。

What stands out from his blogpost is that when you do a loopback network authentication you can exploit a behavior of AcquireCredentialsHandle when used in network redirectors in which would result in LSASS using the first token created in the logon session rather than the caller’s token.
从他的博客文章中突出的是,当您执行环回网络身份验证时,您可以在网络重定向器中使用时利用 AcquireCredentialsHandle 的行为,这将导致 LSASS 使用在登录会话中创建的第一个令牌而不是调用方的令牌。

Bypassing UAC with SSPI Datagram Contexts

How that would apply in our case?

When we complete a Datagram-style authentication, LSASS creates a new logon session and creates the elevated token. Then, starting from the elevated token will create a new filtered token (LUA token) and the two are linked. The LUA token is the one actually associated with the security context “sent” to the server.
完成数据报样式的身份验证后,LSASS 会创建新的登录会话并创建提升的令牌。然后,从提升的令牌开始将创建一个新的筛选令牌(LUA 令牌),并且两者链接在一起。LUA 令牌是实际与“发送”到服务器的安全上下文关联的令牌。

LUA Token vs. Elevated Token properties differences
LUA 令牌与提升令牌属性差异

In the tokens generated in this way, the Logon Session ID (or Authentication ID from the token perspective) are equals and the Token ID values suggest that the Elevated Token is created before and likely is the first token created in that logon session. So according to this “token confusion” bug in LSASS, the server would see our call as it was originatingfrom our elevated token rather than our impersonated limited token.
在以这种方式生成的令牌中,登录会话 ID(或从令牌角度看的身份验证 ID)相等,令牌 ID 值表明提升的令牌是在之前创建的,并且可能是在该登录会话中创建的第一个令牌。因此,根据LSASS中的这个“令牌混淆”错误,服务器将看到我们的调用,因为它源自我们提升的令牌,而不是我们模拟的有限令牌。

To exploit this bug, we first need to check if we are able to impersonate the generated LUA token.
要利用此错误,我们首先需要检查是否能够模拟生成的 LUA 令牌。
According to Microsoft documentation of ImpersonateLoggedOnUser function we should be fine in impersonating a token as long as “the authenticated identity is same as the caller”, which is our case. However, it’s not entirely true. There are more conditions in place in the kernel function SeTokenCanImpersonate that is performing the checks:
根据 ImpersonateLoggedOnUser 函数Microsoft文档,只要“经过身份验证的身份与调用方相同”,我们就应该可以模拟令牌,这就是我们的情况。然而,这并不完全正确。在执行检查的内核函数 SeTokenCanImanalogate 中还有更多条件:

Bypassing UAC with SSPI Datagram Contexts

SeTokenCanImpersonate flow for impersonation decisions, from “Taking Kerberos to The Next Level

Comparing the token properties with our process’s token running under UAC limitations, all conditions appear to be met.
将令牌属性与在 UAC 限制下运行的进程令牌进行比较,似乎满足所有条件。

Cool! So let’s impersonate the token from the Datagram-style authentication and try to write to a named pipe over the loopback interface, e.g. \\\pipe\dummypipe
凉!因此,让我们模拟数据报样式身份验证中的令牌,并尝试通过环回接口写入命名管道,例如 \\\pipe\dummypipe

Bypassing UAC with SSPI Datagram Contexts

Pipe client thread vs. Pipe server thread tokens

Aaand BAM! We are able to authenticate over the loopback interface with our elevated token even if we are impersonating the filtered token! 🎉

Of course the pipe server is running with elevated privileges, otherwise the High IL token would have been downgraded to an Identification token.
当然,管道服务器以提升的权限运行,否则高 IL 令牌将降级为标识令牌。
But what about using this token for authenticating to an already running privileged service? Like the file-sharing service over SMB? It should be as easy as invoking CreateFile using an UNC path, like \\\C$\Windows\bypassuac.txt
但是,使用此令牌对已在运行的特权服务进行身份验证怎么样?喜欢SMB上的文件共享服务吗?它应该像使用 UNC 路径调用 CreateFile 一样简单,例如 \\\C$\Windows\bypassuac.txt

Bypassing UAC with SSPI Datagram Contexts

It worked!  成功了!

So at this point we have a privileged file write primitive that can be combined with any known DLL Hijacking technique to achieve EoP, such as using an XPS Print Job or NetMan DLL Hijacking
因此,在这一点上,我们有一个特权文件写入原语,可以与任何已知的DLL劫持技术结合使用以实现EoP,例如使用XPS打印作业或NetMan DLL劫持。

Privileged File Write is good but Code Execution is better 😀

If you remember, i previously showed you that i’m able to authenticate even to a named pipe with the elevated token.

Having privileged access to named pipes means we have access to all of the RPC servers running with ncacn_npconfiguration, which are a lot!
拥有对命名管道的特权访问权限意味着我们可以访问使用ncacn_np配置运行的所有 RPC 服务器,这很多!
So, why we don’t leverage this bug/feature to achieve code execution instead of our current privileged file write? We have a lot of juicy candidates like Remote SCM, Remote Registry, Remote Task Scheduler and so on…

However, if we try to authenticate to the Remote Registry through a RegConnectRegistryW call, it will fail to open handles to privileged regkeys.
但是,如果我们尝试通过 RegConnectRegistryW 调用向远程注册表进行身份验证,它将无法打开特权注册表项的句柄。
Let’s inspect the behavior:

Bypassing UAC with SSPI Datagram Contexts

WinDbg details of AcquireCredentialsHandle call from RegConnectRegistryW
WinDbg AcquireCredentialsHandle Call from RegConnectRegistryW 的详细信息

What it turns out is that the RPC runtime library (RPCRT4.dll) uses his own implementation for the authentication. As we can observe, the pvLogonId parameter for AcquireCredentialsHandleW is set to 0 which wouldn’t allow to trigger the bug in LSASS and would use the proper limited token for the auth.
事实证明,RPC 运行时库 (RPCRT4.dll) 使用自己的实现进行身份验证。正如我们所观察到的,AcquireCredentialsHandleWis 的 pvLogonId 参数设置为 0,这将不允许触发 LSASS 中的错误,并且将使用适当的有限令牌进行身份验证。

Now let’s see the difference when authenticating to the loopback interface with the CreateFileW function:
现在让我们看看使用 CreateFileW 函数对环回接口进行身份验证时的区别:

Bypassing UAC with SSPI Datagram Contexts

WinDbg details of AcquireCredentialsHandle call from CreateFileW
WinDbg 详细信息的 AcquireCredentialsHandle 来自 CreateFileW 的调用

The first difference we see here is that the authentication is implemented in the kernel by the SMB redirector driver mrxsmb20.sys.
我们在这里看到的第一个区别是身份验证是由 SMB 重定向器驱动程序 mrxsmb20.sys 在内核中实现的。

More important, the pvLogonId parameter for AcquireCredentialsHandleW is set to the logon session associated with our user, which is what would fool lsass in using the elevated token from that logon session.
更重要的是,AcquireCredentialsHandleWis 的 pvLogonId 参数设置为与我们的用户关联的登录会话,这会欺骗 lsass 使用该登录会话中提升的令牌。
According to the documentation, in order to specify the pvLogonId you need to have the SeTcbPrivilege, which is not a problem in this case due to the fact that the code is running with kernel privileges.

This means, unfortunately, we can’t use the RPC runtime library to authenticate to named pipes associated with RPC services if we want to exploit this bug.
这意味着,不幸的是,如果我们想利用这个错误,我们就无法使用 RPC 运行时库对与 RPC 服务关联的命名管道进行身份验证。

However, no one could prohibit us to use our own custom RPC client implementation that leverages the CreateFileWcall for authenticating to the RPC service over SMB. But this would require some hard work and i’m too lazy for that.
但是,没有人可以禁止我们使用自己的自定义 RPC 客户端实现,该实现利用 CreateFileW 调用通过 SMB 向 RPC 服务进行身份验证。但这需要一些努力,我懒得了。

But this time luck seems to have been turned to my side and i found out @x86matthew already did this for the service control manager RPC interface in CreateSvcRpc!
The only change we need to do is to force the usage of SMB instead of ALPC, that technically translates in changing the pipe path from \\.\pipe\ntsvcs to \\\pipe\ntsvcs
我们需要做的唯一更改是强制使用 SMB 而不是 ALPC,这在技术上转化为将管道路径从 \\.\pipe\ntsvcs 更改为 \\\pipe\ntsvcs

Let’s see the full chain in action 😎
让我们看看整个链条的实际应用 😎

Bypassing UAC with SSPI Datagram Contexts

PoC source code can be found at →
PoC 源代码可在 → 

Conclusion 结论

A couple of years ago, i put this project between the many things i failed, thinking i hit a wall. Now i see the way was always there... I just needed to look at it differently or with a different perspective. It turned out to be a new cool way to get around UAC.
几年前,我把这个项目放在我失败的许多事情之间,以为我碰壁了。现在我看到路一直在那里...我只需要以不同的方式或不同的角度看待它。事实证明,这是一种绕过 UAC 的新酷方式。

A big shout-out to James Forshaw and @x86matthew whose research provided invaluable insights and my friend @decoder_it for the proofread!

That's all folks, see you next time 👋

References 引用

原文始发于splinter_code:Bypassing UAC with SSPI Datagram Contexts

版权声明:admin 发表于 2023年9月15日 下午8:37。
转载请注明:Bypassing UAC with SSPI Datagram Contexts | CTF导航