在網路安全領域,埠掃描是攻擊者常用的一種偵察技術。透過掃描目標主機的埠,攻擊者可以了解哪些服務正在執行,從而為進一步的攻擊做準備。作為防禦者,我們可以采取一些措施來混淆或欺騙埠掃描器,增加攻擊者的難度。本文將介紹如何利用eBPF和Rust來模擬開放埠,從而欺騙埠掃描器。
TCP三次握手與埠掃描
在深入技術細節之前,我們先回顧一下TCP三次握手的過程以及它與埠掃描的關系:
客戶端發送SYN包給伺服器
伺服器回復SYN-ACK包
客戶端發送ACK包給伺服器
這個過程對於埠掃描非常重要。當掃描器發送SYN包到目標埠時:
如果埠開放,會收到SYN-ACK響應
如果埠關閉,會收到RST-ACK響應
因此,透過發送SYN包並分析響應,掃描器可以判斷埠是否開放。這就是所謂的SYN掃描或半開放掃描技術。
使用eBPF和Rust模擬開放埠
我們的目標是編寫一個eBPF程式,攔截特定埠範圍內的SYN包,並模擬回復SYN-ACK,讓掃描器誤以為這些埠是開放的。這裏我們選擇9000-9500這個埠範圍作為範例。
設定eBPF程式
首先,我們需要設定eBPF程式來過濾出我們感興趣的封包:
fntry_syn_ack(ctx: XdpContext) -> Result<u32, ExecutionError> {
// 獲取乙太網路頭部
let eth_hdr: *mut EthHdr = get_mut_ptr_at(&ctx, 0)?;
// 檢查是否為IPv4封包
matchunsafe { (*eth_hdr).ether_type } {
EtherType::Ipv4 => {}
_ => returnOk(xdp_action::XDP_PASS),
}
// 獲取IP頭部
let ip_hdr: *mut Ipv4Hdr = get_mut_ptr_at(&ctx, EthHdr::LEN)?;
// 檢查是否為TCP封包
matchunsafe { (*ip_hdr).proto } {
IpProto::Tcp => {}
_ => returnOk(xdp_action::XDP_PASS),
}
// 獲取TCP頭部
let tcp_hdr: *mut TcpHdr = get_mut_ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN)?;
// ...後續程式碼
}
這段程式碼首先檢查封包是否為IPv4和TCP,如果不是則直接放行。
過濾目標埠
接下來,我們需要檢查TCP封包的目標埠是否在我們感興趣的範圍內:
// 檢查目標埠是否在9000-9500範圍內
let port = unsafe { u16::from_be((*tcp_hdr).dest) };
match port {
9000..=9500 => {}
_ => returnOk(xdp_action::XDP_PASS),
}
這裏我們將目標埠從網路字節序轉換為主機字節序,然後檢查是否在9000-9500範圍內。如果不在這個範圍,我們就放行封包。
辨識SYN包
我們只對SYN包感興趣,因為這是埠掃描的第一步:
// 檢查是否為SYN包
let is_syn_packet = unsafe {
match ((*tcp_hdr).syn() != 0, (*tcp_hdr).ack() == 0) {
(true, true) => true,
_ => false,
}
};
if !is_syn_packet {
returnOk(xdp_action::XDP_PASS);
}
這段程式碼檢查TCP頭部的SYN標誌是否設定,同時ACK標誌未設定。這正是SYN包的特征。
構造SYN-ACK響應
現在我們已經確認這是一個針對我們目標埠範圍的SYN包,接下來就要構造SYN-ACK響應:
// 交換乙太網路地址
unsafe { core::mem::swap(&mut (*eth_hdr).src_addr, &mut (*eth_hdr).dst_addr) }
// 交換IP地址
unsafe {
core::mem::swap(&mut (*ip_hdr).src_addr, &mut (*ip_hdr).dst_addr);
}
// 修改TCP頭部為SYN-ACK
unsafe {
core::mem::swap(&mut (*tcp_hdr).source, &mut (*tcp_hdr).dest);
(*tcp_hdr).set_ack(1);
(*tcp_hdr).ack_seq = (*tcp_hdr).seq.to_be() + 1;
(*tcp_hdr).seq = 1u32.to_be();
}
這段程式碼完成了以下操作:
交換乙太網路源地址和目標地址
交換IP源地址和目標地址
交換TCP源埠和目標埠
設定ACK標誌
設定確認號(ack_seq)為收到的序列號加1
設定我們的初始序列號為1
這些修改將入站的SYN包轉變為出站的SYN-ACK包。
發送修改後的封包
最後,我們需要將修改後的封包發送回去:
Ok(xdp_action::XDP_TX)
XDP_TX動作指示XDP框架將修改後的封包從同一網路介面發送出去。
完整的eBPF程式
將所有部份組合在一起,我們得到了完整的eBPF程式:
fntry_syn_ack(ctx: XdpContext) -> Result<u32, ExecutionError> {
let eth_hdr: *mut EthHdr = get_mut_ptr_at(&ctx, 0)?;
matchunsafe { (*eth_hdr).ether_type } {
EtherType::Ipv4 => {}
_ => returnOk(xdp_action::XDP_PASS),
}
let ip_hdr: *mut Ipv4Hdr = get_mut_ptr_at(&ctx, EthHdr::LEN)?;
matchunsafe { (*ip_hdr).proto } {
IpProto::Tcp => {}
_ => returnOk(xdp_action::XDP_PASS),
}
let tcp_hdr: *mut TcpHdr = get_mut_ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN)?;
let port = unsafe { u16::from_be((*tcp_hdr).dest) };
match port {
9000..=9500 => {}
_ => returnOk(xdp_action::XDP_PASS),
}
let is_syn_packet = unsafe {
match ((*tcp_hdr).syn() != 0, (*tcp_hdr).ack() == 0) {
(true, true) => true,
_ => false,
}
};
if !is_syn_packet {
returnOk(xdp_action::XDP_PASS);
}
unsafe {
core::mem::swap(&mut (*eth_hdr).src_addr, &mut (*eth_hdr).dst_addr);
core::mem::swap(&mut (*ip_hdr).src_addr, &mut (*ip_hdr).dst_addr);
core::mem::swap(&mut (*tcp_hdr).source, &mut (*tcp_hdr).dest);
(*tcp_hdr).set_ack(1);
(*tcp_hdr).ack_seq = (*tcp_hdr).seq.to_be() + 1;
(*tcp_hdr).seq = 1u32.to_be();
}
Ok(xdp_action::XDP_TX)
}
這個程式會攔截所有發往9000-9500埠範圍的SYN包,並模擬回復SYN-ACK,讓埠掃描器誤以為這些埠是開放的。
實際執行效果
讓我們來看看這個程式的實際執行效果。首先,我們需要編譯並載入這個eBPF程式:
$ RUST_LOG=info cargo xtask run -- -i wlp5s0
[2024-06-29T19:57:33Z INFO syn_ack] Waiting for Ctrl-C...
這裏我們假設
wlp5s0
是你的網路介面名稱。
現在,讓我們用nmap進行埠掃描:
$ sudo nmap -sS -p9000-9500 192.168.2.107
Starting Nmap 7.95 ( https://nmap.org ) at 2024-06-29 15:57 EDT
Nmap scan report for dlm (192.168.2.107)
Host is up (0.0084s latency).
PORT STATE SERVICE
9000/tcp open cslistener
9001/tcp open tor-orport
9002/tcp open dynamid
9003/tcp open unknown
...
9498/tcp open unknown
9499/tcp open unknown
9500/tcp open ismserver
我們可以看到,nmap報告9000-9500範圍內的所有埠都是開放的。這正是我們的eBPF程式的效果 - 它成功地欺騙了埠掃描器,讓掃描器誤以為這些埠都是開放的。
深入理解與擴充套件
雖然這個範例展示了如何使用eBPF和Rust來欺騙埠掃描器,但在實際套用中還有一些需要考慮的因素:
校驗和計算:在我們的範例中,我們修改了封包但沒有重新計算校驗和。在生產環境中,這可能會導致一些網路棧丟棄這些封包。一個更完善的實作應該包括校驗和的重新計算。
更復雜的掃描技術:我們的程式只處理了簡單的SYN掃描。實際上,還存在其他更復雜的掃描技術,如FIN掃描、NULL掃描等。一個更全面的解決方案應該能夠處理各種型別的掃描。
動態埠管理:我們寫死了9000-9500這個埠範圍。在實際套用中,你可能需要一個更靈活的方式來管理要模擬的埠。這可以透過eBPF maps來實作,允許使用者空間程式動態地更新埠列表。
日誌和監控:在生產環境中,你可能想要記錄掃描嘗試。這可以透過eBPF maps和使用者空間程式的配合來實作。
效能考慮:雖然eBPF程式執行得很快,但在高流量的環境中,處理每個封包可能會帶來一些效能開銷。你可能需要進行效能測試和最佳化。
安全考慮:模擬開放埠可能會吸引更多的攻擊嘗試。你需要確保你的系統有足夠的安全措施來處理這些額外的關註。
結論
透過使用eBPF和Rust,我們成功地實作了一個能夠欺騙埠掃描器的程式。這個範例展示了eBPF在網路安全領域的強大潛力。它允許我們在內核級別操作網路流量,實作高效的包處理和修改。
這種技術不僅可以用於欺騙埠掃描器,還可以套用於更廣泛的網路安全和管理任務,如負載均衡、防火墻、入侵檢測系統等。透過深入理解TCP/IP協定棧和利用eBPF的靈活性,我們可以開發出更加強大和創新的網路工具。
然而,需要註意的是,這種技術應該謹慎使用。雖然它可以增加攻擊者的難度,但也可能帶來意想不到的副作用。在實際部署之前,需要進行充分的測試和風險評估。
總的來說,eBPF和Rust的結合為網路安全和管理提供了一個強大的工具集。透過不斷學習和實踐,我們可以充分利用這些技術來提高系統的安全性和效能。希望這篇文章能夠激發你對eBPF和網路安全的興趣,鼓勵你進行更深入的探索和實踐。
文章精選
點 擊 關 註 並 掃 碼 添 加 進 交 流 群
領
取
「Rust
語
言
」
學
習
資
料