當前位置: 妍妍網 > 碼農

使用 eBPF 和 Rust 模擬開放埠,實作欺騙埠掃描器

2024-08-19碼農

在網路安全領域,埠掃描是攻擊者常用的一種偵察技術。透過掃描目標主機的埠,攻擊者可以了解哪些服務正在執行,從而為進一步的攻擊做準備。作為防禦者,我們可以采取一些措施來混淆或欺騙埠掃描器,增加攻擊者的難度。本文將介紹如何利用eBPF和Rust來模擬開放埠,從而欺騙埠掃描器。

TCP三次握手與埠掃描

在深入技術細節之前,我們先回顧一下TCP三次握手的過程以及它與埠掃描的關系:

  1. 客戶端發送SYN包給伺服器

  2. 伺服器回復SYN-ACK包

  3. 客戶端發送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) {
    (truetrue) => 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();
    }

    這段程式碼完成了以下操作:

    1. 交換乙太網路源地址和目標地址

    2. 交換IP源地址和目標地址

    3. 交換TCP源埠和目標埠

    4. 設定ACK標誌

    5. 設定確認號(ack_seq)為收到的序列號加1

    6. 設定我們的初始序列號為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) {
    (truetrue) => 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來欺騙埠掃描器,但在實際套用中還有一些需要考慮的因素:

    1. 校驗和計算:在我們的範例中,我們修改了封包但沒有重新計算校驗和。在生產環境中,這可能會導致一些網路棧丟棄這些封包。一個更完善的實作應該包括校驗和的重新計算。

    2. 更復雜的掃描技術:我們的程式只處理了簡單的SYN掃描。實際上,還存在其他更復雜的掃描技術,如FIN掃描、NULL掃描等。一個更全面的解決方案應該能夠處理各種型別的掃描。

    3. 動態埠管理:我們寫死了9000-9500這個埠範圍。在實際套用中,你可能需要一個更靈活的方式來管理要模擬的埠。這可以透過eBPF maps來實作,允許使用者空間程式動態地更新埠列表。

    4. 日誌和監控:在生產環境中,你可能想要記錄掃描嘗試。這可以透過eBPF maps和使用者空間程式的配合來實作。

    5. 效能考慮:雖然eBPF程式執行得很快,但在高流量的環境中,處理每個封包可能會帶來一些效能開銷。你可能需要進行效能測試和最佳化。

    6. 安全考慮:模擬開放埠可能會吸引更多的攻擊嘗試。你需要確保你的系統有足夠的安全措施來處理這些額外的關註。

    結論

    透過使用eBPF和Rust,我們成功地實作了一個能夠欺騙埠掃描器的程式。這個範例展示了eBPF在網路安全領域的強大潛力。它允許我們在內核級別操作網路流量,實作高效的包處理和修改。

    這種技術不僅可以用於欺騙埠掃描器,還可以套用於更廣泛的網路安全和管理任務,如負載均衡、防火墻、入侵檢測系統等。透過深入理解TCP/IP協定棧和利用eBPF的靈活性,我們可以開發出更加強大和創新的網路工具。

    然而,需要註意的是,這種技術應該謹慎使用。雖然它可以增加攻擊者的難度,但也可能帶來意想不到的副作用。在實際部署之前,需要進行充分的測試和風險評估。

    總的來說,eBPF和Rust的結合為網路安全和管理提供了一個強大的工具集。透過不斷學習和實踐,我們可以充分利用這些技術來提高系統的安全性和效能。希望這篇文章能夠激發你對eBPF和網路安全的興趣,鼓勵你進行更深入的探索和實踐。

    文章精選

    「Rust