首页
社区
课程
招聘
[原创]ISCC 2026 test题目 [house of apple 2] 利用方法
发表于: 2026-6-2 16:20 2748

[原创]ISCC 2026 test题目 [house of apple 2] 利用方法

2026-6-2 16:20
2748

本次校赛中,这道堆利用题目涉及了 Glibc 2.31 下的类型混淆与 House of Apple 2 利用链,整体构造较为巧妙,非常考验对底层内存状态的追踪能力,值得深入复盘分析。相比之下,另外几道格式化字符串题目偏向基础,因此本篇着重记录此题的完整调试与 Exploit 构造过程

本文章偏基础,可能废话有点多,希望各位师傅见谅

在深入代码之前,先简述本题的几处核心漏洞:

提示:目标环境虽然是 Glibc 2.31,但依然能够修改 __malloc_hook 等机制(实际上直到 2.34 版本 hook 才被彻底移除),不过本题通过劫持 _IO_list_all 走 House of Apple 2 依然是非常通用的高版本打法

题目资源github仓库地址 | 个人博客附件下载 | 文章底部附件下载


程序分为教师学生两个操作域,通过 dword_5010 存放身份标识符,公共函数 sub_1E62() 用于切换身份

教师专属功能:

创建学生 ---> sub_1424()

给所有学生打分 ---> sub_1538()

写评语 ---> sub_1691()

删除学生 ---> sub_1875()

创建0x300大小堆块(malloc) ---> sub_1AC3()

学生专属功能:(注:sub_1A34 为无用输出函数)

为了方便后续理解,这里提前给出管理 Chunk学生 Chunk 的内存布局

整个程序除了 sub_1AC3() 用了 malloc 其他所有地方都用了 calloc

创建一个管理chunk(0x20),一个学生chunk(0x18),创建时要求输入一个 0-9 的数字,放入学生chunk0-4 字节处,管理chunk 存入全局数组,下标为当前学生数量

首先 buf[0] 会等于 0-127 中的一个数字,然后score ---> buf[0] % (q_num * 10) ,分数会写入 学生chunk4-8字节处,如果当前学生lazy标识为1,则该学生的分数会等于当前值 - 10

学生chunk8-16 字节处写入 comment_chunk ,16-24字节处写入comment_chunk_size,大小最大1023` 字节

按照先后顺序依次释放 comment_chunk学生chunk管理chunk,最后把全局数字对应下标清零,学生数量 - 1

这个没啥好讲的

一个小后门,假如当前学生的分数大于 89 就会输出这个学生对应的 学生chunk 地址,并且让一个我们传入的地址的值 +1,后面是正常输出学生评语,当我们触发后门时,它会给 管理chunk0x1C 写入 1 后面该学生不会在触发输出评语函数

注意读取地址时的 sub_131A() 函数,它会把我们输入的最后一个字符改为 0,假设我们输入 123456\n ---> 12345\0\n ,我们需要多输入一个字符跳过这个坑

假如当前学生的 lazy 标识不为1 就会往 管理chunk16-24 字节写入 mode_chunk ,假如标识为1往 管理chunk16 字节写入一个整数

整体程序大量使用了 calloc 分配内存,排除了常规的直接利用未初始化内存的数据残留

输出学生评语 功能中存在一个后门,如果当前学生的分数大于 89,程序会泄露该学生对应的 管理 Chunk 地址,并允许向我们传入的任意地址执行字节加法 ---> ++*v1

注意 读取目标地址时的 sub_131A() 函数存在问题,它会将输入的倒数第二个字符(即 \n 前的字符)截断为 \0。例如输入 123456\n 会变成 12345\0\n。因此在构造 Payload 时,需要在末尾多填充一个无用字符以保护有效地址。

mode 函数中:

这里存在严重的类型混淆。如果我们在 lazy != 1 时创建了 mode_chunk(例如地址为 0x...2e367340),随后将 lazy 翻转为 1,就可以通过输入 0-100 覆盖该指针的最低字节(例如将 40 改为其他值)。配合 lazy == 0 时对 mode_chunk 的正常读写,这就形成了一个局限的任意地址写漏洞。

回看前面给出的管理 Chunk学生 Chunk 的内存布局,假如堆布局完成的足够好,完全可以利用 mode_chunk 修改 comment_chunk_addr 将这个局限的任意地址写 变为 任意地址写

由于程序限制了 comment_chunk 的最大大小为 1023 字节(加上 Chunk 头最大为 0x410),在常规情况下(Tcache 未满)这些被释放的块会进入 Tcache Bin,无法直接暴露出 main_arena 来泄露 libc 基址

结合发现的漏洞点,可以构造如下利用链

那么怎么让分数大于89呢?正常来说是做不到的,因为 v2 = buf[0] % (10 * **(_DWORD **)qword_5080[i]) 翻译一下 v2 = buf[0] % (10 * q_num 我们传入的 q_num 最大为 9 ,也就是说 v2 = 0~127 % 0-90 这个最大的值是 89 ,无论如何我们都不可能大于 89

我们需要借用 lazy 标识 来让分数 -= 10 ,让分数为负数,负数转换成正数肯定比 89 大,那么我们在传入 q_num 就要是 1,这样 v2 = (0~9) - 10 < 0,就能完成了

我们先创建一个学生(stu_0),并给他写上评语,大小为0x328,这就是我们后面需要修改的size的chunk

然后创建第二个学生(stu_1),把 mode_chunk 创建了,这样刚好我们就可以覆盖到 stu_1comment_chunk

接下来创建 stu_1conmment_chunk 我们在这个部分就需要伪造后续的chunk,来绕过释放 stu_0comment_chunk 时候的检查

注意是 0x49 不是 0x48,多偏移一个字节才能修改 0x3,我们可以看一下堆内存,看一下前面部署的能不能通过检查

接下来释放掉这个被修改大小的 chunk ,让他进入 unsorted bin

我们接着把 stu_1 的 学生chunk 中的 comment_chunk_addr 修改成 stu_0comment_chunk,就可以泄露libc基址了

原理:当程序退出时,会把未写入的缓冲区数据写入,我们伪造 _IO_write_ptr > _IO_write_base 表示有数据需要写入,_IO_write_base = 0,程序会认为我们没有缓冲区,但是我们要写入数据,所以他会创建一个缓冲区给我们,就是函数__doallocate,偏移为0x68,所以我们在_IO_wfile_jumps偏移0x68处写入system地址,在结构体头部写入sh即可完成攻击

题目没有符号表,我强行让gdb输出的,只能看最基础的,不过也够了 :( ,可以看出我们的布局十分完美


[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 2026-6-2 16:43 被xnqjns编辑 ,原因:
上传的附件:
收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回