绕过ACL:探索WindowsApps文件夹访问技术

本文详细探讨了如何绕过Windows系统中的访问控制列表(ACL)来访问受保护的WindowsApps文件夹,涉及令牌模拟、进程操作等底层技术,并提供了完整的PowerShell实现代码。

绕过ACL:探索WindowsApps文件夹访问技术

最近关于Windows 11的Recall功能及其安全性的讨论很多。特别是关于存储银行详情、性癖等敏感信息的数据库如何防范恶意软件窃取的讨论。剧透警告:该数据库仅通过SYSTEM的ACL进行保护,因此任何权限提升(或非安全边界咳嗽)都足以泄露信息。

不过,我并没有花时间在任何我拥有的机器上设置Recall,而且这些文件可能正确地设置了ACL。因此,本文不讨论Recall,而是关注Albacore在Mastodon上关于Recall和数据库安全性的讨论,其中一条消息引起了我的兴趣:

“@DrewNaylor 文件资源管理器始终以非提升权限运行,管理员也可以访问C:\Program Files\WindowsApps,但无论你怎么尝试,不破坏ACL就无法在文件资源管理器中打开它。”

基于我对"C:\Program Files\WindowsApps"文件夹的了解,我认为这不正确,因此我决定看看是否能在非提升权限的资源管理器中显示它。由于各种原因,这比应有的更复杂,让我们深入探讨。

什么是WindowsApps文件夹?

WindowsApps文件夹用于存储打包应用程序的系统安装。想想UWP、Desktop Bridge、计算器等。确实,如果你尝试从非提升权限的应用程序查看该文件夹,会收到访问被拒绝的错误:

1
2
PS> ls 'C:\Program Files\WindowsApps\'
ls : Access to the path 'C:\Program Files\WindowsApps' is denied.

微软为什么要这样做,这似乎不是一个安全敏感的位置?如果我必须猜测,这是为了防止用户浏览到打包应用程序并双击可执行文件,然后在不起作用时感到困惑。打包应用程序大多是普通的PE文件,但它们不能直接执行。相反,需要调用涉及COM和/或容器API的复杂序列来设置运行时环境。这个猜测似乎是合理的,因为如果你知道打包应用程序的名称,没有什么能阻止你列出其内容,只有顶层的WindowsApps文件夹被阻止。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
PS> ls 'C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_11.2403.6.0_x64__8wekyb3d8bbwe'
    Directory: C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_11.2403.6.0_x64__8wekyb3d8bbwe
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2024-05-15     19:42                AppxMetadata
d-----       2024-05-15     19:42                Assets
-a----       2024-05-15     19:42         54073 AppxBlockMap.xml
-a----       2024-05-15     19:42         11431 AppxManifest.xml
-a----       2024-05-15     19:42         12255 AppxSignature.p7x
-a----       2024-05-15     19:42       4179968 CalculatorApp.dll
-a----       2024-05-15     19:42         19456 CalculatorApp.exe
<SNIP>

无法访问WindowsApps文件夹的原因似乎是ACL。因此,从管理员PowerShell提示符,我们可以检查ACL是什么:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
PS> Format-Win32SecurityDescriptor 'C:\Program Files\WindowsApps\' -MapGeneric
Path: C:\Program Files\WindowsApps\
Type: File
Control: DaclPresent, DaclAutoInherited, DaclProtected
<SNIP>
 - Type  : AllowedCallback
 - Name  : BUILTIN\Users
 - SID   : S-1-5-32-545
 - Mask  : 0x001200A9
 - Access: GenericExecute|GenericRead
 - Flags : None
 - Condition: Exists WIN://SYSAPPID

我删除了所有输出,只保留了最后的ACE,因为这是重要的一个。ACL的其余部分没有其他引用普通用户的ACE,只有管理员。它显示BUILTIN\Users组应该获得读取和执行访问权限,但前提是用户访问令牌中存在WIN://SYSAPPID安全属性。这就是为什么你不能在资源管理器中列出文件夹,进程令牌没有WIN://SYSAPPID属性,因此访问被拒绝。

WIN://SYSAPPID属性是做什么的?当打包应用程序执行时,它被添加到访问令牌中,并包含有关包标识的信息。然后应用程序或内核可以引用它来检查进程所属包的信息。由于在令牌上设置此安全属性需要SeTcbPrivilege,因此很难伪造。

