2026软件安全赛半决赛PWN题目“Robo_admin”分析与利用教学
1. 题目概览
本题是一个CTF PWN(二进制利用)挑战,名为“Robo_admin”。选手需要利用一个管理员功能中的安全漏洞,最终目标是获取系统权限。题目主要涉及两种利用场景:“Break”(攻破)和“Fix”(修复),其中“Break”是利用现有漏洞获取权限的标准解法,而“Fix”是题目要求的一种变体,侧重于绕过额外的安全限制。
2. 漏洞原理与利用过程详解 (Break)
2.1 初始权限获取:格式化字符串漏洞
程序存在一个关键的 set_notice 函数,用于更新一个公告。函数内部逻辑如下:
- 读取用户输入到缓冲区
s中,最大长度为0x200字节。 - 检查输入中是否包含非法字符
%或$。如果包含,则输出错误信息并退出。 - 如果通过非法字符检查,输入会被送入一个名为
str的函数进行处理。 - 处理后的内容被复制到全局缓冲区
s__1中,并设置一个更新成功标志。
漏洞点:尽管程序在原始输入中过滤了 % 和 $,但 str 函数实际上是一个转义/解码函数。经过逆向分析,该函数的作用是将形如 \x25 的ASCII码转义序列转换成对应的字符(如 \x25 -> %)。这意味着攻击者可以通过输入 \x25 来绕过对 % 的检查,从而在解码后的字符串中嵌入 % 符号,触发格式化字符串漏洞。
利用步骤:
- 通过输入
\x25和\x24等转义序列,在解码后的公告字符串中构造一个包含%p等格式化占位符的payload。 - 程序在输出这个公告时,会执行格式化字符串漏洞,泄漏栈上的敏感数据。
- 在提供的Write-up(WP)中,利用此漏洞泄漏出了两个关键值:
passwd1和passwd2。这两个值与一个固定的token一起,用于通过管理员身份验证,进入admin_function。
2.2 堆利用:Off-by-one 与 House of Some
成功进入 admin_function 后,题目提供了一个堆管理器,具有常见的添加、编辑、查询、列出、删除任务(堆块)的功能。
漏洞点:edit 函数存在一个Off-by-one溢出漏洞。在编辑堆块描述时,可以写入比堆块描述长度多一个字节的数据,导致覆盖下一个堆块的 size 字段的最低字节。
利用链构造:
- 堆布局与塑形:
- 创建多个不同大小的堆块(索引0-6)。
- 利用
edit函数的Off-by-one漏洞,修改特定堆块的size字段,例如将0x111改为0x161,目的是在后续操作中造成堆块重叠(overlapping chunks)。
- 触发Unlink并泄露地址:
- 通过精心设计的释放(
delete)和重新申请(add)操作,触发unlink操作,从而在合并或分割堆块的过程中,让某个堆块的内容区域包含一个main_arena中的地址(即libc地址)。 - 通过
query功能读取这个堆块的内容,即可计算出libc的基地址。 - 类似的技巧可以用于泄露堆(
heap)的起始地址。
- 通过精心设计的释放(
- House of Some 利用:
- 这是本题利用链的核心。
House of Some是一种针对高版本glibc(2.35及以上)的堆利用技术,通过伪造_IO_2_1_stderr或相关_IO_FILE结构体,最终目的是劫持程序控制流。 - 利用堆块重叠,在一个可控的堆块(例如索引5)中写入一段
shellcode(用于后续执行)。 - 在另一个堆块中构造一个伪造的
_IO_FILE结构体(HouseOfSome模板),并利用堆漏洞修改tcache或fastbin的fd指针,使其指向一个关键位置——_IO_list_all符号。这是一个全局指针,指向_IO_2_1_stderr链表的头部。 - 当程序下一次调用类似
exit的函数,或触发abort时,会遍历_IO_list_all链表并调用其中_IO_FILE结构体的虚函数。通过将_IO_list_all指向我们伪造的_IO_FILE结构体,可以控制程序执行流。
- 这是本题利用链的核心。
- 最终利用:
- 在提供的WP中,利用
House of Some将控制流导向一个ROP链。 - ROP链调用
mprotect将存放shellcode的堆内存区域设置为可执行(PROT_EXEC)。 - 最后跳转到
shellcode执行。 shellcode的功能是:调用openat2系统调用打开./flag文件,然后通过readv和writev系统调用将文件内容读取并输出。
- 在提供的WP中,利用
3. 修复绕过分析 (Fix)
“Fix”部分要求参赛者修复漏洞,但WP作者展示的是另一种理解:题目本身可能要求攻击者去绕过新增的过滤。
新增的安全检查:
在 set_notice 函数中,原始的检查是在解码前检查输入是否包含 % 和 $。而“Fix”可能在代码的 .eh_frame 节(通常是异常处理或编译器生成的数据)中增加了一段补丁代码。这段补丁代码的逻辑是:
- 在调用
str解码函数之后,再次检查解码后的缓冲区([rbp-310h])中是否包含%(ASCII 0x25)或$(ASCII 0x24)。 - 如果包含,则输出错误信息
[X] decoded input contains illegal chars并退出。
绕过方法:
尽管补丁增加了解码后的检查,但最初的利用方法依然有效。因为攻击者输入的是 \x25 和 \x24 这样的转义序列。str 解码函数会正确地将它们转换为 % 和 $ 字符。所以,解码后的缓冲区确实包含了非法字符。但是,关键在于触发漏洞的时机。
格式化字符串漏洞的触发,发生在程序输出公告 s__1 的时候。而解码后的检查,发生在 str 函数执行之后、数据复制到 s__1之前。如果检查失败,程序会直接跳到错误处理并返回,不会执行 memcpy(s__1, src, ...)。因此,s__1 中的内容不会被更新,也就不会触发漏洞。
然而,WP作者指出,他们在实际挑战中“一开始没审题”,以为“Fix”是要去修复 off-by-one 漏洞。后来重新读题才发现,题目的真正意图是让选手绕过这个新增的过滤。WP中并未给出绕过这个“解码后检查”的具体方法,但留下了一个“嘻嘻收工~”的评论,暗示可能发现了检查逻辑的缺陷或另一种无需更新 s__1 即可触发漏洞的路径(例如,检查的缓冲区与最终输出的缓冲区可能不是同一个?文中未明确说明)。这通常是CTF比赛中需要选手自行探索的关键点。
4. 关键知识点总结
- 绕过字符过滤:通过输入ASCII码转义序列(如
\x25代表%)来绕过对特定字符的字符串检查,是Web安全和二进制安全中常见的绕过技巧。 - 格式化字符串漏洞:允许攻击者读取栈内存(泄露地址)或向任意地址写入数据。本题中用于泄露密码,完成身份验证。
- 堆利用基础:
- Off-by-one:单字节溢出,可用于精细地修改堆块元数据,是构造堆块重叠的常见入口点。
- 堆布局与塑形:通过有顺序地申请和释放不同大小的堆块,来控制堆内存的布局,为后续利用创造条件。
- 地址泄露:利用堆管理器的行为(如
unlink)将libc或堆地址放入用户数据区,再通过输出功能读取。
- 高级堆利用技术 - House of Some:适用于现代glibc(>=2.35),利用了
_IO_FILE结构体在程序异常退出时的处理流程。其核心是伪造一个_IO_FILE结构体,并利用堆漏洞劫持_IO_list_all指针,从而在调用_IO_overflow等虚函数时获取控制流。这是一种结合了堆漏洞和IO流利用的复杂技术。 - 利用链构造:真实的漏洞利用往往是一个复杂的多阶段链条。本题的链条为:格式化字符串泄露 -> 身份验证 -> 堆溢出制造重叠 -> 泄露libc/heap地址 -> 布置shellcode -> House of Some劫持
_IO_list_all-> ROP链调整内存权限 -> 执行shellcode读取flag。 - Shellcode编写:在限制环境下(如缺少
open系统调用),使用openat2等替代系统调用来打开文件,并使用readv/writev进行读写。
5. 教学启示
- 代码审计要全面:不能只看表面过滤。
set_notice函数在解码前和解码后都可能存在检查点,需要追踪整个数据处理流程。 - 理解漏洞本质:格式化字符串漏洞的本质是用户控制了格式字符串参数。无论过滤发生在解码前还是解码后,只要最终传递给
printf类函数的字符串是用户可控的并包含%,就可能触发。 - 现代缓解机制的绕过:随着ASLR、NX、Stack Canaries、FULL RELRO以及glibc引入
tcache等机制的普及,传统的栈溢出和简单堆利用变得困难。像House of Some这类技术,展示了攻击者如何利用程序中更复杂的组件(如IO流)在防护齐全的环境下实现利用。 - 工具与脚本的重要性:WP中使用了
pwntools、LibcSearcher、自定义的HouseOfSome类等工具,自动化了地址计算、payload生成、交互等过程。在复杂的漏洞利用中,编写可靠的漏洞利用脚本(Exp)是必不可少的技能。
通过深入分析本题,可以系统性地学习从简单的字符过滤绕过,到复杂的现代堆利用技术组合的完整攻击面。