SJTU26校赛出题记录
此次出题过于执着于防AI以及AI agent工具使用,反而忽略了碳基古法解题的做题体验,线上赛看大家问卷反馈也是发现此次出题效果很差,在此向大家谢罪 XD。
【Stack UAF】NekoGarden
题目预期难度:简单
题目评价:该题目是一道防 agent 题目,虽然是在第三天凌晨就被 b0ar 大佬拿下一血(结合该选手其他题目解题情况合理怀疑是编排了很强力的 agent 自动解题。但是整场比赛七天下来在最后两天放出提示后才两解(另一解看wp应该是真人手打的),基本上可以认为是防住了 AI 大人。但是没想到的是把真人也防住了,导致最后解数太低(该题目预期难度简单,考验手动调试),实在惭愧。
起因是看到一个漏洞:CVE-2024-23354
该漏洞是内核中 kgsl gpumem 逻辑中,op 结构体 op->data 成员被赋值为栈上变量地址,可以通过提前kill进程导致栈对应函数提前释放。
因此考虑在用户态对此种情况进行复现,通过 pthread_t 的生命周期管理导致子线程函数提前退出然后触发栈上变量UAF。
这也就是这道题目的由来,题目的漏洞点在于 master_thread 变量为全局变量,每一个 neko spawn master_thread 线程会竞争使用该线程变量,从而导致在 pthread_join 时如果重复 join 同一 master_thread 会导致 join 失败提前返回。
逆向 master_thread 也会发现其会非常诡异的进行 sleep,也暗示了这道题目需要用到 race。
Race PoC 很简单,连接 socket 选择一个 neko1,输入完 Length 后该线程会创建 master_thread 变量;然后再开启一个 socket,选择另一个 neko2,输入一整套流程直到 neko2 返回并 pthread_join(),此时 master_thread 已经被 neko2 覆盖且 join。此时再向 neko1 发送数据令其 join 会直接失败返回,neko1 函数返回,但是如果 master_thread sleep 时间较长,会在 neko1 函数返回后尝试修改 neko1 函数栈上的变量。
然后就是要知道会修改哪一部分变量。逆向分析可以发现 3 个 neko的唯一区别就是数据 buf 长度不同,这里其实就是暗示能够通过多线程之间的栈上布局不同来进行利用(毕竟偏移一样改来改去也没有多大意义 XD)。此处经过笔者的反复调试,将 neko kitty 和 neko luna 的栈布局改为了可导致栈溢出的状态。
首先要说明 neko 和其 spawn 的 master 到底做了什么:以 neko kitty 为例,其分配了 0x100 大小的 buf,同时定义了栈上变量 key_buf, res_buf 用于 master 后续写入。用户输入长度大小后就会创建 master_thread(这么做纯粹是为了好利用,非常的不优雅),master_thread 会等待 neko 的mutex lock,neko会循环等待用户输入,如果用户输入前四字节为 Meow,则通过 check 跳出循环,释放锁。master_thread 继续执行,而 neko_thread 会等待 master_thread 返回。master_thread 会对输入内容进行简单的异或和移位操作,将结果存入 key_buf 和 res_buf,然后退出。neko_thread 继续执行也返回退出。
因此就显而易见我们需要关注 master_thread 写入的 key_buf 和 res_buf 在多线程的情况下能写入到哪些栈上变量。
这里可能有一个隐藏假设,就是栈的内存布局以及其基本用处,在进行函数调用时栈会从高地址上低地址生长,函数返回时栈会进行 pop 将之前函数的栈内存pop掉(可能和实际实现有些许差别,但只需要知道此原理即可),那么在一个 socket 连接中,对于 neko 的 menu 菜单选项,每一次选择调用的函数其栈的栈顶地址其实都是一致的。
下面是 kitty_buf 的栈上变量布局,key_buf 与 res_buf 在 rbp-0x120 - rbp-0x110 的范围
luna 的栈上变量布局如下,key_buf 和 res_buf 在 rbp-0x1d0 - rbp-0x1c0 范围
可以发现 luna 的 key_buf 和 res_buf 可以修改到 kitty 的 nbytes 变量。其实就是可以用 res_buf 修改 nbytes 长度,该长度即 neko 循环读取用户输入的长度变量。至此,栈溢出漏洞原语已经浮现。
到这里可以构造出栈溢出的 PoC。只需要先像上面先做一个 neko1(luna) 的提前退出,然后其master_thread 尽量 sleep 久一点,neko1(luna) 退出后立即选择 kitty,并输入一个长度变量卡进输入数据的循环等待中,然后只需要等待上一个 master_thread 返回即可修改其长度变量为一个极大值。然后随便输入一个不合法的再触发新的读取就可以实现栈溢出。
然后就是该如何泄漏 libc。这里需要注意 master_thread 确认长度是 strlen() 因此可以通过栈溢出一直溢出到canary,从而让 canary 参与 key 的计算,而 key 的计算是可逆并且会由 neko 打印出来。因此可以通过 key 来进行泄漏栈上的 canary,libc等内容。
然后就是再触发一次栈溢出进行 ROP 即可。
暴论:
总结下来,该题目其实整体上可能利用过程比较复杂,但除了 race 外其他地方都是 pwn 中常见的点;同时笔者尝试将其扔给 codex gpt5.4 发现其很快就找出了 race 导致栈变量UAF的漏洞。因此当时出题预期难度被定义为简单。而防 AI 的部分体现在几点猜测,1. AI 不擅长对多线程竞争的调试和脚本运行交互;2. AI 经常不能很好的判断栈上变量的偏移以及长度;3. AI 可能不太擅长如何泄漏libc(?)
该题目的预期解法是使用 AI 辅助逆向分析找出漏洞点甚至给出利用链;但是发现 AI 无法完整独立走通漏洞利用,只能手动进行多线程调试以及后续的exp编写。(没错,题目多线程调试才是本题的唯一难点(x))
赛后看下来,榜前大把的 AI 梭题,但是有几道题目还是比较坚挺,例如 invisible notes, nekogarden, rev is cooked等。其他题目不予评价,nekogarden我觉得是一种反 AI 自动化解题的失败尝试,因为确实本着以简单题目防AI的原则,但是大部分真人还是做不出来。而且该题目一血大概率还是AI。二解是真人但是晚了大概3天,且放了提示,评价是并没有真的找到真人相较于AI的优势点所在,鉴定为拉完了。
RPGMaster
如果说 NekoGarden 是为了放 AI 梭题出的,那么 RPGMaster 就是为了让大家拿 AI 自动化梭题出的。目的是让大家合理的使用 Agent。
本人要在这里声明,本人并不反对 AI 参与 CTF 解题,NekoGarden 的防 AI 也只是防止 AI 全自动梭题。
该题目解数较多,且看了眼 exp 大部分都是 ai 自动化做的,符合预期。这里只简单介绍下原理
漏洞点是在 remove_item 时,在对 map 对象 find 后没有判断其是否为 .end() 直接对 .second() 元素进行了减一操作。这里导致会出现 UB 行为。
灵感来源参考
https://blog.csdn.net/liuyuan185442111/article/details/135431267
具体利用细节就不多讲了,AI大人一切解法都是预期解🙏。
【TODO】XXX
此题目等待线下赛结束后再简单介绍。