Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
vsock_poc — 调查 CVE-2021-26708 背后的漏洞 | Kitploit
工具/GitHubGitHub/jordan9001/vsock_poc
漏洞分析漏洞利用调试器论文与研究学习与教育二进制利用
GitHubjordan9001/vsock_poc

vsock_poc

调查 CVE-2021-26708 背后的漏洞

查看仓库
2825年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

vsock_poc

调查 CVE-2021-26708 背后的漏洞


本仓库包含关于 CVE-2021-26708 的简要分析报告,以及如何将该漏洞转化为释放后使用(Use After Free)写入原语。这里的 PoC 并非完整的利用程序,只是我在调查该漏洞时使用的测试框架。它能够成功访问 kmalloc-64 缓存中释放后的条目,但没有提供任何用于内存整理并在该槽位放置感兴趣对象的相关代码。

这是由 @a13xp0p0v 报告的一个有趣的漏洞。它之所以引起我的注意,是因为补丁非常简单,仅仅在五个地方阻止了在锁外部获取对 vsk->transport 的引用。 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c518adafa39f37858697ac9309c6cf1805581446

下面是从补丁到可用于利用的释放后使用原语的简要分析过程。这份分析应该有助于其他想要探索该漏洞的人。

环境搭建

我下载了 Linux 内核 5.10.13,并手动撤销了上面展示的补丁。有关构建和运行内核的更多信息,可参考以下资料:

https://fedoraproject.org/wiki/Building_a_custom_kernel

我还修改了启动参数,以便使用 kgdb 对内核进行调试。使用之前构建的 vmlinux 文件配合 gdb,我可以获取主内核的所有内核符号,但无法获取任何可加载内核模块的符号。与漏洞相关的代码默认不会被加载,但在使用 PF_VSOCK 协议族时会被加载到内核中(取决于你的内核构建方式)。

为了在 kgdb 中获取已加载模块的符号,我确保至少使用了一次 vsock 套接字,然后使用 sudo cat /proc/modules | grep vsock 获取相关模块的基址。在 gdb 中,我会执行类似 (gdb) add-symbol-file ./net/vmw_vsock/vsock.ko 0xffffffffc0567000 的命令,让 gdb 知道该 .ko 文件的符号在内存中的位置。vsock.ko 和 vmw_vsock_virtio_transport_common.ko 是最相关的两个模块。

寻找原语

从补丁反向分析是很有趣的,因为与大量的漏洞挖掘不同,你确切地知道正在检查的位置是正确的。在这个案例中,我们从补丁中得知,在获取 sock_lock 之前,对传输层的引用已经被保存。我们可以合理预期漏洞是由于传输层发生变化,但旧的引用却被使用了。

在这种情况下,我们希望传输层本身是一个动态分配的对象,可以在获取引用和持有锁之间被释放并替换为其他对象。不幸的是,当我们追踪由其他模块实现的相关传输层的生命周期时,它们似乎都位于全局内存中。因此,我们需要深入一层,寻找在作用域之外使用的条目。

释放部分 1

在 af_vsock.c 中,我们可以找到两个修改 vsk->transport 的地方:vsock_assign_transport 和 vsock_deassign_transport。在 vsock_assign_transport 中,我们可以看到,如果存在不同的现有传输层,那么在设置新传输层之前会调用 vsock_deassign_transport。 如果我们查看 此处 对 vsk->transport->destruct(vsk) 调用的可能性,会发现回环和 virtio 传输层都会在此处对 vsk->trans 参数执行 kfree。找到了!如果我们能找到一条路径,使得 (1) 对该调用的调用能够与 (2) 使用来自销毁前传输层引用的易受攻击函数竞争,从而访问 vsk->trans,那么我们就找到了原语。

寻找通往 vsock_deassign_transport 的路径,我们发现它从 vsock_sk_destruct 或 vsock_assign_transport 中被调用。vsock_sk_destruct 被设置为 sock->destruct 函数,因此对 __sys_close 或其他沿着销毁路径的可调用函数(如 sock_put、sock_close 或 vsock_release)的调用最终可能到达这里。

