堆Heap块Chunk

堆&块

  • 0x01 基础知识——Chunk
  • 0x02 基础知识——Heap
  • 0x03 堆的基本操作
  • 0x04 堆相关的函数
        • 1>malloc
        • 2>free
  • 0x05 堆溢出
  • 0x06 堆溢出中比较重要的几个步骤
        • ·寻找堆分配函数
          • realloc malloc calloc
        • ·寻找危险函数
        • ·确定填充长度
  • 0x07 Fast bin
  • 0x08 Unsort bin
        • 注意点
  • Fastbin attack--blind
  • FILE结构
  • double free--pwn3_text
  • Unlink
        • unlink--babynode

0x01 基础知识——Chunk

分配过的chunk

  • 32位机,8字节对齐,64位机,16字节对齐
  • 位P指明前一个chunk的状态(空闲为0,使用为1)
  • prev_size表示前一个chunk的大小
    当前chunk释放时,可用于定位前一个chunk,并将两个chunk合并为一个更大的chunk
    当P为1时,prev_size无意义,该区域(4B)可被前一个chunk使用

堆Heap块Chunk_第1张图片

在glibc malloc中将整个堆内存空间分成了连续的、大小不一的chunk,即对于堆内存管理而言chunk就是最小操作单位。

Chunk总共分为4类:
1)allocated chunk; 2)free chunk; 3)top chunk; 4)Last remainder chunk。
从本质上来说,所有类型的chunk都是内存中一块连续的区域,只是通过该区域中特定位置的某些标识符加以区分。为了简便,我们先将这4类chunk简化为2类:allocated chunk以及free chunk,前者表示已经分配给用户使用的chunk,后者表示未使用的chunk。

众所周知,无论是何种堆内存管理器,其完成的核心目的都是能够高效地分配和回收内存块(chunk)。因此,它需要设计好相关算法以及相应的数据结构,而数据结构往往是根据算法的需要加以改变的。

0x02 基础知识——Heap

在程序运行过程中,堆可以提供动态分配的内存允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。我们一般称管理堆的那部分程序为堆管理器。

堆管理器处于用户程序与内核中间,主要做以下工作

响应用户的申请内存请求,向操作系统申请内存,然后将其返回给用户程序。同时,为了保持内存管理的高效性,内核一般都会预先分配很大的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互。
管理用户所释放的内存。一般来说,用户释放的内存并不是直接返还给操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用户新申请的内存的请求。

0x03 堆的基本操作

1>堆的分配,回收,堆分配背后的系统调用
2>堆目前多线程支持

0x04 堆相关的函数

1>malloc

在 glibc 的 malloc.h 中,malloc 的说明如下

/*
  malloc(size_t n)
  Returns a pointer to a newly allocated chunk of at least n bytes, or null
  if no space is available. Additionally, on failure, errno is
  set to ENOMEM on ANSI C systems.
  If n is zero, malloc returns a minumum-sized chunk. (在大多数32位系统中,最小大小为16字节,在64位上为24字节或32字节。) 
 On most systems, size_t is an unsigned type, so calls
  with negative arguments are interpreted as requests for huge amounts
  of space, which will often fail. The maximum supported value of n
  differs across systems, but is in all cases less than the maximum
  representable value of a size_t.+
*/

可以看出,malloc 函数返回对应大小字节的内存块的指针。此外,该函数还对一些异常情况进行了处理

当 n=0 时,返回当前系统允许的堆的最小内存块。
当 n 为负数时,由于在大多数系统上,size_t 是无符号数(这一点非常重要),所以程序就会申请很大的内存空间,但通常来说都会失败,因为系统没有那么多的内存可以分配。

2>free

在 glibc 的 malloc.h 中,free 的说明如下

/*
      free(void* p)
      Releases the chunk of memory pointed to by p, that had been previously
      allocated using malloc or a related routine such as realloc.
      It has no effect if p is null. It can have arbitrary (i.e., bad!)
      effects if p has already been freed.
      Unless disabled (using mallopt), freeing very large spaces will
      when possible, automatically trigger operations that give
      back unused memory to the system, thus reducing program footprint.
*/

可以看出,free 函数会释放由 p 所指向的内存块。这个内存块有可能是通过 malloc 函数得到的,也有可能是通过相关的函数 realloc 得到的。

此外,该函数也同样对异常情况进行了处理
·当 p 为空指针时,函数不执行任何操作。
·当 p 已经被释放之后,再次释放会出现乱七八糟的效果,这其实就是 double free。
除了被禁用 (mallopt) 的情况下,当释放很大的内存空间时,程序会将这些内存空间还给系统,以便于减小程序所使用的内存空间。

0x05 堆溢出

堆溢出是指程序向某个堆块中写入的字节数超过了堆块本身可使用的字节数之所以是可使用而不是用户申请的字节数,是因为堆管理器会对用户所申请的字节数进行调整,这也导致可利用的字节数都不小于用户申请的字节数),因而导致了数据溢出,并覆盖到物理相邻的高地址的下一个堆块。

不难发现,堆溢出漏洞发生的基本前提是

  1. 程序向堆上写入数据。
  2. 写入的数据大小没有被良好地控制。

对于攻击者来说,堆溢出漏洞轻则可以使得程序崩溃,重则可以使得攻击者控制程序执行流程。

