一个WSLBOF来统治它们:跨版本COM接口的逆向工程与利用

admin 2026-01-20 01:35:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了一种针对WSL2的BOF工具,解决了因COM接口版本变化导致的兼容性问题。作者通过逆向工程提取不同版本WSL的IDL接口,实现了跨版本的命令执行与枚举。该工具允许红队人员在WSL2环境中隐蔽活动,并提供了完整的源代码与构建方法。 综合评分: 96 文章分类: 红队,内网渗透,逆向分析,渗透测试,安全工具


cover_image

一个 WSL BOF 来统治它们:跨版本 COM 接口的逆向工程与利用

DANIEL MAYER DANIEL MAYER

securitainment

2026年1月19日 11:59 中国香港

核心要点 –Windows Subsystem for Linux (WSL) 为攻击者提供了强大的隐身方式。WSL2 作为运行在 Hyper-V 中的完全独立虚拟机,几乎不受任何监控。我已多次成功地从受严格监控的 Windows 主机转入 WSL2,并在该主机和网络中自由活动,不受任何限制。

自然地,我想开发一个 Beacon 对象文件 (BOF) 来轻松枚举和在 WSL2 主机上执行命令,进一步发挥这个攻击原语的作用。不幸的是,WSL2 的组件对象模型 (COM) 接口在不同版本间经历了大量变化,但却共享同一个 CLSID;这既不符合微软一贯的做法,在开发跨版本兼容的 COM 客户端时也带来了巨大的麻烦。因此,SpecterOps 团队为大家踏上了这次发现之旅,最终将其融入了一个 BOF 工具中!你可以在这里找到它。

介绍

WSL 对 Windows 开发体验的提升是革命性的。我本人是其忠实拥护者;如今我将 Windows 视为一个便利的 Ubuntu 虚拟机外壳,可以在需要时调用 Windows EXE 文件。看起来我不是唯一的支持者——在红队测试中,越来越多的开发机器上都安装了 WSL 实例。这对攻击者而言是个大胜利,因为自 2019 年 WSL2 发布以来,通过 WSL2 运行的 Linux 发行版就是一个完全独立、运行在 Hyper-V 内的虚拟机

作为红队人员,你应该从这句话中看出:每台装有 WSL2 的主机都拥有一个飞地,你可以在其中生成进程、读取目标文件、访问内网,而无需担心端点监控。更妙的是,通常还能找到未受保护的 SSH 密钥、环境变量/dotfiles 中的凭证等。

我以前的做法是使用包装 CreateProcess 的 BOF 来调用 WSL:

Sketchy WSL 命令

虽然这从未被检出过(我认为端点监控解决方案还没有针对它的静态检测 – _至少目前没有_),但这让我很不安。这是一个可疑的进程,拥有恐怖的命令行,将从我的 agent 进程中生成。EDR 会记录这一点,如果防御者看到它,必然会知道有异常。基于这一认识,我开始寻求更好的方案。

顺便说一句,实际上我的做法并非如此极端;上面的例子是为了演示而特意夸大的。实际上,你可以通过 $WSL 共享将有效负载直接写入 WSL 文件系统,使用无害的名称然后执行。你也可以通过将 shell 脚本写入磁盘,然后仅调用 WSL.exe 来执行它,从而在运行多条命令时进行混淆。但核心问题是:一个随机进程生成 WSL.exe 看起来很可疑……至少我是这样认为的!

为什么你应该反编译 DLL 的一课

上周,我与 SpecterOps 的所有同事聚集在一起参加年度会议。我们举办了一场 24 小时的编程马拉松,我提议开发一个 BOF 来简化对 WSL2 的渗透。幸运的是,我成功吸引了两位杰出的队友——Adam Chester 和 Antero Guy——加入这个项目。

这个 BOF 看似简单直接:存在一个 API 函数 WslLaunch,由 wslapi.h/WslApi.dll 导出,它完全符合我们的需求。你只需提供一个 WSL 实例和一条命令,它就会在该发行版上执行你想要的命令。小菜一碟!

一个完美的导出函数,感谢微软!

所以我们成功了!从此幸福地生活;故事结束。

只是开玩笑!好吧,部分正确——我们确实开发了那个 BOF,你可以在这里看到,但如 ReadMe 所示,有一个重要的免责声明。WslLaunch 所做的一切就是生成一个 WSL.exe 进程!它通过你猜对了的方式——格式化命令并调用 CreateProcess 来实现,这与我在红队中的做法无异!

WslApi.dll 导出的 WslLaunch 中的命令格式化逻辑

这既让人欣慰又令人失望。这意味着从某个随机进程生成 WSL.exe来在 WSL 实例上运行命令实际上 不是那么异常。这类检测缺失的部分原因可能是:已有大量良性二进制文件在做我认为很可疑的事情!这是个胜利,但也意味着我们新开发的 BOF 形同虚设;我可以继续使用任何允许调用 CreateProcess 的 BOF 或工具来实现相同效果,任何情况下都会留下相同的工件,这是我担心防御者会发现的。

