微软未沙盒化Windows Defender?我来实现它!

本文详细介绍了如何使用Rust语言构建Flying Sandbox Monster工具,将Windows Defender的MpEngine组件沙盒化,通过AppContainer技术实现安全隔离,并分享Rust在Windows平台开发的实践经验。

微软未沙盒化Windows Defender,所以我来实现 - The Trail of Bits博客

Trail of Bits
2017年8月2日
mitigations, rust

微软在发布Windows Defender时未采用沙盒技术,使其用户面临大量风险。这令我感到惊讶。沙盒化是最有效的安全加固技术之一。为何微软沙盒化了其他高价值攻击面(如Microsoft Edge中的JIT代码),却让Windows Defender毫无防护?

作为概念验证,我为微软沙盒化了Windows Defender,并将我的代码作为Flying Sandbox Monster开源。Flying Sandbox Monster的核心是AppJailLauncher-rs,这是一个基于Rust的框架,用于在AppContainer中封装不可信的应用程序。它还允许您将应用程序的I/O包装在TCP服务器后面,使沙盒化应用程序可以在完全不同的机器上运行,实现额外的隔离层。

在这篇博客文章中,我描述了创建此工具的过程和结果,以及对Rust在Windows上使用的思考。

计划

Windows Defender对其主机机器的无限制访问以及对危险文件格式的大规模接受使其成为恶意黑客的理想目标。核心Windows Defender进程MsMpEng以SYSTEM权限作为服务运行。扫描组件MpEngine支持解析天文数字般的文件格式。它还捆绑了各种架构的全系统模拟器和各种语言的解释器。所有这些都在Windows系统上以最高权限执行。哎呀。

这让我思考。用我两年前为CTF社区沙盒化挑战所使用的同一套工具来沙盒化MpEngine会有多困难?

沙盒化Windows Defender的第一步是能够启动AppContainer。我想重用AppJailLauncher,但有一个问题。原始的AppJailLauncher是作为概念验证示例编写的。如果我当时有任何理智,我会用C++ Core编写它,而不是处理内存管理的痛苦。在过去的两年里,我尝试用C++重写它,但都以失败告终(为什么依赖项总是这么麻烦?)。

但后来灵感来了。为什么不使用Rust重写AppContainer启动代码呢?

构建沙盒

几个月后,在快速学习Rust教程并编写了大量Rust示例代码后,我拥有了在Rust中启动AppContainer的三个支柱支持:SimpleDacl、Profile和WinFFI。

SimpleDacl是一个通用类,处理在Windows上添加和删除简单的自由访问控制条目(ACE)。虽然SimpleDacl可以针对文件和目录,但它有一些缺点。首先,它完全用新的ACL覆盖现有的ACL,并将继承的ACE转换为“正常”ACE。此外,它忽略任何无法解析的ACE(即除AccessAllowedAce和AccessDeniedAce之外的任何内容。注意:我们不支持强制和审计访问控制条目。)。

Profile实现了AppContainer配置文件和进程的创建。从配置文件中,我们可以获得一个SID,用于在AppContainer需要访问的资源上创建ACE。

WinFFI包含了winapi-rs未实现的大部分函数和结构,以及有用的实用类/函数。我努力将每个原始的HANDLE和指针包装在Rust对象中,以管理它们的生命周期。

接下来,我需要了解如何与Windows Defender的扫描组件交互。Tavis Ormandy的loadlibrary存储库已经提供了一个C实现示例和启动MsMpEng扫描的说明。将结构和函数原型移植到Rust是一个简单的自动化过程,尽管我最初忘记了数组字段和函数指针,这导致了各种问题;然而,借助Rust的内置测试功能,我迅速解决了所有移植错误,并拥有了一个扫描EICAR测试文件的最小测试用例。

我们的概念验证Flying Sandbox Monster由一个沙盒包装器和恶意软件保护引擎(MpEngine)组成。单个可执行文件有两种模式:父进程和子进程。模式由包含要扫描文件的HANDLE和子/父通信的环境变量决定。父进程在创建AppContainer子进程之前填充这两个HANDLE值。现在沙盒化的子进程加载恶意软件保护引擎库并扫描输入文件中的恶意软件。

这还不足以让概念验证工作。恶意软件保护引擎拒绝在AppContainer内初始化。最初,我认为这是一个访问控制问题。在ProcMon中进行广泛的差异调试(比较AppContainer与非AppContainer执行)后,我意识到问题可能实际上与检测到的Windows版本有关。Tavis的代码总是自报告Windows版本为Windows XP。我的代码报告了真实的底层操作系统;在我的情况下是Windows 10。通过WinDbg验证证明这确实是导致初始化失败的唯一问题。我需要向MpEngine谎报底层Windows版本。当使用C/C++时,我会用Detours编写一些函数钩子代码。不幸的是,Windows上没有等效的Rust函数钩子库(可用的几个钩子库似乎比我需要的“重量级”得多)。自然,我在Rust中实现了一个简单的IAT钩子库(仅限32位Windows PE)。

介绍AppJailLauncher-rs

