**TCP/IP 劫持(hijacking)**是一種用偽造封包接管受害者與主機之間連線的巧妙技術。

為什麼劫持特別有用#

這技術在受害者使用一次性密碼(one-time password)連線時尤其有用。一次性密碼只能認證一次,所以嗅探認證對攻擊者毫無用處——但劫持繞過了認證本身,直接接管已建立的連線。

執行劫持,攻擊者必須與受害者在同一網路上:

  1. 嗅探本地網段,從標頭取得所有開放 TCP 連線的細節。
  2. 每個 TCP 封包標頭都含序號(隨每個送出封包遞增,確保順序)。嗅探時,攻擊者能取得受害者(系統 A)與主機(系統 B)之間連線的序號。
  3. 攻擊者從受害者的 IP 位址送出偽造封包給主機,用嗅到的序號提供正確的確認號。

主機收到帶正確確認號的偽造封包,沒有理由相信它不是來自受害機器。

圖 0x400-10:攻擊者用嗅到的序號偽造封包,劫持受害者與主機之間的 TCP 連線

RST 劫持#

最簡單的一種形式:注入一個看似合法的重置(RST)封包

若來源被偽造、確認號正確,接收端就會相信來源真的送了重置封包,連線隨即被重置。

rst_hijack.c 用 libpcap 嗅探、用 libnet 注入 RST,只需看送往目標 IP 的已建立 TCP 連線。

Berkeley Packet Filter(BPF)#

許多用 libpcap 的程式不需看每個封包,所以 libpcap 提供告訴核心只送符合過濾條件封包的方法——這就是 BPF,很像一支程式。

過濾規則需先編譯才送進核心。以「已建立連線」為例,這類連線都設了 ACK 旗標:

TCP 旗標位於 TCP 標頭的第 13 個八位元組,由左到右為 URG、ACK、PSH、RST、SYN、FIN。ACK 開啟時第 13 個八位元組是二進位 00010000(十進位 16)。用位元 AND 忽略其他位元:tcp[13] & 16 == 16 就能匹配 ACK 開啟的封包。

以具名值改寫更易讀:

sudo tcpdump -nl "tcp[tcpflags] & tcp-ack != 0 and dst host 192.168.42.88"

rst_hijack.c 用相同規則過濾,收到封包後用其標頭資訊偽造 RST 封包:

// caught_packet 回呼中,偽造 RST:來源與目的對調,用嗅到的 ack 當序號
libnet_build_ip(..., *((u_long *)&(IPhdr->ip_dst)),  // 假裝我們是 dst
                     *((u_long *)&(IPhdr->ip_src)), ...); // 送回給 src
libnet_build_tcp(htons(TCPhdr->th_dport), htons(TCPhdr->th_sport),
                 htonl(TCPhdr->th_ack),   // 用先前的 ack 當序號
                 ..., TH_RST, ...);       // 只設 RST 旗標

持續劫持#

偽造封包不必是 RST——當偽造封包帶資料時更有趣:

  1. 主機收到偽造封包、遞增序號、回應受害者的 IP。
  2. 受害機器不知道偽造封包的存在,所以主機的回應對它而言序號不對,於是忽略。
  3. 受害者忽略了主機的回應,於是受害者的序號計數也偏掉——它之後送給主機的任何封包序號也會錯,主機也忽略。

此時連線兩端合法方的序號都不對,進入**去同步(desynchronized)**狀態。而攻擊者送出了引發這一切的第一個偽造封包,所以它能持續追蹤序號、繼續從受害者 IP 偽造封包給主機——讓攻擊者繼續與主機通訊,而受害者的連線卡死。