首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创] 第十题:卯时·曦光初现
发表于: 2026-8-24 11:27
326
[原创] 第十题:卯时·曦光初现
F1ux_Team
1
2026-8-24 11:27
326
保护与程序结构 目标是 64 位 PIE 程序,开启了 Full RELRO、NX 和栈保护,运行环境为 Ubuntu glibc 2.27。程序没有直接的栈溢出点,攻击面位于自定义表达式解释器及其对象序列化逻辑。 主循环每次读取一行不超过 255 字节的输入,将其解析成 AST,调用第一个虚函数执行,最后调用析构虚函数释放 AST。 解释器支持以下核心语法: ```text $0=123 标量赋值 $0=[1,2,3] 数组赋值 $1=$0[2] 字段读取 $0[2]=$1 字段写入 $0+$1 原地算术或位运算 ``` 变量编号在执行时限制为 `0..255`。二元运算包括加、减、乘、除、取模、异或、或、与。 初始化函数申请一个 `0x10000` 字节的连续区域作为对象 arena,并从 `/dev/urandom` 读取 8 字节随机数作为编码密钥。数组序列化格式如下: ```text header = key ^ (0xBEEF000000000000 | element_count) data = 递归序列化后的元素 ``` 变量中的数组引用使用另一种标签: ```text 0x1337000000000000 | arena_pointer_low48 ``` 普通数字会被限制在低 48 位。数组字段读取时,程序用密钥异或内存中的 qword;若结果的高 16 位是 `0xBEEF`,该 qword 就被当作嵌套数组头处理。 ## 漏洞成因 字段写入只拒绝高 16 位为 `0x1337` 的数组引用,却允许写入任意其他 64 位标量。二元运算又能从低 48 位常量构造完整的 64 位值,例如: ```text 0xBEEF0000 * 0x100000000 = 0xBEEF000000000000 ``` 再加上一个可控长度,便能得到伪造数组头: ```text 0xBEEF000000004000 ``` 字段写入会自动执行 `key ^ value`,因此将这个数写进真实数组的首元素后,字段读取器解码出的内容恰好是合法的 `0xBEEF` 数组头。递归遍历函数只相信头部携带的元素数量,没有再次确认地址仍处于 arena 内。后续索引因此可以越过 `0x10000` 字节边界,读写 arena 后方的 glibc 堆块。 构造过程为: ```text $0=[0] $1=3203334144 $2=4294967296 $1*$2 $3=16384 $1+$3 $0[0]=$1 ``` 此时 `$0[0]` 已成为长度 `0x4000` 的伪造数组。 ## 获取编码密钥与 libc 地址 arena 尚未使用的区域全部为零。读取伪造数组中的第一个越界零 qword 时,解释器返回: ```text 0 ^ key = key ``` 这样即可把随机密钥保存到普通变量。 在精确匹配的 glibc 2.27 和同代 libstdc++ 环境中,arena 后方偏移 `0x10078` 处稳定残留 `_IO_2_1_stderr_` 指针。越界读取获得的是: ```text raw_stderr_pointer ^ key ``` 再与刚得到的密钥异或,即可恢复真实指针。减去 `_IO_2_1_stderr_` 的符号偏移 `0x3EC680`,解释器变量中便保存了 libc 基址。 程序没有打印数值的功能,因此需要建立一个确定性的单比特输出通道。二元运算会拒绝高 16 位为 `0x1337` 的操作数,并打印 `runtime error!`。对于 libc 基址的第 `i` 位,计算: ```text x = libc_base & (1 << i) x = x * (1 << (48 - i)) x = x | (0x1336 << 48) ``` 原位为 1 时,`x` 的高 16 位变成 `0x1337`,下一次二元运算会报错;原位为 0 时,高 16 位保持 `0x1336`,运算正常完成。逐位处理第 12 至 47 位即可精确恢复当前连接的 libc 基址。低 12 位由页对齐确定,整个过程不依赖地址猜测。 ## tcache 投毒 解释器初始化和短命 AST 对象在 arena 后方形成三个稳定的 `0x20` tcache 块。它们的用户区相对 arena 位于: ```text 0x10270 0x10290 0x102B0 ``` 不同 AST 会交换链表前两项,Exp 按每条语句的实际分配和释放顺序同步追踪当前链表。 最终目标为: ```text target = __free_hook - 8 ``` 字段写入会附加一次密钥异或,所以先在解释器内部计算 `target ^ key`,再把它写入空闲块的 `next` 字段,内存中的原始值就会是 `target`。 带两个索引的字段写入会为索引向量依次申请两个 `0x20` 块。第一次扩容得到的块会在 AST 执行前释放,成为当时的 tcache 头;第二个块在 AST 析构时释放到链首。对第一个块执行越界写后,链表变成: ```text 第二个正常块 -> 第一个正常块 -> (__free_hook - 8) ``` ## 覆盖 `__free_hook` 最后发送一条长度严格为 23 字节的输入: ```text $0=0<system 的十五位十进制地址>;sh# ``` 开头额外的 `0` 使数字部分长度达到 16 字节,同时不改变 `strtoul` 得到的数值。该输入依次产生三个 `0x20` 分配: 1. 主循环保存整行输入的 `std::string` 缓冲区; 2. 数字解析时创建的 16 字节临时字符串; 3. 大小为 `0x10` 的 Number 对象。 前两个分配消耗正常块,第三个分配返回 `__free_hook - 8`。Number 构造函数在对象偏移 `0` 写入合法虚表,在偏移 `8` 写入数值,于是 `system` 地址正好覆盖 `__free_hook`。 数字后的分号使整条表达式解析失败,Number 随即被释放并触发 `system`。之后发送命令字符串即可通过后续释放执行命令。远程读取结果为: ```text flag{c42a0365-6ddb-456e-b6e7-9f75462e43f7}
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
上传的附件:
exp.py
(5.31kb,7次下载)
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
F1ux_Team
1
10
发帖
0
回帖
70
RANK
关注
私信
他的文章
[原创] 第十题:卯时·曦光初现
326
[原创] 第九题:丑寅同墟·星海抉择 Writeup
18
[原创]第五题 申时·忆海倒带 Writeup
834
[原创]第六题:酉时·书院迷局 Writeup
587
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
风子
huangyalei
pzhxbz
孤独灬剑客
mb_rplbjxbp
GuestLiang
mb_dxmnasas
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部