pwn.college 中的 Dynamic Allocator Misuse 堆漏洞利用思路小结

level 1

UAF

level 2

UAF
read_flag 申请的堆块大小做了一个 rand 处理,但是并未设置随机种子,所以每次运行还是固定的值

level 3

UAF
read_flag 申请 2 次堆块,但我们同时也能访问多个堆块

level 4

Double Free
思考 tcache 在 free 时会做什么检查

level 5

read_flag 会将申请的堆块首字节置 0,puts_flag 检查其是否为 0
可以利用 free 设置 next 指针将其修改

level 6

未开启 PIE,.bss 段上存放了 8 字节 secret,需要读出来作为 send_flag 的输入
可以修改 next 指针从而 malloc 得到目标地址堆块

level 7

和 level 6 相似,只是 8 字节 secret 变成了 16 字节
注意 malloc 会将 key 清 0 即可

level 8

secret 地址最低字节为 \x0A,即 \n,通过 scanf 发送给程序会被截断
所以不断修改最低字节分部分泄露即可,善用 malloc 将 key 清 0

level 9

和上面的题目类似
无法访问分配到目标地址的堆块,但是 malloc 确实执行了

level 10

泄露了栈地址和 main 函数地址,提供了 win 函数
只需分配一个栈上的 chunk 然后覆盖返回地址为 win 函数地址即可

level 11

如何获取栈地址和程序加载地址,关键在 echo 执行了 malloc,而我们有 UAF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 __fastcall echo(__int64 a1, __int64 a2)
{
char **argv; // [rsp+18h] [rbp-18h]
char v4[6]; // [rsp+22h] [rbp-Eh] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
strcpy(v4, "Data:");
argv = (char **)malloc(0x20uLL);
*argv = "/bin/echo";
argv[1] = v4;
argv[2] = (char *)(a1 + a2);
argv[3] = 0LL;
if ( !fork() )
{
execve(*argv, argv, 0LL);
exit(0);
}
wait(0LL);
return __readfsqword(0x28u) ^ v5;
}

level 12

House of Spirit
提供了 stack_scanf 写栈,stack_free 尝试 free 栈上一个地址,stack_malloc_win 尝试 malloc 然后对比拿到的地址和 stack_free 地址,相同则输出 flag
所以利用 stack_scanf 伪造一个待 free 的堆块即可

level 13

House of Spirit
栈上存放了 16 字节的 secret,拿到 secret 就可以拿到 flag
同 level 12 利用 stack_scanf 伪造一个待 free 的堆块,再 malloc 得到栈上的堆块,即可控制 secret

level 14

和 level 11 类似,但是 echo 不一样了,主要区别在于栈地址的获取不太一样
不过我们依然可以获取,因为我们能够 malloc 拿到栈上的堆块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__pid_t __fastcall echo(__int64 a1, __int64 a2)
{
char **argv; // [rsp+18h] [rbp-8h]

argv = (char **)malloc(0x20uLL);
*argv = "/bin/echo";
argv[1] = "Data:";
argv[2] = (char *)(a1 + a2);
argv[3] = 0LL;
if ( !fork() )
{
execve(*argv, argv, 0LL);
exit(0);
}
return wait(0LL);
}

level 15

UAF 被 ban,但是 echo 是可以跨 chunk 访问的,所以我们依然能够利用 echo 拿到栈地址和程序加载地址
并且提供的 read 也是不限长度,我们只需要最开始分配一个块,将其作为我们访问后续空间的跳板

level 16

从这一题开始,glibc 版本升到 2.35,主要的变化是 safe-linking 机制,以及堆块地址 0x10 对齐
safe-linking 会对 next 指针进行保护,需要了解在 malloc / free 时 next 指针发生了什么变化

Safe-Linking
Safe-Linking in Malloc
Safe-Linking in Free

这道题就是 level 9 的 safe-linking 版,需要额外弄清楚下面 2 件事:

  1. 要分配到目标地址,next 应该填入什么
  2. malloc 目标地址后,我们无法访问该 chunk,思考其 next 指针的值会改变什么

level 17

level 10 的 safe-linking 版
直接分配到返回地址附近变得不可行,原因如下:

  1. chunk 地址 0x10 对齐,不能分配到 saved rip 地址
  2. 分配到 saved rbp 地址,chunk size 是 canary,会导致 malloc_usable_size 访问非法地址,从而发生段错误
  3. 再往栈上更低地址分配,需要泄露 canary

幸运的是题目泄露的栈地址不仅仅是栈地址,而是确切的我们可访问的堆块指针列表的地址

level 18

level 13 的 safe-linking 版
但是好像不用管 safe-linking

level 19

Chunk Overlapping
House of Spirit 是直接伪造,这个就是修改现成的

level 20

这道题是前面内容的综合
初看无从下手,基本目标是要泄露 libc 地址和栈地址,但是仅以前面所学知识我们不知道仅靠 tcache 能获取什么,之前的题目或多或少都提供了栈地址 / binary 地址 / 获取 flag 的便捷方式

根据 level 19,我们能想到可以构造重叠堆块,但是接下来呢?堆内存会存放 libc 的位置吗?

经过摸索可以发现以下事实:

  1. malloc 一个较大的重叠堆块并用 safe_write 输出其中内容,会发现疑似 libc 地址的东西
  2. 几次 malloc 之间调用一次 safe_write 会导致这几次 malloc 得到的地址不连续

关键:safe_write 第一次被调用时会调用 fdopen 打开 FD 1,并调用 malloc 申请堆块存储相应的 locked_FILE 结构体

level 20 部分源码:

1
2
3
4
if ( strcmp(s1, "safe_write") )
break;
if ( !size_4 )
size_4 = fdopen(1, "w");

fdopen 部分源码:

1
2
3
new_f = (struct locked_FILE *)malloc(sizeof(struct locked_FILE));
if (new_f == NULL)
return NULL;

也就是说,我们可以通过重叠堆块访问到一个 _IO_FILE_plus 结构体,该结构体中的 vtable 指向一个全局的跳转表 _IO_file_jumps,得到它就获得了 libc 地址

下面展示 stdout 结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pwndbg> p *(struct _IO_FILE_plus *) stdout
$1 = {
file = {
_flags = -72540028,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7e1aaa0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7ffff7e1ca70 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7ffff7e1a9a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7e17600 <_IO_file_jumps>
}

至于如何拿到栈地址,可以问问 environ