更糟糕的是:如果这是微软官方认可的通过 Windows API 访问 WSL 的方式,那说明 WSL.exe在幕后做的事情如此混乱,以至于微软开发人员要么不想在 DLL 中重新实现,要么根本无法实现。

COM 接口的混乱局面

微软开发人员为什么选择从 DLL 中仅生成 WSL.exe的答案很快浮出水面。Adam 在 GitHub 上发现了我们所需服务的 IDL 文件(即 wslservice.idl),因为 WSL 现已开源。完美!理论上这应该很容易:使用 MIDL.EXE从 IDL 文件生成适当的头文件,然后通过 COM 调用我们感兴趣的函数(即 CreateLxProcess)。

我们确实这样做了,成果发表在同一 GitHub 项目的 COM 分支上。然而,当三人测试时,代码只在 Antero 的机器上运行。他当天刚下载 WSL,而 Adam 和我使用的是较旧版本。当 Adam 和我运行它时,都因内存损坏而崩溃。这只能说明一个问题:COM 接口在版本间发生了变化,我们基于 WSL 仓库 IDL 生成的代码期望的是最新版本。Adam 和我的旧版本 COM 服务器 DLL 接收到了错误的参数,在编组过程中崩溃了。

作为非资深系统开发人员的我来说,这非常奇异。每当微软引入可能破坏兼容性的改动时,通常只是在名称后加上 Ex并将其重新实现为完全不同的函数!我从未见过相同 CLSID 的 COM 对象在版本间改变参数。

我们的理论在遇到 wslbridge2 项目时得到了证实。该项目反向工程了这个接口不同版本的虚表定义,并在头文件中友好地添加了注释,详细说明不同版本间的变化——这些变化非常剧烈!参数被添加、删除,函数被添加、删除,虚表函数顺序在版本间发生了变化。在 Adam 和我的机器上,使用 WSL GitHub 页面的 IDL 文件时,我们的代码甚至没有调用正确的 函数

难怪 WslApi.dll只是生成 WSL.exe_:微软深知这样做极其困难且容易出错,但可以推测WSL.exe_ 使用相同的 IDL 文件编译,因为两者作为一个包一起安装。很可能当其中一个更新时,另一个也会更新,因为它们通过相同的 MSI 进行更新。这个看似奇怪的进程生成方案实际上是为了最小化 Adam 和我在测试中遭遇的客户端/服务器不匹配的可能性。

评论高亮标记了新添加到 CreateLxProcess 的参数,这是一个破坏性变化

评论展示了已删除的函数,这也是一个破坏性变化

所以我们在黑客马拉松中以失败告终。我们无法使用仓库的 IDL 开发出能在任意机器上运行的可用 BOF;它只能在运行最新版本 WSL 的机器上工作。如果在其他版本的机器上使用,会导致内存访问错误,幸好我们用异常处理捕获了它,至少整个 agent 不会崩溃。考虑到 Adam 和我甚至不知道 如何更新 WSL,在实际部署环境中找到最新版本的希望微乎其微。

铸造一个 WSL BOF 来统治它们

获取 IDL

我可以告诉你,这个故事在 SpecterOps 博客中可能听起来很熟悉:一个团队成员尝试新鲜事物,遭遇障碍,希望破灭,然后 Lee Chagolla-Christensen 提出一个关键建议,问题迎刃而解。虽然有点老套,但这解释了为什么电影公司现在只制作续集。故事没坏就别修!

在这个案例中,Lee 提醒我们 OleView.Net——James Forshaw 用于与 COM 对象交互的研究工具——允许你动态生成有效的 IDL 文件来从 COM 服务器创建 COM 客户端。如果我们能够获得所有不同版本的 WSL,就可以做类似 wslbridge2 开发者所尝试的事:查询主机上的 WSL 版本,然后根据该版本调用相应的接口。

这是完美解决方案,因为自 2.0.0.0 以来的所有 WSL 版本都可在 GitHub 上获得,每个 MSI 内都包含 COM 代理存根 DLL(_WslServiceProxyStub.dll_)!

此时你可能有些疑问。让我快速解答:

什么是代理存根 DLL?实际的 COM 服务器运行在另一个进程中(WslService.exe),所以需要处理函数调用及其参数编组和传输的代码。存根就是实现这一功能的。

为什么代理存根 DLL 有用?因为它包含一个称为 ProxyFileList 的数据结构,通过 MIDL 生成,包含编组和解组 COM 接口调用所需的所有元数据。这包含足够的信息来重新生成我们后续可与 MIDL 一起使用的 IDL 文件。

James Forshaw 已在 OleView.NET 中为 ProxyFileList 实现了解析器,仅需两行代码即可:

使用 OleView.NET 解析 ProxyFileList

获取该数据结构指针的过程从相当简单到略显复杂不等。有时代理 DLL 会导出名为 GetProxyDllInfo 的函数,返回指向该数据结构的指针和代理 CLSID。我们没那么幸运:

WslServiceProxyStub.dll 缺少 GetProxyDllInfo 导出

当 GetProxyDllInfo 未导出时,你需要知道代理 DLL 的 CLSID,可通过几种方式找到:

  • 在 HKEY_CLASSES_ROOTCLSID 中搜索 DLL 名称,因为它会在那里自注册
  • 如果知道 IID,在 HKEY_CLASSES_ROOTInterfaceProxyStubClsid32 的注册表中查找
  • 使用 Resource Hacker 等工具检查 DLL,有时能在代理 DLL 本身的资源中找到
  • 通过 Google 搜索和 GitHub 上的其他项目获得——站在巨人肩膀上

一旦拥有代理的 CLSID,按这些步骤相当可靠地获取指向 ProxyFileList 的指针:

  • 调用 DllGetClassObject(由你的代理 DLL 导出),使用你的 CLSID 和 D5F569D0-593B-101A-B569-08002B2DBF7A 作为 IID。这是 IPSFactoryBuffer 的 IID。
  • 这个接口在每个 MIDL 生成的代理 DLL 中都有实现,用于辅助编组和解组,总是返回相同的结构:CStdPSFactoryBuffer。这个结构方便地在偏移 0x10(16 字节)处有一个指向存根 ProxyFileList 的指针。Wine 的源代码在这里定义了它。

代码看起来像这样:

无需 GetProxyDllInfo 即可获取代理 DLL ProxyFileList 的示例代码

如想查看我使用的实际代码,可以在 the-one-wsl-bof仓库的 discovery 文件夹中找到,以及我用来简化这个过程的所有其他工具。你可以在这里查看。

该目录中的其他工具包括:

  • 获取解析的 ProxyFileList 元数据并输出伪 IDL(包含在 .NET 工具中)
  • 将其清理为真正的 IDL 文件
  • 对所有 IDL 文件进行哈希处理,确定哪个 WSL 版本范围需要哪个接口定义
  • 从开源 IDL 文件中覆盖更详细的类型、函数名称和参数名称到我通过上述方法生成的所有文件
  • 通过 MIDL 将这些编译为头文件

构建 BOF

拥有所有头文件后,我从 wslbridge2 的开发者那里获得灵感:他们只是定义每个版本作为不同的接口,然后用启发式方法确定要使用的版本。我找到的指示器是存储在 HKLMSOFTWAREMicrosoftWindowsCurrentVersionLxssMSI 的版本号。

通过注册表发现 WSL 版本

这理论上 应该适用于 GitHub 上所有可用安装程序的 WSL2 版本,但我没有进行广泛测试。它在我的非常旧的版本上工作,以及更新到最新版本后也能工作。如果你在部署的环境中遇到问题,我很希望看到向 BOF 仓库提交的 issue。WSL1 目前不受支持。

你可能现在想知道那个 BOF 看起来有多糟糕。让我直言:非常糟糕。特别是因为某些 C2 agent 无法正确处理 switch 语句,所以代码中充满了 if/else 逻辑。

目前对于 WSL2,我们需要跟踪八个不同的接口版本,所以每个条件语句看起来像这样:

呃!

我不确定是否不会有另外八个版本的接口要跟踪,因为看起来微软团队在这里没有任何向后兼容性的政策。

我现在要管理期望:如果 WSL 的较新版本中有大量破坏性变化,我会很难维护这个项目。不过,我欢迎收到 PRs 和错误消息粘贴,当这个 BOF 在陌生机器上失败时!

还有未来的工作要做,因为这个相同的接口似乎也暴露了 安装WSL 实例的功能,所以你可以在甚至没有现成 WSL 发行版的主机上带来你自己的攻击虚拟机。那会非常强大!

总之,你现在拥有了一个 WSL BOF 来统治它们。你应该能够针对遇到的任何 WSL2 版本进行操作,我建议使用 Poseidon 来与你找到的任何发行版交互。

源代码、编译的 BOF 和 CNA 脚本可以在这里找到:https://github.com/MayerDaniel/the-one-wsl-bof/tree/main/bof。BOF 包含通过注册表枚举 WSL 发行版,然后在找到的任何发行版上执行命令的功能。更多信息请参阅 ReadMe。

特别感谢 Adam、Antero、Lee、James Forshaw(OleView.NET 的开发者)和 Biswapriyo Nath(wslbridge2 的维护者)的帮助、灵感和出色的工具!

祝黑客愉快!


One WSL BOF to Rule Them All

免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:securitainment DANIEL MAYER DANIEL MAYER《一个 WSL BOF 来统治它们:跨版本 COM 接口的逆向工程与利用》

评论:0   参与:  0