在这种情况下,我们不需要伪造属性的特定值,它只需要存在。我们不能创建它,但也许已经有一个我们可以借用的访问令牌来给我们访问权限。

寻找合适的访问令牌

很可能有一个以用户身份运行的进程设置了WIN://SYSAPPID属性。由于该进程的主令牌应该是同一用户,因此没有什么能阻止我们模拟它以访问WindowsApps文件夹。首先让我们找到一个具有合适令牌的进程:

1
2
3
4
5
6
PS> $ps = Get-NtProcess -FilterScript { 
 Use-NtObject($token = Get-NtToken -Process $_ -Access Query, Duplicate) { 
     "WIN://SYSAPPID" -in $token.SecurityAttributes.Name -and -not $token.AppContainer 
 }}
PS> $ps.Count
7

此脚本枚举所有可访问的进程,然后将它们过滤到仅具有带有WIN://SYSAPPID属性的令牌的进程。我们还过滤掉任何App Container令牌,因为它们可能也无法访问文件夹,而且处理它们更麻烦。我们还确保可以打开令牌进行Duplicate访问,因为我们需要调用DuplicateToken从主令牌获取模拟令牌。在此示例中,我们可以看到有7个进程符合我们的条件。

最后,我们可以通过获取模拟令牌,模拟它然后枚举WindowsApps文件夹来测试访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
PS> $token = Get-NtToken -Process $ps[0] -Duplicate
PS> Invoke-NtToken $token { 
    ls 'C:\Program Files\WindowsApps\' 
}
    Directory: C:\Program Files\WindowsApps
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2024-05-31     07:50                Deleted
d-----       2020-09-25     07:18                DeletedAllUserPackages
d-----       2019-12-07     01:53                Microsoft.Advertising...
<SNIP>

它工作了!然而,这实际上不是原始消息所说的。我应该能够在非提升权限的资源管理器窗口中显示WindowsApps文件夹。让我们试试看。

完成工作

由于进程令牌是我们自己的用户并且在我们自己的登录会话中,那么我们满足从该进程复制新主令牌并使用CreateProcessAsUser的条件。不幸的是,有一个大问题,如果你运行资源管理器的新副本,它会意识到已经有一个实例在运行,并调用现有实例并显示新窗口。因此,永远不会有一个资源管理器副本运行带有你指定令牌的UI。有一个你可以传递的"/SEPARATE"命令行参数,它确实创建了一个新的UI实例。不幸的是,不是你启动的进程留下来,而是通过COM生成一个新的资源管理器实例,那是托管UI的实例。

相反,“最简单"的方法是终止所有资源管理器实例并生成一个新的。有点苛刻,但我认为公平。你应该以非零退出代码终止,否则资源管理器实例将自动重新启动。然而,还有第二个问题。如果你指定一个带有WIN://SYSAPPID属性的主令牌,你会发现一旦进程启动,它就不再存在。这是由于内核在为进程构建新令牌时剥离了此属性。有各种方法可以解决这个问题,但最简单的是以挂起方式启动进程,然后使用NtSetInformationProcess将令牌交换为带有属性的令牌。创建后设置令牌不会剥离属性。将所有内容放在一起:

1
2
3
4
5
6
PS> $ex = Get-NtProcess -Name 'explorer.exe'
PS> $ex.Terminate(1)
PS> $token = Get-NtToken -Process $ps[0] -Duplicate -TokenType Primary
PS> $p = New-Win32Process "explorer.exe" -CreationFlags Suspended
PS> Set-NtToken -Process $p -Token $token
PS> Resume-NtProcess -Process $p

现在你可以导航到WindowsApps文件夹并查看结果。

希望这能让你对尝试绕过ACL的思考过程有所了解。

更新 2024/06/05 - 结果我错了,Recall并不安全。他们使用我在这篇博客文章中描述的相同技术,只是他们需要特定的WIN://SYSAPPID,例如"MicrosoftWindows.Client.AIX_cw5n1h2txyewy”。你可以通过打开AIXHost.exe的实例,获取其令牌并使用它来访问数据库文件来获取此属性的令牌。或者,由于文件由用户拥有,你可以仅重写文件的DACL并以这种方式获得访问权限,不需要管理员;-)

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计