#nmap #端口扫描 #网络安全 #安全 #渗透测试

pistol

关于网络安全的 Rust 库

22 个版本 (14 个稳定版)

2.0.4 2024年7月24日
2.0.1 2024年7月22日
1.1.0 2024年7月20日
1.0.7 2024年6月21日
0.1.0 2023年6月10日

#126 in 网络编程

Download history 675/week @ 2024-06-01 72/week @ 2024-06-08 269/week @ 2024-06-15 50/week @ 2024-06-22 81/week @ 2024-06-29 215/week @ 2024-07-06 17/week @ 2024-07-13 663/week @ 2024-07-20 80/week @ 2024-07-27 1/week @ 2024-08-03

每月767次下载

MIT/Apache

2MB
17K SLoC

pistol-rs

库必须以 root(Linux、*BSD)或管理员(Windows)的身份运行,建议使用 rust 的稳定版。

从 crates.io 导入

[dependencies]
pistol = "^2"

在 Windows 上,从这里下载 winpcap 这里npcap 这里,然后将 x64 文件夹中的 Packet.lib 放置在您的代码根目录下(注意:根据 libpnet 的文档,libpnet 没有测试 npcap)。

跨平台支持

平台 注意
Linux 支持
Unix (*BSD, MacOS) 支持
Windows 支持(winpcap 或 npcap)

libpnet 在 Windows 上的错误

错误问题: https://github.com/libpnet/libpnet/issues/707,libpnet 无法在 Windows 上获取 IPv6 地址。

因此,直到 libpnet 修复此错误,Windows 上尚不支持 IPv6。

rust 夜间版本上的 libpnet 错误

错误问题: https://github.com/libpnet/libpnet/issues/686

主机发现(Ping 扫描)

`pistol` 主机发现的实现根据 nmap 文档

方法 详细文档 注意
[x] TCP SYN Ping nmap 参考 IPv4 & IPv6
[x] TCP ACK Ping nmap 参考 IPv4 & IPv6
[x] UDP Ping nmap 参考 IPv4 & IPv6
[x] ICMP Ping nmap 参考 IPv4 & IPv6 (ICMP, ICMPv6)
[x] ARP 扫描 nmap 参考 IPv4
[ ] IP 协议 Ping nmap 参考 复杂且不太有用

端口扫描技术和算法

`pistol` 端口扫描的实现根据 nmap pdf文档

方法 详细文档 注意
[x] TCP SYN 扫描 nmap 参考 IPv4 & IPv6
[x] TCP Connect() 扫描 nmap 参考 IPv4 & IPv6
[√] TCP FIN 扫描 nmap 参考 IPv4 & IPv6
[√] TCP Null 扫描 nmap 参考 IPv4 & IPv6
[√] TCP Xmas 扫描 nmap 参考 IPv4 & IPv6
[√] TCP ACK 扫描 nmap 参考 IPv4 & IPv6
[√] TCP 窗口扫描 nmap 参考 IPv4 & IPv6
[√] TCP Maimon 扫描 nmap 参考 IPv4 & IPv6
[√] UDP 扫描 nmap 参考 IPv4 & IPv6
[√] TCP 空闲扫描 nmap 参考 IPv4
[ ] IP 协议扫描 nmap 参考 复杂且不太有用
[ ] TCP FTP 反向扫描 nmap 参考 已修复被利用的漏洞

洪水攻击

方法 注意
[√] TCP SYN 洪水 IPv4 & IPv6 支持
[√] TCP ACK 洪水 IPv4 & IPv6 支持
[√] UDP 洪水 IPv4 & IPv6 支持
[√] ICMP 洪水 IPv4 & IPv6 支持 (ICMP, ICMPv6)

远程操作系统检测

方法 详细文档 注意
[√] IPv4 操作系统检测 nmap 参考 现在支持以 nmap 格式打印指纹
[√] IPv6 操作系统检测 nmap 参考 现在支持以 nmap 格式打印指纹

IPv6 上的操作系统检测?

在 ipv6 上,指纹对于人类来说难以阅读且没有意义,有关详细信息,请参阅此处,nmap 使用逻辑回归在 ipv6 上匹配目标操作系统,但匹配算法相当过时,设计逻辑复杂。

