『CTF』堆入门(二)

admin 2024年1月20日10:58:59评论16 views字数 3072阅读10分14秒阅读模式
『CTF』堆入门(二)

点击蓝字 关注我们

日期: 2024-01-19

作者: Mr-hello

介绍: 艰难的堆学习之路。

0x00 前言

堆学习第二篇文章了,也是比较难的一部分,因为堆和栈不同,栈的结构较为简单,并且栈结构的溢出可以将返回地址覆盖,并轻易控制程序走向,但是堆块涉及到很多的结构体,以及各种函数等,堆上不会存在返回地址等可以让攻击者直接控制执行流程的数据,所以针对于堆溢出,我们一般不可能直接控制到 EIP 。针对于堆溢出的策略来说,我们一般是需要利用堆中的机制(如 unlink 等)来实现任意地址写入或者控制堆块中的内容等,这就需要我们深入了解 glibc 中的某些函数的源码,所以和栈溢出相比较,堆溢出难度比较大。

『CTF』堆入门(二)

0x01 堆溢出

堆溢出指的是,向程序里的某个堆块中写入的字节数超过了堆块本身的可利用字节数,导致出现数据溢出问题,覆盖到物理相邻的高地址的下一个堆块。一般我们利用堆溢出的流程如下:

  • 覆盖物理相邻的下个 chunk 内容

  • 利用堆中的机制来实现任意地址写入或者控制堆块中的内容等效果,从而控制程序的执行

堆申请函数

最常见的是使用 malloc 函数进行申请堆块,有些情况下会使用 calloc 分配,其中的区别为,calloc 在分配之后会自动进行清空。而 malloc 函数并不会自动清空。

还有部分情况下,会使用 realloc 函数进行分配,该函数主要出现在,堆块已经存在,但需要重新对堆块大小进行调整时,也就是说,realloc 函数,会先 free 之前的堆块,然后对其再分配。

realloc 函数使用时,存在以下几种情况。

1.申请 size > 原本 size
  • 如果 chunktop chunk 相邻,直接利用 top chunk 拓展这个 chunk 到新 size

  • 如果 chunktop chunk 不相邻,相当于 free + malloc

2.申请 size  < 原本 size
  • size 相差不足以容纳最小 chunk64位系统32字节,32位系统16字节),保持不变

  • 可以容纳最小 chunk,对原堆块进行切割,free 掉后一部分

3.申请 size  = 原本 size,不进行任何操作

4.申请 size  = 0,相当于 free

危险函数

1.输入

  • gets

  • scanf

  • vscanf

2.输出

  • sprintf

3.字符串

  • strcpy

  • strcat

  • bcopy

重点:其实堆溢出和栈溢出最重要的一个点,就是要确定填充长度,也就是输入多少字符可以实现溢出,在堆的数据结构处,已经提到,每个堆块在申请的时候,其实是和系统的机器字长有关系的,每次堆块的申请,并不是根据用户输入来决定的,而是由 ptmalloc 函数分配出来的大小决定的,该大小是对齐的,一般是机器字长的 2 倍。

用户区域大小和 chunk_head.size 不同,chunk_head.size = user_data_size + 2 * 字节长度

另外一点

之前文章讲过一次堆块空间复用的问题,也就是说,prev_size 只有在前面堆块空闲时,才会记录大小,但是当前面堆块不是空闲状态时,prev_size0,所以当一个堆块被申请时,可能会出现,用户申请24字节的用户空间,但系统只给分配16字节,因为系统会把下个堆块的  prev_size 分配给该堆块使用。

『CTF』堆入门(二)

我这里申请了一个 24 字节的堆块,但是,系统只给分配了,16 字节,剩余的 8 个字节,可以复用下一个堆块的 prev_size 字段。

系统分配内存是以最小单元为基本单位,用于做内存对齐,32位系统最小分配空间的单元大小为 16字节 ,即分配的空间肯定是 16字节 的整数倍,64位系统最小分配空间的单元大小为 32字节 ,即分配的空间肯定是 32字节 的整数倍。

0x02 unlink

看完了堆溢出之后,本来想接着看 Off-By-One ,但是发现里面提到一个知识点,unlink 于是又转过头去研究了一下这方面内容,发现其基本原理就是双向循环链表的删除元素操作。只不过这个元素是一个 chunk。并且涉及到堆块的分配、释放等。

前文在讲 bins 数据结构时提到,只有small bin、large bin 才会存在双向循环列表,small bin 中,每个下标头部之间的 chunk 不会再次形成循环双向列表,所以放入 small bin 中的空闲堆块,不会涉及到 fd_nextsize、bk_nextsize的改变。在网上找到一张较为详细的图示。

『CTF』堆入门(二)

在较早版本的 glibc 中,unlink 未对 FD->bk、BK->fd 进行限制,导致该两处指针可控,从而造成任意地址读写,在 glibc2.26 之前的版本都是存在 unlink 漏洞。

//较早期glibc中的 unlink函数void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD){    FD = P->fd;    BK = P->bk;    //这里如果FD->bk和BK->fd不做限制的话,我们可以通过输入,溢出覆盖P->fd和P->bk,从而达到控制任意位置写入。    FD->bk = BK;    BK->fd = FD;}

在较新版本的 glibc 中可以看到已经对 FD->bkBK->fd 做了判断。

/* Take a chunk off a bin list.  */static voidunlink_chunk (mstate av, mchunkptr p){  if (chunksize (p) != prev_size (next_chunk (p)))    malloc_printerr ("corrupted size vs. prev_size");  mchunkptr fd = p->fd;  mchunkptr bk = p->bk;  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))    malloc_printerr ("corrupted double-linked list");  fd->bk = bk;  bk->fd = fd;  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)    {      if (p->fd_nextsize->bk_nextsize != p   || p->bk_nextsize->fd_nextsize != p) malloc_printerr ("corrupted double-linked list (not small)");      if (fd->fd_nextsize == NULL) {   if (p->fd_nextsize == p)     fd->fd_nextsize = fd->bk_nextsize = fd;   else     {       fd->fd_nextsize = p->fd_nextsize;       fd->bk_nextsize = p->bk_nextsize;       p->fd_nextsize->bk_nextsize = fd;       p->bk_nextsize->fd_nextsize = fd;     } }      else {   p->fd_nextsize->bk_nextsize = p->bk_nextsize;   p->bk_nextsize->fd_nextsize = p->fd_nextsize; }    }}

0x03 结尾

先写到这里吧,堆知识越到后面越难,作为 CTF 大龄选手,只好慢慢啃这块硬骨头,实在太难理解了,后悔自己大学的时候,没好好学习数据结构,理解指针太费事了,现在想想,当时学数据结构的时候,已经是八九年前了。。。

『CTF』堆入门(二)

往期回顾

文章丨『CTF』堆入门

文章丨『CTF』pwnable 学习笔记

文章丨『CTF』模 p 非奇异矩阵的群阶的计算

免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。

点此亲启

原文始发于微信公众号(宸极实验室):『CTF』堆入门(二)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月20日10:58:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『CTF』堆入门(二)https://cn-sec.com/archives/2411583.html

发表评论

匿名网友 填写信息