论坛首页 CTF竞赛交流区 阅读主题

[原创] KCTF2025 - 第八题 暗云涌动 题解 (vm题,任意读写 侧信道leak)

450 浏览 0 回复
#1 楼主 2026-06-01 21:08:55
VM 题,给了任意读写,但读写使用的指针必须是有 SFI mask 的指针,但是 mask 可以伪造,所以等于直接给了任意读写
但 VM 各段和寄存器都被完美清零,且没有直接的越界可以读取初始指针
保护除了 Canary 全开,容器 ubuntu22.04 (但是看雪远端应该并不是 ubuntu 作为容器的 host,所以 ASLR
有细微差异)
然而,SFI 的实现提供了侧信道可以安全地探测一个地址是不是属于 vm 的堆或者栈段,而 vm 的栈是通过 zalloc 分配的,在程序的堆上。
因此,解题思路是:看到 opcode 知 vm 题,看几个 push pop add 之类的简单分支还原一下结构体:然后再苦力一下把 25 种 opcode 都逆出来:
急需一个 MCP 来自动干这一步.jpgVM 的所有运算都是 64 位的,但是只支持 32 位的立即数。逆完 VM 本体之后很容易发现 load 和 store 给了任意读写,但要求指针最高 bit 是 1。
而 pushaddr(2) 和各种运算会检测数据是不是一个合法的程序指针(堆或者栈),不是的话就返回一个 vm 里堆的指针并把高位设成 1。 然而,高位的 1 我们可以直接通过普通的乘法或者加法运算得到,伪造一个符合 SFI 的指针并不是问题
这样来看,我们已经有任意读写了,唯一问题是怎么泄露一个有用的地址出来。
来看 vm 内存分配:

分配的内存都很干净 上面没有任何残留,并且堆地址是固定值,而指令地址和 libc 或者 ld 对齐。VM 的所有数组访问都被加上了合适的范围检查,保证不会有越界(上溢或者下溢都不会),也就不存在栈溢出一下读到临近的堆指针的可能了
找来找去看到逻辑运算都加了的 SFI 实现其实是有问题的:

它本应只检查堆指针(堆地址已知)但是却同时检查了传入的指针是否属于 vm 栈的范围,是的话就会把最高 bit 设为 1。
借助这个侧信道我们就能泄露 vm 的栈 程序的堆地址了。
不过程序的跳转逻辑似乎只支持向后跳和跳到0,但这并不是问题,因为寄存器不会清空,我们可以:这个循环我本机测试 10s 内可以运行 0x10000000 轮,加上 ASLR 的对齐,10s内可以探测 0x100_00000000 个地址,而 ubuntu22 默认的随机化范围大概是 0x5500_00000000 ~ 0x6f00_00000000 (?)
也就是要爆破 55 - 6f 一共 26种可能,那似乎多试几次就跑出来了(吧?) 那么有了堆地址之后,就可以用上面说的思路一路拿到 ld ,栈和 libc 地址想打什么打什么了
这里比较坏的是出题人给了 libc 但是没给 ld(好吧都不给也不是不能做侧信道还是能 dump 但是好无聊啊)
那先简单解决一下,猜测远程差异不打,拉一个同版本的镜像 ubuntu:jammy-20250404
虽然但是,还是希望出题人能不吝啬要么给全附件要么给 Dockerfile拿到偏移之后本地写脚本,不到 10 次出了,很开心的打远程
然后 100 轮都没出,想了一下可能是远程随机化熵更高或者范围完全不同,于是把范围改大再试一轮:
brup = random.randint(0x40, 0x7f) << 8
(因为 ASLR 本身是跟着内核走的,docker 的虚拟化并不虚拟化 ASLR 的生成,而是一定程度上由 host 决定的)范围改了之后很快就出了:
这里笔者没有继续测看雪 host 的 ASLR 范围到底是多少,后续可以再观察一下(或者看看其他师傅们题解有没有提到),方便之后玩看雪的 pwn 时候打的更丝滑些(完整脚本:好耶,是flag:
struct vm {

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288303.htm

暂无回复,快来抢沙发吧!

请登录后参与讨论

立即登录 注册账号