既然我已经在Rust中实现了AppJailLauncher的核心组件,为什么不完成工作并将其全部包装在Rust TCP服务器中呢?我做到了,现在我很高兴宣布AppJailLauncher的“版本2”,AppJailLauncher-rs。

AppJailLauncher是一个TCP服务器,监听指定端口并为每个接受的TCP连接启动一个AppContainer进程。我试图不重新发明轮子,但mio,Rust的轻量级IO库,就是不行。首先,mio的TcpClient在Windows上不提供对原始“socket HANDLEs”的访问。其次,这些原始“socket HANDLEs”不能被子AppContainer进程继承。由于这些问题,我不得不引入另一个“支柱”来支持appjaillauncher-rs:TcpServer。

TcpServer负责实例化一个异步TCP服务器,其客户端套接字与STDIN/STDOUT/STDERR重定向兼容。通过socket调用创建的套接字不能重定向进程的标准输入/输出流。正常工作标准输入/输出重定向需要“本机”套接字(通过WSASocket构建)。为了允许重定向,TcpServer创建这些“本机”套接字,并不显式禁用它们的继承。

我的Rust体验

尽管有一些小挫折,我对Rust的整体体验非常积极。让我描述一些在AppJailLauncher开发过程中真正突出的关键特性。

Cargo。在Windows上使用C++进行依赖管理既繁琐又复杂,尤其是在链接第三方库时。Rust通过cargo包管理系统巧妙地解决了依赖管理问题。Cargo有广泛的包,解决了许多常见问题,如参数解析(clap-rs)、Windows FFI(winapi-rs等)和处理宽字符串(widestring)。

内置测试。C++应用程序的单元测试需要第三方库和繁琐的手动努力。这就是为什么像原始AppJailLauncher这样的小项目很少编写单元测试。在Rust中,单元测试功能内置在cargo系统中,单元测试与核心功能共存。

宏系统。Rust的宏系统在抽象语法树(AST)级别工作,不像C/C++中的简单文本替换引擎。虽然有一些学习曲线,但Rust宏完全消除了C/C++宏的烦恼,如命名和范围冲突。

调试。在Windows上调试Rust就是有效。Rust生成与WinDbg兼容的调试符号(PDB文件),提供无缝的源代码级调试。

外部函数接口。Windows API是用C/C++代码编写的,并旨在从C/C++代码调用。其他语言,如Rust,必须使用外部函数接口(FFI)来调用Windows API。Rust对Windows的FFI(winapi-rs crate)大部分是完整的。它有核心API,但缺少一些较少使用的子系统,如访问控制列表修改API。

属性。设置属性非常繁琐,因为它们只适用于下一行。压制特定代码格式警告需要在程序代码中散布属性。

借用检查器。所有权的概念是Rust实现内存安全的方式。理解借用检查器的工作原理充满了晦涩、独特的错误,需要花费数小时阅读文档和教程。最终是值得的:一旦“点击”,我的Rust编程 dramatically improved。

向量。在C++中,std::vector可以将其后备缓冲区暴露给其他代码。即使后备缓冲区被修改,原始向量仍然有效。Rust的Vec不是这样。Rust的Vec需要从旧Vec的“原始部分”形成一个新的Vec对象。

Option和Result类型。本机option和result类型应该使错误检查更容易,但错误检查似乎更冗长。可以假装错误永远不会存在,只是调用unwrap,但当错误(或None)不可避免地返回时,这将导致运行时失败。

拥有类型和切片。拥有类型及其互补切片(例如String/str、PathBuf/Path)需要一点时间来适应。它们成对出现,名称相似,但行为不同。在Rust中,拥有类型表示可增长的可变对象(通常是字符串)。切片是不可变字符缓冲区的视图(也通常是字符串)。

未来

Windows的Rust生态系统仍在成熟。有足够的空间用于新的Rust库来简化Windows上安全软件的开发。我已经实现了几个Rust库的初始版本,用于Windows沙盒化、PE解析和IAT钩子。我希望这些对初生的Windows Rust社区有用。

我使用Rust和AppJailLauncher沙盒化了Windows Defender,微软的旗舰反病毒产品。我的成就既伟大又有点可耻:伟大的是Windows强大的沙盒机制暴露给第三方软件。可耻的是微软没有自行沙盒化Defender。微软在2004年购买了最终成为Windows Defender的产品。回到2004年,这些错误和设计决策是不可接受的,但可以理解。在过去的13年里,微软开发了一个伟大的安全工程组织,先进的模糊测试和程序测试,并沙盒化了Internet Explorer的关键部分。不知何故,Windows Defender卡在了2004年。与其采取Project Zero的方法,通过不断指出这种固有缺陷的症状,让我们将Windows Defender带回未来。

如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News

页面内容
计划
构建沙盒
介绍AppJailLauncher-rs
我的Rust体验
未来
近期帖子
Trail of Bits的Buttercup在AIxCC挑战中获得第二名
Buttercup现已开源!
AIxCC决赛:磁带故事
攻击者的提示注入工程:利用GitHub Copilot
在NVIDIA Triton中 uncovering内存损坏(作为新员工)
© 2025 Trail of Bits。 使用Hugo和Mainroad主题生成。

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