第一点是关于在ipv6检测中的指纹中的STRTEXTRA度量标准,这三个度量标准在代码中完全没有使用,同时没有详细说明如何计算STRT,我不知道为什么 nmap 会保留它们在最终的指纹中。

第二点是NI探测。在 nmap 的相关文档中,它描述了NI探测的具体结构,但我没有在代码中看到任何关于它的内容,并且它似乎在逻辑回归预测时完全忽略了这种探测。

此外,对于当前主流操作系统,ipv6 指纹支持不如 ipv4 丰富,因此请先尝试 ipv4。

服务和应用程序版本检测

方法 详细文档
[√] IPv4 服务扫描 nmap 参考
[√] IPv6 服务扫描 nmap 参考

调试

use pistol::Logger;

fn main() -> Result<()> {
    Logger::init_debug_logging()?;
    // Logger::init_warn_logging()?;
    // your code below
    ...
}

示例

0. 创建目标

现在您可以在创建扫描目标时包含 IPv4 和 IPv6 地址,并且 pistol 将自动调用相应的算法来处理。

但是请注意,某些算法只能与某些协议一起使用,例如,空闲扫描只能与 IPv4 一起使用,如果与 IPv6 一起使用,则不会执行任何操作,并显示警告消息。

use pistol::Target;
use pistol::Host;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;

fn main() -> Result<()> {
    let dst_ipv4 = Ipv4Addr::new(192, 168, 72, 134);
    let host1 = Host::new(dst_ipv4.into(), Some(vec![22, 99]));
    let dst_ipv6 = Ipv6Addr::new(0xfe80, 0, 0, 0, 0x020c, 0x29ff, 0xfeb6, 0x8d99);
    let host2 = Host::new(dst_ipv6.into(), Some(vec![443, 8080]));
    let target = Target::new(vec![host1, host2]);
    // your code below
    ...
}

注意

如果您不想使用 Target,您也可以使用我们提供的 _raw 函数,例如,tcp_syn_scan 的相应原始函数是 tcp_syn_scan_raw

1. SYN 端口扫描示例

use pistol::scan::tcp_syn_scan;
use pistol::Target;
use pistol::Host;
use std::net::Ipv4Addr;
use std::time::Duration;
use anyhow::Result;

fn main() -> Result<()> {
    // When using scanning, please use a real local address to get the return packet.
    // And for flood attacks, please consider using a fake address.
    // If the value here is None, the programme will automatically look up the available addresses from the existing interfaces on the device.
    let src_ipv4 = None;
    // If the value of `source port` is `None`, the program will generate the source port randomly.
    let src_port = None;
    // The destination address is required.
    let dst_ipv4 = Ipv4Addr::new(192, 168, 72, 134);
    let threads_num = 8;
    let timeout = Some(Duration::new(1, 0));
    // Test with an open port `22` and a closed port `99`.
    let host = Host::new(dst_ipv4.into(), Some(vec![22, 99]));
    // Users should build the `target` themselves.
    let target = Target::new(vec![host]);
    // Number of tests
    let tests = 4;
    let ret = tcp_syn_scan(
        target,
        src_ipv4,
        src_port,
        threads_num,
        timeout,
        tests
    ).unwrap();
    println!("{}", ret);
    Ok(())
}

或者

use pistol::scan::tcp_syn_scan_raw;
use std::net::Ipv4Addr;
use std::time::Duration;
use anyhow::Result;

fn main() -> Result<()> {
    let dst_ipv4 = Ipv4Addr::new(192, 168, 72, 134);
    let dst_port = 80;
    let src_ipv4 = None;
    let src_port = None;
    let timeout = Some(Duration::new(1, 0));
    let (ret, _rtt) =
        tcp_syn_ping_raw(dst_ipv4.into(), dst_port, src_ipv4, src_port, timeout)?;
    println!("{:?}", ret);
    Ok(())
}

输出

