堆的初学笔记1

理解堆(源于ctfwiki:堆概述 - CTF Wiki (ctf-wiki.org)

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

堆管理器

寄存内存的地方,于操作系统交互,响应用户的申请内存与接受用户释放的内存

ps:需要注意的是,在内存分配与使用的过程中,Linux 有这样的一个基本内存管理思想,只有当真正访问一个地址的时候,系统才会建立虚拟页面与物理页面的映射关系。 所以虽然操作系统已经给程序分配了很大的一块内存,但是这块内存其实只是虚拟内存。只有当用户使用到相应的内存时,系统才会真正分配物理页面给用户使用。

函数

(s)brk,可以通过增加brk的大小来向操作系统申请内存

初始时,堆的起始地址start_brk以及堆当前末尾brk指向同一地址。

不开启ASLR保护,start_brk以及brk会指向data/bss段的结尾

开启后,start_brk与brk也会指向同一位置,只是这个位置在data/bss段结尾后的随机偏移处。
image
在主线程释放内存后,我们从下面的输出可以看出,其对应的 arena 并没有进行回收,而是交由 glibc 来进行管理。当后面程序再次申请内存时,在 glibc 中管理的内存充足的情况下,glibc 就会根据堆分配的算法来给程序分配相应的内存。

堆相关数据结构

宏观结构与微观结构

宏观

我们称malloc申请的内存为chunk,这块内存在ptmalloc内部用malloc_chunk结构体表示,当chunk被free后,会被加入到相应的空闲管理列表之中。

无论一个 chunk 的大小如何,处于分配状态还是释放状态,它们都使用一个统一的结构。虽然它们使用了同一个数据结构,但是根据是否被释放,它们的表现形式会有所不同。

前面一堆基础概念,看也看不懂,现在也不想看,先看看堆溢出怎么操作,需要什么知识

堆溢出

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

基本前提

向堆上写数据

写入数据大小可以溢出

对于我们来说,目的是控制程序执行流程,得到shell

与栈溢出相区别的是堆上不存在返回地址等可以让攻击者直接控制执行流程的数据,因此一般无法直接通过堆溢出控制EIP

策略
image

重要步骤

1、寻找堆分配函数

通常是调用glibc中的malloc进行分配,但某些情况下会使用calloc分配。两者的基本区别在于calloc在分配后会自动进行清空。还有一种是realloc,可身兼malloc与free两者的功能
image

2、寻找危险函数

输入输出字符串(和栈溢出没啥太大区别,都在于找到溢出点等可操作点)

确定填充长度

常见误区:一个常见的误区是 malloc 的参数等于实际分配堆块的大小,但是事实上 ptmalloc 分配出来的大小是对齐的。这个长度一般是字长的 2 倍,比如 32 位系统是 8 个字节,64 位系统是 16 个字节。但是对于不大于 2 倍字长的请求,malloc 会直接返回 2 倍字长的块也就是最小 chunk,比如 64 位系统执行malloc(0)会返回用户区域为 16 字节的块。

注意用户区域的大小不等于 chunk_head.size,chunk_head.size = 用户区域大小 + 2 * 字长
image

不懂的看上面这个,就可以理解了

永远双字节对齐

但是还不是很理解如何控制程序流

off-by-one

指一种特殊的溢出漏洞,写入的字节数超过了这个缓冲区本身所申请的字节数并且只多了一个字节

一般来说,单字节溢出被认为是难以利用的,但是因为 Linux 的堆管理机制 ptmalloc 验证的松散性,基于 Linux 堆的 off-by-one 漏洞利用起来并不复杂,并且威力强大。 此外,需要说明的一点是 off-by-one 是可以基于各种缓冲区的,比如栈、bss 段等等,但是堆上(heap based) 的 off-by-one 是 CTF 中比较常见的。我们这里仅讨论堆上的 off-by-one 情况。

(细嗦ptmalloc验证的松散性)

利用思路

1、溢出字节可控制任意字节:通过修改大小造成块结构出现重叠(原理,但没看懂什么是块结构出现重叠,以及为什么会出现),从而泄露其他块数据或是覆盖其他块数据(目的)。也可使用NULL字节溢出的方法(什么是NULL字节溢出?)

2、溢出字节为NULL字节:在size为0x100时,溢出NULL字节可以使得prev_in_use(是什么东西?)被清,这样前块会被认为是free块(什么意思?被free吗)

​ (1)可以使用unlink方法(不懂,后有细嗦)进行处理

​ (2)另外,这时候prev_size域就会启用,就可以伪造prev_size(什么东西),从而造成块重叠。此方法的关键在于unlink的时候没有检查按照prev_size找到的块的大小域prev_size是否一致

常见off-by-one问题

栅栏错误,循环边界未控制好,导致写入多执行一次

字符串操作,不同函数之间不同逻辑计算导致漏洞,例如strlen与strcpy,strlen在计算时是不把结束符\x00计算在内的,而strcpy在拷贝的时候是会把结束符\x00拷贝进去的,导致写入字节多一个
image
unlink
image
image
伪造
image

利用
image

UAF

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

fastbin attach

fastbin double free
image

house of spirit

核心在于在目标位置伪造fastbin chunk,并且释放,从而达到分配指定地址chunk的目的。

posted @ 2025-07-26 20:40  cauit  阅读(16)  评论(0)    收藏  举报