pig
1 | GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9) stable release version 2.31. |
- PS:源题目是
2.31-0ubuntu9.1
1 | pig: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e9ee5187a2dee7365b11251f5fe19c5217b84ab5, for GNU/Linux 3.2.0, stripped |
- 64位,dynamically,全开
漏洞分析
在释放模块中没有置空 free chunk 的指针:
1 | if ( *(_QWORD *)(a1 + 8LL * num) && !*(_BYTE *)(a1 + num + 288) && !*(_BYTE *)(a1 + num + 312) ) |
- UAF 漏洞,但是
a1 + num + 0x120
这个位置用于记录该 chunk 是否 free
1 | if ( num < 0x14 ) |
- 后续会检查
a1 + num + 0x120
的标记
在实现“角色切换”的函数中,缺少关键位的初始化:
1 | unsigned __int64 __fastcall change_1(__int64 a1) |
- 第二个
memcpy
刚好复制到a1 + 0x120
就停止了,没有复制 free chunk 标记位 - 导致后续将
mmap_buf
赋值回a1
时,free chunk 标记位被置空:
1 | unsigned __int64 __fastcall set_mmap_buf_1(__int64 a1) |
- 也就是说,当
a1
中的标志位被设置为 “1” 时,可以通过修改角色并切回将其置为 “0”
在“角色切换”函数中的保护可以被 “\x00” 截断:
1 | if ( !memcmp(decode, pwd1, 0x11uLL) || !memcmp(decode, pwd2, 0x11uLL) || !strcmp(decode, pwd3) ) |
- 原本程序有一个对 hash 的检查保护,3个 hash 密码只要对一个就可以通过
- 但最后一个 hash 的匹配函数为
strcmp
可以被 “\x00” 截断 - 分别爆破一下以
A B C
开头,并且可以通过strcmp
的密码即可
入侵思路
程序使用 calloc 申请内存,与 malloc 相比,calloc 主要有以下特点:
- 对内存空间进行初始化
- 跳过 tcache,无法完成常规的 tcache attack 等利用
由于本程序使用了 calloc 并限制了 size 的大小,常规的 fastbin 和 tcache 攻击都不起作用,通常使用 calloc 申请的程序就可以考虑使用 house of pig
程序的限制如下:
- 第1种 chunk:每隔 0x30 中的前面 0x10 个字节可以被写一次(共20个)
- 第2种 chunk:每隔 0x30 中的中间 0x10 个字节可以被写一次(共10个)
- 第3种 chunk:每隔 0x30 中的后面 0x10 个字节可以被写一次(共5个)
- 申请 chunk 时,给定的 size 必须从小到大
而我们需要完成如下工作:
- 将某个 tcach 填满,进行泄露
- 第一次 largebin attack 往
free_hook-8
写入堆地址(PS:进行 largebin attack 的 chunk 将不能被申请,因此最好将 largebin attack 放到后面) - 第二次 largebin attack 劫持
_IO_list_all
- 进行 tcache_stashing_unlink 攻击
- 写入 fake_IO_FILE 并将其触发
基于题目本身的限制,本题目的堆风水比较难,但总体来说保持如下的思路:
- 第1种类型数目最多,用它来填满 tcache 并泄露地址,由于它可以写入前 0x10 字节,于是需要用它来进行 tcache_stashing_unlink 攻击
- 第2种类型可以中间 0x10 字节,用它来进行 largebin attack
- 第3种类型只有5个,申请完这5个 chunk 后,会提供一个无限制的输入,只能用它来写 fake IO_file,因此也只能用前5个 chunk 来进行一些填充操作
先进行泄露,并为 tcache_stashing_unlink 做准备:
1 | change(2) |
- 其实这里我尝试了各种各样的堆风水,申请大堆块然后用它来分割出合适的 small chunk,但要么是后面的 size 太小不能申请,要么是申请的次数不够
- 参考了下官方 wp 的做法,把 tcache_stashing_unlink 和泄露的过程放在一起,再把另一个 tcache 填满(不申请大堆块),使 chunk 保持较小的 size,方便之后的 largebin attack
- 此时的堆布局如下:
1 | pwndbg> bins |
接下来要进行第一次 largebin attack,往 free_hook-0x8
写入一个可控堆地址:
1 | # largebin attack1 |
- PS:其实 largebin attak 不需要申请更大的 chunk,只需要让 unsorted chunk 分割即可
- 这个 large chunk 我们需要使用两次,因此在 largebin attack 结束以后要将它复原
然后进行第二次 largebin attack,用于劫持 io_list_all
:
1 | # largebin attack2 |
由于我们没法直接控制写入 io_list_all
的 chunk(程序对输入有限制),只能将其申请回来,并修改 _chain
条目为第3种类型的第6个 chunk(这是唯一一个可以完整写入的 chunk)
然后执行 tcache_stashing_unlink 攻击将 free_hook
写到 tcache 的头部,最后写入 fake_IO_FILE 并触发 IO 流
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |