unlink glibc2.23 unlink源码
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 #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; } } } }
glibc 2.31unlink源码
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 static void unlink_chunk (mstate av, mchunkptr p) { if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size" ); mchunkptr fd = p->fd; mchunkptr bk = p->bk; if (__builtin_expect (fd->bk != p || bk->fd != p, 0 )) malloc_printerr ("corrupted double-linked list" ); fd->bk = bk; bk->fd = fd; if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL ) { if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p) malloc_printerr ("corrupted double-linked list (not small)" ); 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; } } }
2.31比2.23多了chunk_size == next_chunk的prev_size的检测,prev_size位正常情况下都是能修改的,更不用说能修改prev_inuse位的情况,所以影响不大,下面以2.23为例
1.哪些情况下会unlink malloc 从恰好大小合适的largebin中获取chunk,发生unlink 从比malloc要求大的largebin中取chunk,发生unlinkFree 后向合并,合并物理相邻低物理地址空闲chunk时 前向合并,合并物理相邻高物理地址空闲chunk时(top chunk除外)
合并时对被合并的chunk的大小无要求,前向合并时,在低地址的chunk中构造一个被合并的chunk,同时将高地址chunk的prev_size位设置成被合并chunk的大小(包括chunk_header),size位的最低地址设置为0
malloc_consolidate 后向合并,合并物理相邻低地址空闲chunk时。 前向合并,合并物理相邻高地址空闲 chunk时(top chunk除外)realloc
前向扩展,合并物理相邻高地址空闲chunk(除top chunk)
为什么small bin和unsorted bin不发生unlink glibc malloc和free源码解析
事实上small bin和unsorted bin是在malloc函数中进行脱链的,而不是通过unlink函数,当malloc的chunk需要从small bin和unsorted bin中取出时,进行类似unlink中的操作
下面是malloc函数中small bin的分配源码,关键是这几句,victim指向双向链表末尾的small chunk(small bin采用先进先出策略),该chunk的fd指向链表头,即代码中的bin,bk指向前一个chunk
if (__glibc_unlikely (bck->fd != victim))
bck = victim->bk;
bin->bk = bck; bck->fd = bin;
这个过程其实和unlink很像,但问题在于bin是不可控的,如果按照unlink的利用思路,bin->bk = bck;不仅无法通过fd来指定地址,还会破坏链表,bck->fd = bin;可以由bk来指定要修改的地址,但是内容固定为bin
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 #define NBINS 128 #define NSMALLBINS 64 #define SMALLBIN_WIDTH MALLOC_ALIGNMENT #define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ) #define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH) #define in_smallbin_range(sz) \ ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE) #define smallbin_index(sz) \ ((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\ + SMALLBIN_CORRECTION) #define bin_at(m, i) \ (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \ - offsetof (struct malloc_chunk, fd)) #define first(b) ((b)->fd) #define last(b) ((b)->bk) if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) { if (victim == 0 ) malloc_consolidate (av); else { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted" ; goto errout; } set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } }
2.unlink时的漏洞及利用 默认64位
关键代码
1 2 3 4 5 6 7 FD = P->fd; BK = P->bk; if (__builtin_expect (FD->bk != P || BK->fd != P, 0 )) FD->bk = BK; BK->fd = FD;
绕过检测要求:
1 2 3 4 5 6 7 8 p->fd->bk = p p->bk->fd = p 等价于 *(*(P+0X10 )+0X18 ) = P *(*(P+0X18 )+0X10 ) = P 等价于 FD = &p - 0x18 #&p为heap_array中当前chunk(被合并chunk)的指针的存放地址 BK = &p - 0x10
也就是说把被unlink的chunk的fd和bk分别设置成&p - 0x18和&p - 0x10 就能绕过检测 ,这里需要题目中存在一个存放p指针的数据结构
p是heap_array中存放的指针(指向低地址的chunk)
p是heap_array中存放的指针(指向低地址的chunk)
p是heap_array中存放的指针(指向低地址的chunk)
重要的话说三遍,高地址的chunk被合并后会被清空
绕过检测后执行
1 2 3 4 5 FD->bk = BK; BK->fd = FD; 等价于 p = &p - 0x10 p = &p - 0x18
p是原本指向被unlink的chunk的chunk头的指针,此时已经指向了&p-0x18,假设p原本被存放在bss段的一个数组(heap_array)中,那么此时修改p原本指向的chunk的内容,就变成了修改bss段chunk头首地址在&p-0x18的fake chunk的内容,通常就可以修改存放chunk指针的数组了
总结:
条件:
bss段存在chunk指针数组heap_arr(经典的用法)
第一种情况:存在uaf,使被合并chunk在被free后还能修改chunk中的fd和bk指针
第二种情况:存在堆溢出,将被合并的chunk伪装成释放状态
构造:一般是后向合并,即free掉高地址的chunk,去合并低地址的chunk。在被合并chunk的usr_data中再伪造一个chunk,构造该chunk的fd和bk(fake_fd = &p - 0x18 fake_bk = &p - 0x10),同时将高地址chunk的prev_size位设置成伪造的chunk的大小(包括chunk_header),size位的最低地址设置为0。
效果:
p = &p - 0x18
p是heap_array中存放的指针(指向低地址的chunk) ,&p是该指针在heap_array中的地址,unlink后再去修改该指针指向的chunk的内容就变成了修改heap_array的内容
3.例题: [ZJCTF 2019]EasyHeap hitcon2014_stkof