堆溢出是一种特定的缓冲区溢出(还有栈溢出, bss 段溢出等)。但是其与栈溢出所不同的是,堆上并不存在返回地址等可以让攻击者直接控制执行流程的数据,因此我们一般无法直接通过堆溢出来控制 EIP 。

一般来说,我们利用堆溢出的策略是

1.覆盖与其物理相邻的下一个 chunk 的内容。

  • prev_size
  • size,主要有三个比特位,以及该堆块真正的大小。
    • NON_MAIN_ARENA
    • IS_MAPPED
    • PREV_INUSE
    • the True chunk size
    • chunk content,从而改变程序固有的执行流。

2.利用堆中的机制(如 unlink 等 )来实现任意地址写入( Write-Anything-Anywhere)或控制堆块中的内容等效果,从而来控制程序的执行流。

0x06 堆溢出中比较重要的几个步骤

·寻找堆分配函数

realloc malloc calloc

通常来说堆是通过调用 glibc 函数 malloc 进行分配的,在某些情况下会使用 calloc 分配。calloc 与 malloc 的区别是 calloc 在分配后会自动进行清空,这对于某些信息泄露漏洞的利用来说是致命的。

calloc(0x20);
//等同于
ptr=malloc(0x20);
memset(ptr,0,0x20);

除此之外,还有一种分配是经由 realloc 进行的,realloc 函数可以身兼 malloc 和 free 两个函数的功能。
realloc 的操作并不是像字面意义上那么简单,其内部会根据不同的情况进行不同操作
当 realloc(ptr,size) 的 size 不等于 ptr 的 size 时
如果申请 size > 原来 size
如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
如果申请 size < 原来 size
如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作

·寻找危险函数

通过寻找危险函数,我们快速确定程序是否可能有堆溢出,以及有的话,堆溢出的位置在哪里。

常见的危险函数如下

输入

  • gets,直接读取一行,忽略 ‘\x00’
  • scanf
  • vscanf

输出

  • sprintf

字符串

  • strcpy,字符串复制,遇到 ‘\x00’ 停止
  • strcat,字符串拼接,遇到 ‘\x00’ 停止
  • bcopy

·确定填充长度

这一部分主要是计算我们开始写入的地址与我们所要覆盖的地址之间的距离。 一个常见的误区是 malloc 的参数等于实际分配堆块的大小,但是事实上 ptmalloc 分配出来的大小是对齐的。这个长度一般是字长的 2 倍,比如 32 位系统是 8 个字节,64 位系统是 16 个字节。但是对于不大于 2 倍字长的请求,malloc 会直接返回 2 倍字长的块也就是最小 chunk,比如 64 位系统执行malloc(0)会返回用户区域为 16 字节的块。

0x07 Fast bin

堆Heap块Chunk_第2张图片
在chunk被free的时候如果大小小于0x80会被放入fast bin
需要注意的是这里的prive_inuse位不会置0
可以在这里泄露出来heap的地址。

0x08 Unsort bin

堆Heap块Chunk_第3张图片
在chunk合并之后大于0x80或者是大于0x80的chunk被释放之后会放入unsort bin
这里是双向链表连接
其中main_arena和libc_base之间的偏移是固定的,可以用来leak libc_base

注意点

定义的read函数

  • 可以输入比size多一位。
  • 可以输入size之后又加上\x00

free掉chunk之后指针是否清零
是否存在堆溢出

Fastbin attack–blind

堆Heap块Chunk_第4张图片
注意在申请空间时有一个size验证。

malloc(20) malloc(20)
free(1) free(2)
edit(2,my_addr)
my_addr=0x6020xx
my_addr_size=0x70,1
size需要满足
0x80以内
prive_inuse为1

size一般是偏移的

FILE结构

struct _IO_FILE_plus
{
    _IO_FILE    file;
    IO_jump_t   *vtable;
}

puts函数最终调用的其实是这里的xsputn函数。伪造IO_FILE_plus结构把xsgetn设置为想要的值可以劫持执行流。

详细参考https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/

void * vtablt[] = {
   1 NULL, // "extra word"
   2 NULL, // DUMMY
   3 exit, // finish
   4 NULL, // overflow
   5 NULL, // underflow
   6 NULL, // uflow
   7 NULL, // pbackfail
   
   8 NULL, // xsputn  #printf
   9 NULL, // xsgetn

print *(struct _IO_FILE_plus*)0x6020a0

double free–pwn3_text

堆Heap块Chunk_第5张图片

所谓的free其实就是数据结构里面的头插。就是一些指针的变化
达到左图在经过三次malloc再次malloc就能申请到想要的空间
限制:需要一个合适的size

Unlink

堆Heap块Chunk_第6张图片
unlink的发生:

  1. glibc 判断这个块是 small chunk。
  2. 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并。
  3. 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并。
    继而对 nextchunk 采取 unlink 操作。

unlink(p)

unlink–babynode

填充:fd=p-12 bk=p-8 next_prevsize=size … chunk2_privesize=size chunk2size=size2 这里的size全部为chunk的实际大小
结果:*p=p-12
堆Heap块Chunk_第7张图片
堆Heap块Chunk_第8张图片

你可能感兴趣的:(工具)