81 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Heap Overflow
{{#include ../../banners/hacktricks-training.md}}
## 基本信息
堆溢出类似于 [**栈溢出**](../stack-overflow/index.html),但发生在堆中。基本上,这意味着在堆中保留了一些空间来存储数据,而 **存储的数据大于保留的空间。**
在栈溢出中,我们知道一些寄存器,如指令指针或栈帧,将从栈中恢复,并且可能会被滥用。在堆溢出的情况下,**默认情况下堆块中没有存储任何敏感信息**,可以被溢出。然而,它可能包含敏感信息或指针,因此这种漏洞的 **严重性** **取决于** **哪些数据可能被覆盖** 以及攻击者如何利用这一点。
> [!TIP]
> 为了找到溢出偏移量,您可以使用与 [**栈溢出**](../stack-overflow/index.html#finding-stack-overflows-offsets) 相同的模式。
### 栈溢出与堆溢出
在栈溢出中,触发漏洞时栈中将存在的排列和数据是相当可靠的。这是因为栈是线性的,总是增加在冲突的内存中,在 **程序运行的特定位置,栈内存通常存储类似类型的数据**,并且它具有一些特定的结构,末尾有一些指向每个函数使用的栈部分的指针。
然而,在堆溢出的情况下,使用的内存不是线性的,而是 **分配的块通常位于内存的不同位置**(而不是一个接一个),因为 **bins 和 zones** 按大小分隔分配,并且因为 **先前释放的内存在分配新块之前被使用**。因此,**很难知道将与易受堆溢出影响的对象发生冲突的对象**。因此,当发现堆溢出时,需要找到一种 **可靠的方法使所需对象在内存中紧挨着可以被溢出的对象**
用于此的一种技术是 **Heap Grooming**,例如在 [**这篇文章**](https://azeria-labs.com/grooming-the-ios-kernel-heap/) 中进行了说明。文章解释了当 iOS 内核中的一个区域没有足够的内存来存储内存块时,它通过一个内核页面进行扩展,并且该页面被分割成预期大小的块,这些块将按顺序使用(直到 iOS 版本 9.2,然后这些块以随机方式使用,以增加这些攻击的利用难度)。
因此,在发生堆溢出的前一篇文章中,为了强制溢出的对象与受害者对象发生冲突,多个 **`kallocs` 被多个线程强制执行,以确保所有空闲块都被填满,并且创建一个新页面**。
为了强制用特定大小的对象填充,**与 iOS mach 端口相关的离线分配**是一个理想的候选者。通过精确设置消息的大小,可以准确指定 `kalloc` 分配的大小,当相应的 mach 端口被销毁时,相应的分配将立即释放回 `kfree`
然后,这些占位符中的一些可以被 **释放**。**`kalloc.4096` 空闲列表以后进先出顺序释放元素**,这基本上意味着如果一些占位符被释放,而利用尝试在分配易受溢出影响的对象时分配多个受害者对象,则该对象很可能会被一个受害者对象跟随。
### 示例 libc
[**在此页面**](https://guyinatuxedo.github.io/27-edit_free_chunk/heap_consolidation_explanation/index.html) 可以找到一个基本的堆溢出仿真,展示了如何通过覆盖下一个块的 prev in use 位和 prev size 的位置来 **合并一个已使用的块**(使其认为是未使用的),然后 **再次分配它**,能够覆盖在不同指针中使用的数据。
另一个来自 [**protostar heap 0**](https://guyinatuxedo.github.io/24-heap_overflow/protostar_heap0/index.html) 的示例展示了一个非常基本的 CTF 示例,其中 **堆溢出** 可以被滥用以调用赢家函数以 **获取标志**
在 [**protostar heap 1**](https://guyinatuxedo.github.io/24-heap_overflow/protostar_heap1/index.html) 示例中,可以看到如何通过滥用缓冲区溢出来 **在一个临近块中覆盖一个地址**,该地址将 **写入用户的任意数据**
### 示例 ARM64
在页面 [https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/](https://8ksec.io/arm64-reversing-and-exploitation-part-1-arm-instruction-set-simple-heap-overflow/) 中,您可以找到一个堆溢出示例,其中要执行的命令存储在溢出块的下一个块中。因此,可以通过用简单的利用覆盖它来修改执行的命令,例如:
```bash
python3 -c 'print("/"*0x400+"/bin/ls\x00")' > hax.txt
```
### 其他示例
- [**Auth-or-out. Hack The Box**](https://7rocky.github.io/en/ctf/htb-challenges/pwn/auth-or-out/)
- 我们利用整数溢出漏洞来获取堆溢出。
- 我们破坏指向溢出块内 `struct` 中函数的指针,以设置如 `system` 的函数并获得代码执行。
### 真实世界示例CVE-2025-40597 错误使用 `__sprintf_chk`
在 SonicWall SMA100 固件 10.2.1.15 中,反向代理模块 `mod_httprp.so` 分配了一个 **0x80-byte** 的堆块,然后使用 `__sprintf_chk` 将多个字符串连接到其中:
```c
char *buf = calloc(0x80, 1);
/* … */
__sprintf_chk(buf, /* destination (0x80-byte chunk) */
-1, /* <-- size argument !!! */
0, /* flags */
"%s%s%s%s", /* format */
"/", "https://", path, host);
```
`__sprintf_chk`**_FORTIFY_SOURCE** 的一部分。当它接收到一个 **正**`size` 参数时,它会验证结果字符串是否适合目标缓冲区。通过传递 **`-1` (0xFFFFFFFFFFFFFFFF)**,开发人员实际上 **禁用了边界检查**,将强化调用转回经典的不安全 `sprintf`
因此,提供一个过长的 **`Host:`** 头部允许攻击者 **溢出 0x80 字节的块并覆盖后续堆块的元数据**(根据分配器的不同,可能是 tcache / fast-bin / small-bin。可以通过以下方式重现崩溃
```python
import requests, warnings
warnings.filterwarnings('ignore')
requests.get(
'https://TARGET/__api__/',
headers={'Host': 'A'*750},
verify=False
)
```
实际利用需要**堆整理**以将可控对象放置在易受攻击的块之后,但根本原因强调了两个重要的要点:
1. **_FORTIFY_SOURCE 不是灵丹妙药** 错误使用可能会使保护失效。
2. 始终将**正确的缓冲区大小**传递给`_chk`系列(或者,更好的是,使用`snprintf`)。
## 参考文献
* [watchTowr Labs Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)](https://labs.watchtowr.com/stack-overflows-heap-overflows-and-existential-dread-sonicwall-sma100-cve-2025-40596-cve-2025-40597-and-cve-2025-40598/)
{{#include ../../banners/hacktricks-training.md}}