​ 由于Pwn的堆方向感觉异常抽象,所以,我想着通过how2heap这个项目来入门heap。但是,由于有的程序不知为何调试总是出问题,不过,我会慢慢来解决的,所以这个文章也是处于慢慢更新的状态。

​ glibc版本:最新

​ 源码:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
fprintf(stderr, "尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制\n");
fprintf(stderr, "glibc 使用首次适应算法选择空闲的堆块\n");
fprintf(stderr, "如果有一个空闲堆块且足够大,那么 malloc 将选择它\n");
fprintf(stderr, "如果存在 use-after-free 的情况那可以利用这一特性\n");

fprintf(stderr, "首先申请两个比较大的 chunk\n");
char* a = malloc(0x512);
char* b = malloc(0x256);
char* c;

fprintf(stderr, "第一个 a = malloc(0x512) 在: %p\n", a);
fprintf(stderr, "第二个 a = malloc(0x256) 在: %p\n", b);
fprintf(stderr, "我们可以继续分配\n");
fprintf(stderr, "现在我们把 \"AAAAAAAA\" 这个字符串写到 a 那里 \n");
strcpy(a, "AAAAAAAA");
fprintf(stderr, "第一次申请的 %p 指向 %s\n", a, a);

fprintf(stderr, "接下来 free 掉第一个...\n");
free(a);

fprintf(stderr, "接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: %p\n", a);

c = malloc(0x500);
fprintf(stderr, "第三次 c = malloc(0x500) 在: %p\n", c);
fprintf(stderr, "我们这次往里写一串 \"CCCCCCCC\" 到刚申请的 c 中\n");
strcpy(c, "CCCCCCCC");
fprintf(stderr, "第三次申请的 c %p 指向 %s\n", c, c);
fprintf(stderr, "第一次申请的 a %p 指向 %s\n", a, a);
fprintf(stderr, "可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 \"CCCCCCCC\"\n");
}

​ 编译一下:gcc -g first_fit.c

​ 运行一下看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
g01den@MSI:~/Temp$ ./heap
尽管这个例子没有演示攻击效果,但是它演示了 glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x5595c3a182a0
第二个 a = malloc(0x256) 在: 0x5595c3a187c0
我们可以继续分配
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x5595c3a182a0 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x5595c3a182a0
第三次 c = malloc(0x500) 在: 0x5595c3a182a0
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
第三次申请的 c 0x5595c3a182a0 指向 CCCCCCCC
第一次申请的 a 0x5595c3a182a0 指向 CCCCCCCC
可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 "CCCCCCCC"

​ 开始调试:

​ 首先,最开始,在第一次malloc分配内存之前,使用vmmap查看内存布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555555000 r--p 1000 0 /home/g01den/Temp/how2heap/first
0x555555555000 0x555555556000 r-xp 1000 1000 /home/g01den/Temp/how2heap/first
0x555555556000 0x555555557000 r--p 1000 2000 /home/g01den/Temp/how2heap/first
0x555555557000 0x555555558000 r--p 1000 2000 /home/g01den/Temp/how2heap/first
0x555555558000 0x555555559000 rw-p 1000 3000 /home/g01den/Temp/how2heap/first
0x7ffff7dcb000 0x7ffff7ded000 r--p 22000 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7ded000 0x7ffff7f65000 r-xp 178000 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7f65000 0x7ffff7fb3000 r--p 4e000 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fb3000 0x7ffff7fb7000 r--p 4000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fb7000 0x7ffff7fb9000 rw-p 2000 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fb9000 0x7ffff7fbf000 rw-p 6000 0 [anon_7ffff7fb9]
0x7ffff7fc9000 0x7ffff7fcd000 r--p 4000 0 [vvar]
0x7ffff7fcd000 0x7ffff7fcf000 r-xp 2000 0 [vdso]
0x7ffff7fcf000 0x7ffff7fd0000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000 0x7ffff7ff3000 r-xp 23000 1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000 0x7ffff7ffb000 r--p 8000 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 [anon_7ffff7ffe]
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]

​ 明显,这里不存在heap段,进行malloc之后,发现多出来了个heap段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555555000 r--p 1000 0 /home/g01den/Temp/how2heap/first
0x555555555000 0x555555556000 r-xp 1000 1000 /home/g01den/Temp/how2heap/first
0x555555556000 0x555555557000 r--p 1000 2000 /home/g01den/Temp/how2heap/first
0x555555557000 0x555555558000 r--p 1000 2000 /home/g01den/Temp/how2heap/first
0x555555558000 0x555555559000 rw-p 1000 3000 /home/g01den/Temp/how2heap/first
0x555555559000 0x55555557a000 rw-p 21000 0 [heap]
0x7ffff7dcb000 0x7ffff7ded000 r--p 22000 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7ded000 0x7ffff7f65000 r-xp 178000 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7f65000 0x7ffff7fb3000 r--p 4e000 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fb3000 0x7ffff7fb7000 r--p 4000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fb7000 0x7ffff7fb9000 rw-p 2000 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fb9000 0x7ffff7fbf000 rw-p 6000 0 [anon_7ffff7fb9]
0x7ffff7fc9000 0x7ffff7fcd000 r--p 4000 0 [vvar]
0x7ffff7fcd000 0x7ffff7fcf000 r-xp 2000 0 [vdso]
0x7ffff7fcf000 0x7ffff7fd0000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000 0x7ffff7ff3000 r-xp 23000 1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000 0x7ffff7ffb000 r--p 8000 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 [anon_7ffff7ffe]
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]

​ 先查看下chunk有些啥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x555555559290
Size: 0x520 (with flag bits: 0x521)

Allocated chunk | PREV_INUSE
Addr: 0x5555555597b0
Size: 0x260 (with flag bits: 0x261)

Top chunk | PREV_INUSE
Addr: 0x555555559a10
Size: 0x205f0 (with flag bits: 0x205f1)

​ 根据下面两个输出:

1
2
第一个 a = malloc(0x512) 在: 0x5555555592a0
第二个 a = malloc(0x256) 在: 0x5555555597c0

​ 可以看出来,上面那个heap结构中,第二个为a所在地址,第三个为b所在地址,为啥上下看到的地址不同,这个主要是和malloc函数返回指针的地址有关,这个这儿就不细说了。

​ 在把AAAAAAAA这个字符串写入a中,我们看看a的内存布局:

1
2
3
4
5
6
pwndbg> x/10gx 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000521
0x5555555592a0: 0x4141414141414141 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000000
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000

​ 可以看出来,已经成功写入了,先free掉a,之后malloc一个比a小一点的,把这个地址赋给c,可以看出,输出的时候发现a原本的地址和c的地址相等:

1
2
#第一个 a = malloc(0x512) 在: 0x5555555592a0
第三次 c = malloc(0x500) 在: 0x5555555592a0

​ 之后将CCCCCCCC写入c的地址,之后通过a和c输出试试看:

1
2
第三次申请的 c 0x5555555592a0 指向 CCCCCCCC
第一次申请的 a 0x5555555592a0 指向 CCCCCCCC

​ 当释放了一块内存之后再去申请一个大小略小的空间,那么 glibc 倾向于将先前释放的空间重新分配,由于a的指针没有被置零,这就造成了可以直接通过a来访问已经free掉的chunk和重新分配好的略小的chunk,由此,造成了UAF漏洞。