Large Bin Attack学习(_int_malloc源码细读 )




源码分析(largebin malloc)


在正式阅读源码之前,我们先理清楚largebin的结构(去除了头部的fd_nextsize/bk_nextsize 为了图片干净一点)
Large Bin Attack学习(_int_malloc源码细读 )

我们可以简化一下,去除尾链的fd和头链的bk,方便我们理清逻辑。
Large Bin Attack学习(_int_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 这个关系。

从unsorted_chunk最后一位开始遍历,直到碰到unsorted_bin的头部,我在这里,没很确定是否unsortedbin可不可以指向自己,我们可以调试看看。
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)){ bck = victim->bk; size = chunksize (victim); /* 计算 size */ // ...}

调试:

unsortedbin
all: 0x555555559680 —▸ 0x7ffff7fb9be0 (main_arena+96) ◂— 0x555555559680

然后查看chunk结构:
Free chunk (unsortedbin) | PREV_INUSEAddr: 0x555555559680Size: 0x90 (with flag bits: 0x91)fd: 0x7ffff7fb9be0bk: 0x7ffff7fb9be0

查看unsortedbin的大小:
pwndbg> tel 0x7ffff7fb9be000:0000│ rdx r10 r11 0x7ffff7fb9be0 (main_arena+96) —▸ 0x5555555597a0 ◂— 0x001:0008│ 0x7ffff7fb9be8 (main_arena+104) ◂— 0x002:0010│ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x003:0018│ 0x7ffff7fb9bf8 (main_arena+120) —▸ 0x555555559680 ◂— 0x004:0020│ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x005:0028│ 0x7ffff7fb9c08 (main_arena+136) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x006:0030│ 0x7ffff7fb9c10 (main_arena+144) —▸ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x007:0038│ 0x7ffff7fb9c18 (main_arena+152) —▸ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x555555559680 ◂— 0x0

可以发现fd bk都是指向的unsortedbin中第一个chunk,我们清空unsortedbin看看。
pwndbg> tel 0x7ffff7fb9be000:0000│ rsi r11 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x001:0008│ 0x7ffff7fb9be8 (main_arena+104) —▸ 0x555555559710 ◂— 0x9002:0010│ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x003:0018│ 0x7ffff7fb9bf8 (main_arena+120) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x004:0020│ 0x7ffff7fb9c00 (main_arena+128) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x005:0028│ 0x7ffff7fb9c08 (main_arena+136) —▸ 0x7ffff7fb9bf0 (main_arena+112) —▸ 0x7ffff7fb9be0 (main_arena+96) —▸ 0x555555559830 ◂— 0x006: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进行的检查


不能小于2*SIZE_SZ不能大于av->system_men也就是该分配去的内存分配总量。
if (__glibc_unlikely (size <= 2 * SIZE_SZ) || __glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): invalid size (unsorted)");

对next chunk(物理意义上的紧挨着)也进行一样的操作。
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的prev_size 是否等于当前的chunk size:

size = chunksize (victim); /* 计算 size */
/* 如果 next chunk 中记录前一个 chunk 大小的 prev_size 与 size 不符,则报错 */if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size)) malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");

检查bck的fd是否为当前chunk,或者当前chunk的fd是否是bin的头结点。

bck = victim->bk;
victim = unsorted_chunks (av)->bk)
应该就是检查下一个chunk是否是合法的
if (__glibc_unlikely (bck->fd != victim) || __glibc_unlikely (victim->fd != unsorted_chunks (av))) malloc_printerr ("malloc(): unsorted double linked list corrupted");