通往 vsock_assign_transport 的最相关路径是通过 vsock_stream_connect,但这要求套接字处于特定的几个状态,并且只有当传输层发生变化时才会调用 vsock_deassign_transport。如果新的传输层不是 NULL,它会替换 vsk->trans 参数。

使用部分

在深入确定哪条释放路径适合我们之前,我们想先确认是否存在一个有效的路径,该路径使用 vsk->trans 成员,但引用的是一个已销毁的无效传输层。我们可以系统地检查每个可能使用无效引用访问传输层的位置。追踪这些漏洞点,我们可以找到使用 vsk->trans 的地方。最好的路径似乎是通过 vsock_stream_setsockopt,在此处,当 transport->notify_buffer_size 在此处 写入 vsk->trans 内部的偏移量时(对于回环和 virtio 传输层)。如果 trans 在使用时已经被释放,我们就能得到一个不错的写入原语:将一个 u32 写入 kmalloc-64 分配中偏移量为 0x28 的位置。

竞争条件

将 vsock_stream_setsockopt 用作原语依赖于一个竞争条件:在获取传输层引用和获取 sock_lock 之间。这是一个很小的窗口,并且中间有很多指令。因此,我们可以利用 Linux 中一个很棒的特性——userfaultfd 来增加我们成功的机会。这种机制允许我们在用户空间随意处理页错误。 参见 https://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html 和 https://man7.org/linux/man-pages/man2/userfaultfd.2.html。

通过这个机制,我们可以让一个线程(称为门控线程)获取 sock_lock,然后访问一些用户内存并引发页错误。我们可以让该线程保持暂停状态(仍然持有锁),时长不限。其他试图获取该锁的线程将一直等待,直到我们释放门控线程并释放锁。我们可以安排执行销毁操作的线程和使用无效引用的线程同时等待 sock_lock,这样就能大大提高赢得竞争的机会。如果执行销毁的线程下一个获取到锁,那么我们的 setsockopt 调用将在之后完成。它会在 vsk->trans 指针被释放(并替换)后使用该指针。

如果 setsockopt 调用先获取到锁,则我们输掉竞争,但可以安全地重试整个过程。

释放部分 2

在构建这个机制时,我花了一些时间走上了错误的道路。我尝试通过 close 和超时来触发 vsock_deassign_transport 的调用,但遇到了许多引用计数检查,导致实际销毁被延迟,最终为时已晚。

顺便提一句,调试这些路径可能很困难;可以想象,在系统调用 close 上设置断点会频繁触发。即使你使用条件断点只在正确的线程中停止,机器的速度也会慢得像蜗牛。一个有趣的解决方法是使用带有 tracepoint 的 ebpf,只有在条件正确时才调用 bpf_trace_printk。然后可以在 kgdb 中对 bpf_trace_printk 设置断点,这样就能接近正确的位置。由于已经处于断点处理程序中,这不能与 kprobes 一起使用。我认为在 ebpf 中添加一个 bpf_trace_kgdb_break 调用会是对内核的一个不错的补充。

当我最终转向研究 vsock_assign_transport 路径时,一切很快变得清晰。为了满足要求,我们首先在没有监听服务器的情况下连接到 VM_ADDR_CID_LOCAL。这会给我们分配回环传输层,但当我们的连接超时或失败时,状态将返回到 SS_UNCONNECTED。这使得我们可以再次连接到大于 VM_ADDR_CID_HOST 的地址,这将导致我们的传输层发生变化,销毁现有的传输层并触发释放。重要的是,当我们这样做时,实际上没有注册任何 transport_g2h 或 transport_h2g,因此新的传输层将为 NULL,而我们的释放引用将保留在 vsk->trans 中。

利用

有了所有这些准备,我们就得到了一个可靠的释放后使用漏洞,可用于权限提升。

本仓库仅涉及我们如何实现最初的释放后使用。但现在我们有了一个原语,可以在 kmalloc-64 缓存中先前分配 virtio_vsock_sock 的位置写入一个值。我决定在此处停止分析,因为,难道所有事情都要我来做吗?

下载工具