**連接埠掃描(port scanning)**是找出哪些連接埠正在監聽並接受連線的方法。由於多數服務跑在標準、有文件記載的連接埠上,這資訊可用來判斷正在執行哪些服務。

最簡單的掃描是對目標每個可能的連接埠嘗試開 TCP 連線。這雖有效,卻吵雜且易被偵測——連線建立時服務通常會記錄 IP 位址。為避免這點,人們發明了幾種巧妙技術。

Fyodor 寫的 nmap 實作了以下所有掃描技術,已成為最受歡迎的開源連接埠掃描工具之一。

隱蔽 SYN 掃描#

SYN 掃描又稱半開掃描(half-open scan),因為它不真的開完整的 TCP 連線:

回想三向交握。SYN 掃描只送初始 SYN 封包,然後檢視回應:

  • 收到 SYN/ACK → 該埠正接受連線,記錄下來,並送 RST 拆掉連線(避免不小心 DoS 服務)。
  • 沒收到 → 該埠沒在監聽。

nmap 用 -sS 執行 SYN 掃描(需 root,因為它用原始網路存取而非標準 socket)。

FIN、X-mas、Null 掃描#

為對抗 SYN 掃描,出現了偵測半開連線的工具,於是又演化出更隱蔽的一組技術。它們都送一個無意義的封包到目標每個埠:

  • FIN 掃描:送 FIN 封包。
  • X-mas 掃描:送 FIN、URG、PUSH 都開的封包(旗標亮得像聖誕樹,故名)。
  • Null 掃描:送不設任何 TCP 旗標的封包。

若埠在監聽,這些封包會被忽略;若埠關閉且實作遵循 RFC 793,就會回 RST。這個差異能判斷哪些埠接受連線、又不真的開任何連線。

但這些掃描也可能不可靠:例如微軟的 TCP 實作不按規矩送 RST,使這類掃描失效。(nmap 選項分別為 -sF-sX-sN。)

隱藏於誘餌之中#

另一種避偵測法是藏在多個誘餌(decoy)之間:在每個真正的掃描連線之間,偽造來自各個誘餌 IP 的連線。偽造連線的回應不需要(它們只是誤導),但誘餌位址必須用活躍主機的真實 IP,否則可能不小心把目標 SYN flood 了。(nmap 選項 -D。)

閒置掃描#

**閒置掃描(idle scanning)**利用一個閒置主機的偽造封包來掃描目標,靠觀察閒置主機的變化推斷結果。

攻擊者需要一個可用的閒置主機:沒有其他網路流量,且 TCP 實作產生可預測的 IP ID(每個封包以已知增量遞增)。可預測的 IP ID 從未被真正視為安全風險,閒置掃描正利用這個誤解。較新的作業系統(近期 Linux 核心、OpenBSD、Windows Vista)會隨機化 IP ID,但舊系統與硬體(如印表機)通常不會。

步驟:

  1. 先用 SYN 或非請求的 SYN/ACK 封包接觸閒置主機、觀察回應的 IP ID,重複幾次以確定每封包的遞增量。
  2. 攻擊者以閒置主機的 IP 送偽造 SYN 給目標某埠:
    • 埠在監聽 → 目標回 SYN/ACK 給閒置主機;對閒置主機而言這是非請求的,於是它回一個 RST(IP ID +1)。
    • 埠沒在監聽 → 目標不回,閒置主機也不回。
  3. 再次接觸閒置主機看 IP ID 遞增了多少:只遞增一個間隔 → 埠關閉;遞增兩個間隔(閒置主機送了 RST)→ 埠開放。

圖 0x400-11:閒置掃描——目標埠開放與關閉時,閒置主機 IP ID 的不同變化

若閒置主機不是真的閒置,結果會偏差;輕微流量可對每個埠送多個封包來抵消。用得當時,攻擊者能掃描任何目標而從不洩露自己的 IP 位址。(nmap 選項 -sI。)

主動式防禦:shroud#

連接埠掃描常用於攻擊前刺探系統。許多 IDS 能偵測掃描,但那時資訊已經洩露。駭客的本質是想出新點子——這裡呈現一個新開發的、在掃描發生前就先防禦的方法。

先擋掉 FIN / Null / X-mas#

這三種掃描靠 RST 封包,只要核心永不送 RST它們就一無所獲。在核心的 tcp_v4_send_reset() 函式開頭加一行 return;,重編譯後核心就不再送 RST,避免資訊洩露。

用 SYN/ACK 洪水淹沒掃描器#

SYN 掃描與全連線掃描較難防:為維持功能,開放埠必須回 SYN/ACK。

但若所有關閉的埠也都回 SYN/ACK,攻擊者能從掃描取得的有用資訊就被降到最低。直接開啟每個埠會嚴重拖累效能,所以理想上這一切應不透過 TCP 堆疊完成。

shroud.c 正是這麼做——它修改自 rst_hijack.c,用更複雜的 BPF 只過濾送往關閉埠的 SYN 封包,回呼函式對每個通過的 SYN 偽造一個看似合法的 SYN/ACK:

// BPF:目標 IP 的 SYN(非 ACK),排除既有的開放埠
"dst host <ip> and tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack = 0 and not (dst port 22 or dst port 80)"

// 回呼:偽造 SYN/ACK
libnet_build_tcp(..., htonl((TCPhdr->th_seq) + 1),  // ack = SYN 的 seq + 1
                 TH_SYN | TH_ACK, ...);

執行後,任何掃描都會看到每個埠都開放

reader@hacking:~/booksrc $ sudo ./shroud 192.168.42.72 22 80

真正在跑的只有埠 22 的 ssh,卻藏在一片誤報的汪洋中。專注的攻擊者仍可 telnet 到每個埠檢查橫幅(banner),但這技術也能輕易擴充成偽造橫幅。