+----------------+------+-----------------------------+
|                    Scan Results                     |
+----------------+------+-----------------------------+
| 192.168.72.134 |  22  |     open|open|open|open     |
+----------------+------+-----------------------------+
| 192.168.72.134 |  99  | closed|closed|closed|closed |
+----------------+------+-----------------------------+
| avg rtt: 51.5ms                                     |
| open ports: 1                                       |
+----------------+------+-----------------------------+

2. 远程操作系统检测示例

测试目标服务器是 ubuntu 22.04 服务器。

use pistol::os::os_detect;
use pistol::Target;
use pistol::Host;
use std::net::Ipv4Addr;
use std::time::Duration;
use anyhow::Result;

fn main() -> Result<()> {
    // If the value of `src_ipv4` is `None`, the program will find it auto.
    let src_ipv4 = None;
    // If the value of `src_port` is `None`, the program will generate it randomly.
    let src_port = None;
    let dst_ipv4 = Ipv4Addr::new(192, 168, 72, 134);
    // `dst_open_tcp_port` must be a certain open tcp port.
    let dst_open_tcp_port = 22;
    // `dst_closed_tcp_port` must be a certain closed tcp port.
    let dst_closed_tcp_port = 8765;
    // `dst_closed_udp_port` must be a certain closed udp port.
    let dst_closed_udp_port = 9876;
    let host = Host::new(
        dst_ipv4.into(),
        Some(vec![
            dst_open_tcp_port,   // The order of these three ports cannot be disrupted.
            dst_closed_tcp_port,
            dst_closed_udp_port,
        ]),
    );
    let target = Target::new(vec![host]);
    let timeout = Some(Duration::new(3, 0));
    let top_k = 3;
    let threads_num = 8;

    // The `fingerprint` is the obtained fingerprint of the target OS.
    // Return the `top_k` best results (the number of os detect result may not equal to `top_k`), sorted by score.
    let ret = os_detect(
        target,
        src_ipv4,
        src_port,
        top_k,
        threads_num,
        timeout,
    )?;
    println!("{}", ret);
    Ok(())
}

输出

+----------------+------+--------+--------------------------------------------------------------------------------------------------------------+
|                                                               OS Detect Results                                                               |
+----------------+------+--------+--------------------------------------------------------------------------------------------------------------+
| 192.168.72.134 |  #1  | 82/101 | # Linux 4.15.0-88-generic #88~16.04.1-Ubuntu SMP Wed Feb 12 04:19:15 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux |
|                |      |        |                # Linux 4.19.0-9-amd64 #1 SMP Debian 4.19.118-2 (2020-04-29) x86_64 GNU/Linux                 |
|                |      |        | # Linux 5.0.0-32-generic #34~18.04.2-Ubuntu SMP Thu Oct 10 10:36:02 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux  |
|                |      |        |   # Linux 5.2.10-yocto-standard #1 SMP PREEMPT Fri Oct 4 11:58:01 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux    |
|                |      |        |                                          # Linux 5.3.0-kali3-amd64                                           |
|                |      |        |      # Linux 5.3.16-200.fc30.x86_64 #1 SMP Fri Dec 13 17:48:38 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux       |
|                |      |        |                # Linux 5.4.6-amd64.gbcm #3 SMP Thu Dec 26 13:55:41 -03 2019 x86_64 GNU/Linux                 |
|                |      |        |            # Linux 5.6.15-arch1-1 #1 SMP PREEMPT Wed, 27 May 2020 23:42:26 +0000 x86_64 GNU/Linux            |
|                |      |        |                                         # Linux 5.2.11-arch1-1-ARCH                                          |
|                |      |        |    # Linux 5.4.0-1012-raspi #12-Ubuntu SMP Wed May 27 04:08:35 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux    |
+----------------+------+--------+--------------------------------------------------------------------------------------------------------------+
| 192.168.72.134 |  #2  | 81/101 |     # Linux 5.0.0-23-generic #24-Ubuntu SMP Mon Jul 29 15:36:44 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux      |
|                |      |        |                                 # Linux 5.3.0-24-generic x86_64 Ubuntu 19.10                                 |
|                |      |        |              # Linux 5.3.9-sunxi (root@builder) (gcc version 7.4.1 20181213 [linaro-7.4-2019.02              |
+----------------+------+--------+--------------------------------------------------------------------------------------------------------------+
| 192.168.72.134 |  #3  | 80/101 |     # Linux 5.4.0-1008-raspi #8-Ubuntu SMP Wed Apr 8 11:13:06 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux     |
+----------------+------+--------+--------------------------------------------------------------------------------------------------------------+

