-
-
[原创]ISCC 2026 test题目 [house of apple 2] 利用方法
-
发表于: 2026-6-2 16:20 2754
-
本次校赛中,这道堆利用题目涉及了 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 的数字,放入学生chunk的 0-4 字节处,管理chunk 存入全局数组,下标为当前学生数量
首先 buf[0] 会等于 0-127 中的一个数字,然后score ---> buf[0] % (q_num * 10) ,分数会写入 学生chunk 的 4-8字节处,如果当前学生lazy标识为1,则该学生的分数会等于当前值 - 10
往 学生chunk 的 8-16 字节处写入 comment_chunk ,16-24字节处写入comment_chunk_size,大小最大1023` 字节
按照先后顺序依次释放 comment_chunk,学生chunk,管理chunk,最后把全局数字对应下标清零,学生数量 - 1
这个没啥好讲的
一个小后门,假如当前学生的分数大于 89 就会输出这个学生对应的 学生chunk 地址,并且让一个我们传入的地址的值 +1,后面是正常输出学生评语,当我们触发后门时,它会给 管理chunk 的 0x1C 写入 1 后面该学生不会在触发输出评语函数
注意读取地址时的 sub_131A() 函数,它会把我们输入的最后一个字符改为 0,假设我们输入 123456\n ---> 12345\0\n ,我们需要多输入一个字符跳过这个坑
假如当前学生的 lazy 标识不为1 就会往 管理chunk 的 16-24 字节写入 mode_chunk ,假如标识为1往 管理chunk 的 16 字节写入一个整数
整体程序大量使用了 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_1 的 comment_chunk 了

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


注意是 0x49 不是 0x48,多偏移一个字节才能修改 0x3,我们可以看一下堆内存,看一下前面部署的能不能通过检查
接下来释放掉这个被修改大小的 chunk ,让他进入 unsorted bin
我们接着把 stu_1 的 学生chunk 中的 comment_chunk_addr 修改成 stu_0 的 comment_chunk,就可以泄露libc基址了


原理:当程序退出时,会把未写入的缓冲区数据写入,我们伪造 _IO_write_ptr > _IO_write_base 表示有数据需要写入,_IO_write_base = 0,程序会认为我们没有缓冲区,但是我们要写入数据,所以他会创建一个缓冲区给我们,就是函数__doallocate,偏移为0x68,所以我们在_IO_wfile_jumps偏移0x68处写入system地址,在结构体头部写入sh即可完成攻击
题目没有符号表,我强行让gdb输出的,只能看最基础的,不过也够了 :( ,可以看出我们的布局十分完美

[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!