检查当前chunk是否是free的,通过next chunk的p值:
/* 如果 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);


大小刚好相等情况


如果chunk和当前需要的chunk大小一致,则直接返回chunk 并且设置物理意义上紧挨着的下一个chunk的size中p为0也就是free状态。
set_inuse_bit_at_offset (victim, size);

如果开启了tcache机制 且tcache未满则将chunk放入tcache中。
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赋值


◆如果属于small bin则进行fwd和bck的赋值
small bin 的链表表头赋值给 bck:bck = bin_at (av, victim_index);
首个chunk赋值给fwd :fwd = bck->fd;
if (in_smallbin_range (size)){ victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd;}

largebin 入bin链和chunk size链


如果属于large_bins同理进行赋值,然后判断该插入什么合适的位置。

因为largebin是按照大小进行的排序,由大到小,所以最小的在链表最后。
victim_index = largebin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;

判断large是否有空闲chunk:
if (fwd != bck)

如果当前chunk比最后一位chunk还小则直接加入链表末尾。
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


largebin情况

chunk脱链 remainder chunk入链

首先是判断情况,我们只处理这一种情况:largebin中有chunk,然后largebin中最大的chunk大于我们的需求。

接下来的代码都是从largebin中获取chunk。
if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim)>= (unsigned long) (nb))

否则进行遍历判断,匹配第一个小于等于当前chunk的:
while ((unsigned long) size < chunksize_nomask (fwd)){ fwd = fwd->fd_nextsize; assert (chunk_main_arena (fwd));}

如果该chunk与当前chunk相同则让chunk插入fwd之后,所以:
因为large bin是按照大小进行的排序,所以我们为了不额外修改chunk size链表,直接将chunk链接到fwd后面。
if ((unsigned long) size== (unsigned long) chunksize_nomask (fwd)) fwd = fwd->fd;

当我们需求的chunk size大于large中所有的chunk size的情况,执行largebin的入chunk_size链操作:<span id = “attack”></span>
这里我理解的是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的入链操作。


首先这是初始状态:

Large Bin Attack学习(_int_malloc源码细读 )


让bck等于fwd->bk,也就是把bck提到fwd前方,并且进行安全检查。
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))


取最小的chunk,反方向循环,找到刚好大于等于我们所需chunk size的 chunk。

如果一个大小的chunk链表中有多个chunk,优先取第二个,不轻易改变chunk size链表的值。
// 取largebin的最后一个chunk 也就是最小的那个chunkvictim = victim->bk_nextsize;// 取首个大于所需的chunk size的large chunkwhile (((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;


chunk 通过unlink脱链,remainder chunk入unsortedbin链。

安全检查,是否切割后的chunk大于minsize 与安全检查 largebin第一个chunk和头的互锁状态。
// 算剩余的remainder_size remainder_size = size - nb; // 对 我们large bin中的chunk 进行unlink操作 unlink_chunk (av, victim);
/* Exhaust */// 安全检查 如果切割的chunk 小于Minsize 则 设置下一个chunk p为0if (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不够就去系统申请。





漏洞利用


我们主要是利用:largechunk中最大的chunk还是小于我们所需求的chunk大小(https://bbs.kanxue.com/thread-280827.htm?style=1#attack)这种情况,我们来详细分析一下这个流程中究竟干了什么?

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;

我们可以发现这里的代码,危险的地方在于如果现在我们能够修改largebin中fwd位置的chunk,我们就能够泄露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



参考文章:
wiki:Large Bin Attack – CTF Wiki (https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/large-bin-attack/)

源码级调试glibc:源码级调试glibc_glibc cannot be compiled without optimization-CSDN博客(https://blog.csdn.net/astrotycoon/article/details/52662685)

源码分析:glibc 2.31 malloc与free 源码分析(https://www.cnblogs.com/luoleqi/p/15520621.html) – PwnKi – 博客园 (cnblogs.com)
glibc malloc源码分析 – PwnKi – 博客园 (https://www.cnblogs.com/luoleqi/p/12731875.html#_int_malloc)


Large Bin Attack学习(_int_malloc源码细读 )


看雪ID:ElegyYuan0x1

https://bbs.kanxue.com/user-home-994584.htm

*本文为看雪论坛优秀文章,由 ElegyYuan0x1 原创,转载请注明来自看雪社区

Large Bin Attack学习(_int_malloc源码细读 )


# 往期推荐

1、XAntiDenbug的检测逻辑与基本反调试

2、Frida-Hook-Java层操作大全

3、符号执行去除BR指令混淆

4、Frida 实战 KGB Messenger

5、打造柚子(yuzu)模拟器的金手指工具

6、APP sign签名参数分析


Large Bin Attack学习(_int_malloc源码细读 )

Large Bin Attack学习(_int_malloc源码细读 )

球分享

Large Bin Attack学习(_int_malloc源码细读 )

球点赞

Large Bin Attack学习(_int_malloc源码细读 )

球在看



Large Bin Attack学习(_int_malloc源码细读 )

点击阅读原文查看更多

原文始发于微信公众号(看雪学苑):Large Bin Attack学习(_int_malloc源码细读 )

版权声明:admin 发表于 2024年3月31日 下午6:00。
转载请注明:Large Bin Attack学习(_int_malloc源码细读 ) | CTF导航

相关文章