3. IPv6 上的远程操作系统检测示例

测试目标服务器是 ubuntu 22.04 服务器。

use pistol::os::os_detect;
use pistol::Target;
use pistol::Host;
use std::net::Ipv4Addr;
use std::time::Duration;
use anyhow::Result;

fn main() -> Result<()> {
    let src_ipv6 = None;
    let dst_ipv6: Ipv6Addr = "fe80::20c:29ff:feb6:8d99".parse().unwrap();
    let dst_open_tcp_port = 22;
    let dst_closed_tcp_port = 8765;
    let dst_closed_udp_port = 9876;
    let host = Host::new(
        dst_ipv6.into(),
        Some(vec![
            dst_open_tcp_port,
            dst_closed_tcp_port,
            dst_closed_udp_port,
        ]),
    );

    let target = Target::new(vec![host]);
    let src_port = None;
    let timeout = Some(Duration::new(3, 0));
    let top_k = 3;
    let threads_num = 8;
    let ret = os_detect(target, src_ipv6, src_port, top_k, threads_num, timeout)?;
    println!("{}", ret);
    Ok(())
}

输出

+---------------- ---------+------+------+--------------------------+
|                        OS Detect Results                          |
+--------------------------+------+------+--------------------------+
| fe80::20c:29ff:feb6:8d99 |  #1  | 0.9  |        Linux 4.19        |
+--------------------------+------+------+--------------------------+
| fe80::20c:29ff:feb6:8d99 |  #2  | 0.7  |     Linux 3.13 - 4.6     |
+--------------------------+------+------+--------------------------+
| fe80::20c:29ff:feb6:8d99 |  #3  | 0.0  | Android 7.1 (Linux 3.18) |
+--------------------------+------+------+--------------------------+

根据 nmap文档,如果探测结果的意义不小于 15,则 novelty 值(表中的第三列)必须小于 15,因此当此值大于 15 时,返回空列表。同样,当两个最高 OS 类别的分数相差不到 10% 时,分类被认为是模糊的,并且不是成功的匹配。

3. 远程服务检测示例

  • 192.168.1.51 - Ubuntu 22.04 (ssh: 22, httpd: 80)
use pistol::vs::vs_scan;
use pistol::vs::ExcludePorts;
use pistol::Target;
use pistol::Host;
use std::net::Ipv4Addr;
use std::time::Duration;
use anyhow::Result;

fn main() -> Result<()> {
    let dst_addr = Ipv4Addr::new(192, 168, 1, 51);
    let host = Host::new(dst_addr.into(), Some(vec![22, 80]));
    let target = Target::new(vec![host]);
    let threads_num = 8;
    let timeout = Some(Duration::new(1, 0));
    // only_null_probe = true, only_tcp_recommended = any, only_udp_recomended = any: only try the NULL probe (for TCP)
    // only_tcp_recommended = true: only try the tcp probe recommended port
    // only_udp_recommended = true: only try the udp probe recommended port
    let (only_null_probe, only_tcp_recommended, only_udp_recomended) = (false, true, true);
    let exclude_ports = Some(ExcludePorts::new(vec![51, 52]));
    let intensity = 7; // nmap default
    let ret = vs_scan(
        target,
        only_null_probe,
        only_tcp_recommended,
        only_udp_recommended,
        exclude_ports,
        intensity,
        threads_num,
        timeout,
    )?;
    println!("{}", ret);
    Ok(())
}

输出

+--------------+--------+--------+
|      Service Scan Results      |
+--------------+--------+--------+
| 192.168.1.51 |   22   |  ssh   |
+--------------+--------+--------+
| 192.168.1.51 |   80   |  http  |
+--------------+--------+--------+

依赖关系

~7–17MB
~196K SLoC