一
源码分析(largebin malloc)
大概就是这个样子,也就是bin头部通过fd/bk链接chunk size链表的头部和尾部,然后chunk size链表之间通过fd_nextsize/bk_nextsize链接。
chunksize链表中同一个大小的chunk通过fd/bk进行链接,所以largebin的fd和bk和其他的双向链不同我们不能通过从bin一路通过fd返回到large bin的头部。
Unsortedbin的合并/入链/分配操作
遍历的开始(梦的开始)
后面的操作中最重要的就是Victim变量,这个变量是当前循环到的unsortedbin chunk<br>bck变量,也就是bck <——-> victim 这个关系。
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)){
bck = victim->bk;
size = chunksize (victim); /* 计算 size */
// ...
}
调试:
unsortedbin
all: 0x555555559680 —▸ 0x7ffff7fb9be0 (main_arena+96) ◂— 0x555555559680
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555555559680
Size: 0x90 (with flag bits: 0x91)
fd: 0x7ffff7fb9be0
bk: 0x7ffff7fb9be0
pwndbg> tel 0x7ffff7fb9be0
00:0000│ rdx r10 r11 0x7ffff7fb9be0 (main_arena+96) —▸ 0x5555555597a0 ◂— 0x0
01:0008│ 0x7ffff7fb9be8 (main_arena+104) ◂— 0x0
02:0010│ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x0
03:0018│ 0x7ffff7fb9bf8 (main_arena+120) —▸ 0x555555559680 ◂— 0x0
04:0020│ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x0
05:0028│ 0x7ffff7fb9c08 (main_arena+136) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x0
06:0030│ 0x7ffff7fb9c10 (main_arena+144) —▸ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x0
07:0038│ 0x7ffff7fb9c18 (main_arena+152) —▸ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x0
pwndbg> tel 0x7ffff7fb9be0
00:0000│ rsi r11 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x0
01:0008│ 0x7ffff7fb9be8 (main_arena+104) —▸ 0x555555559710 ◂— 0x90
02:0010│ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x0
03:0018│ 0x7ffff7fb9bf8 (main_arena+120) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x0
04:0020│ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x0
05:0028│ 0x7ffff7fb9c08 (main_arena+136) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x0
06:0030│ 0x7ffff7fb9c10 (main_arena+144) —▸ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— ...
07:0038│ 0x7ffff7fb9c18 (main_arena+152) —▸ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x55555555983
我们会发现fd和bk都是指向了自己本身也就是main_arena+96这个位置。
安全检查机制:这里的安全机制全是对unsortedbin中的chunk进行的检查
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
mchunkptr next = chunk_at_offset (victim, size); /* 获得指向内存空间中当前 chunk 的下一个chunk 的指针 */
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
/* 如果 next chunk 中记录前一个 chunk 大小的 prev_size 与 size 不符,则报错 */
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
/* 如果 next chunk 中的显示前一个 chunk 是否正在使用的标志位为1,*/
/* 即前一个 chunk 正在使用,则报错 */
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
直接返回smallbin_chunk情况
然后就是从unsortedbin割small chunk 如果符合条件 ◆所需chunk大小在smallbin的范围之内 ◆bck为unsortedbin的头 也就是unsortedbin中仅有一个chunk ◆victim为last remainder chunk 也就是分割过一次 ◆大小刚好大于所需nb大小+Minsize(这里猜测就是一个最小chunk 这样才能切割) 满足以上条件就直接分割,然后将victim返回给用户。
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size)){
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
从unsortedbin中移除
在这里已经将chunk从unsortdbin中移除。
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
大小刚好相等情况
set_inuse_bit_at_offset (victim, size);
if (tcache_nb && tcache->counts[tc_idx] < mp_.tcache_count){
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);alloc_perturb (p, bytes);return p; /* 返回内存指针 */
归类入链操作
这里主要是将unsortedbin合并后的,入small链表或者large链表的操作。 这里的fwd和bck记好了,我们从unsortedbin抠出来的chunk就要合并进入fwd和bck的中间。 这后面的操作往往是先让fwd到指定的位置,然后bck通过fwd->bk来进行的定位。
small 和 large最终入bin操作
这里把最后的部分,直接提前拿出来。因为smallbin和largebin的入链操作都含这个代码。 largebin还有chunk size的入链操作,以及其他的复杂检查。
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
smallbin的fwd bck赋值
if (in_smallbin_range (size)){
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
largebin 入bin链和chunk size链
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
if (fwd != bck)
bck是头 bck->bk应该就是最后一位 然后要加入fwd和bck之间,我们应该先调整fwd和bck,所以bck改为链表最后一位fwd改为链表头 bck<—–>chunk<—–>fwd
if ((unsigned long) (size)< (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
从largebin中获取chunk
chunk脱链 remainder chunk入链
首先是判断情况,我们只处理这一种情况:largebin中有chunk,然后largebin中最大的chunk大于我们的需求。 接下来的代码都是从largebin中获取chunk。
if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim)>= (unsigned long) (nb))
while ((unsigned long) size < chunksize_nomask (fwd)){
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size== (unsigned long) chunksize_nomask (fwd))
fwd = fwd->fd;
这里我理解的是largebin存在两条链,也就是chunk size的链和fd bk构成的bins链,这里先是入的chunk size的链。
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
这里就是重点了 也就是large bin的入链操作。
首先这是初始状态:
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
从largebin中获取chunk
largebin情况
if (!in_smallbin_range (nb))
chunk脱链 remainder chunk入链
首先是判断情况,我们只处理这一种情况:largebin中有chunk,然后largebin中最大的chunk大于我们的需求。 接下来的代码都是从largebin中获取chunk。
if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim)>= (unsigned long) (nb))
// 取largebin的最后一个chunk 也就是最小的那个chunk
victim = victim->bk_nextsize;
// 取首个大于所需的chunk size的large chunk
while (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
// 这里避免删除chunk size链中的首个chunk 避免我们修改chunk size链表 所以我们取第二个
if (victim != last (bin) && chunksize_nomask (victim) == chunksize_nomask (victim->fd))
victim = victim->fd;
// 算剩余的remainder_size
remainder_size = size - nb;
// 对 我们large bin中的chunk 进行unlink操作
unlink_chunk (av, victim);
/* Exhaust */
// 安全检查 如果切割的chunk 小于Minsize 则 设置下一个chunk p为0
if (remainder_size < MINSIZE){
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}else{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
// 根据注释大概知道是进行完整的插入操作
// 取得unsorted_chunk bin链表的的头
bck = unsorted_chunks(av);
// 取 第一个chunk
fwd = bck->fd;
// 安全检查:检查第一个chunk的bk是否为unsorted bin的头
if (__glibc_unlikely (fwd->bk != bck)){
malloc_printerr ("malloc(): corrupted unsorted chunks");
}
// remainder 入unsortedbin
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
// 如果是remiander 则将fd_nextsize bk_nextsize 设置为null
if (!in_smallbin_range (remainder_size)){
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 这里应该是设置head的一系列操作
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
// foot就是下一个chunk的prev_size部分
set_foot (remainder, remainder_size);
}
返回被切割后的chunk
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
从topchunk中获取chunk
我是大概浏览的,大概意思是去剩下的chunk中寻找,如果没找到就去topchunk分配,如果topchunk不够就去系统申请。
二
漏洞利用
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
// 以及
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
victim->bk_nextsize->fd_nextsize = victim;
bck->fd = victim;
-
我们修改largebin中的chunk 也就是fwd的bk为我们想要泄露到的目标地址-0x10时
-
所以fwd->bk->fd也就是目标地址
-
阅读前后逻辑我们知道这段代码中bck=fwd->bk
-
bck->fd 最后被赋值victim
-
所以也就是fwd->bk->fd被赋值victim 也就是目标地址赋值victim
-
我们修改fwd的bk_nextsize为目标地址-0x20
-
所以fwd->bk_nextsize->fd_nextsize等于目标地址
-
然后也因为victim->bk_nextsize = fwd->bk_nextsize; 和victim->bk_nextsize->fd_nextsize = victim所以等价替换
-
fwd->bk_nextsize->fd_nextsize=victim也就是目标地址等于victim
看雪ID:ElegyYuan0x1
https://bbs.kanxue.com/user-home-994584.htm
# 往期推荐
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):Large Bin Attack学习(_int_malloc源码细读 )