X-SCAN:Rust从零实现一个命令行端口扫描工具

0. 成品预览

本文将基于Rust构建一个常见的网络工具,端口扫描器。

按照惯例,还是和之前实现的文本编辑器一样,我给这个工具起名为X-SCAN,它的功能很简单,通过命令行参数的方式对指定IP进行扫描,扫描结束之后返回该IP地址中处于开放状态的端口号,学完本文,你将自己实现一个如下效果的端口扫描工具(截图以CSDN平台的IP地址的扫描结果为例)

image-20240518085144512


1. 相关依赖

tokio = {version = "1.37.0",features = ["full"]}
bpaf = {version = "0.9.12",features = ["derive","bright-color"]}
ansi_term = "0.12.1"
prettytable-rs = "0.10.0"
  • Tokio :用于异步编程
  • bpaf :一个简化命令行实现的库
  • ansi_term:美化终端字符
  • prettytable-rs:将数据进行表格化打印

上面这些依赖都是在后续的代码中需要用到的,后面会在针对每一个依赖库进行简单的入门讲解,便于理解最终要实现的端口扫描工具。过多的还是建议去官方文档学习。


2. 基本实现原理

通过异步请求对目标IP的端口进行tcp链接扫描,一旦连接建立成功,将本次连接的端口号返回,以此类推,直到全部扫描结束,打印扫描的结果即可。

image-20240517212712200


3. 几个依赖库的快速入门

这小节会对上面列出来的几个依赖库进行简单的入门,为后续编码扫清障碍。

3.1 tokio

  • Tokio官网

tokio中,实现异步编程的两大核心

  • async
  • await

如果某个函数需要异步执行,可以通过async关键字实现,比如下面connect函数的定义

use mini_redis::Result;
use mini_redis::client::Client;
use tokio::net::ToSocketAddrs;pub async fn connect<T: ToSocketAddrs>(addr: T) -> Result<Client> {// ...
}
  • 这个异步函数的定义看起来像一个普通的同步函数,但实际上是以异步方式运行的。这意味着在代码编写时,异步函数的语法和结构与同步函数类似,使得编写异步代码更加直观和易于理解。

  • Rust 编译器会对异步函数进行转换和优化,以便在运行时能够以异步的方式执行。

  • 当异步函数内部遇到 .await 关键字时,它会暂时挂起当前操作,将控制权交还给线程,从而允许线程执行其他任务。

  • 当异步操作在后台进行时,线程并不会被阻塞,而是可以继续执行其他任务,从而提高程序的效率和并发性能。

async fn say_hi() {println!("Tokio");
}#[tokio::main]
async fn main() {let op = say_hi();println!("hello");op.await;
}
  • 使用#[tokio::main]宏将主函数标记为异步。
    • 运行时包含异步任务调度器,提供事件 I/O、计时器等。运行时不会自动启动,因此需要 main 函数启动它。
  • 对于异步函数,它的调用方式和普通的Rust函数类似,无需其他冗余操作;
  • 当异步函数被调用时,函数体不会立即执行,而是会返回一个表示操作的值,类似于返回一个尚未执行的操作描述标识;
  • 这个概念类似于返回一个零参数的闭包,闭包本身不会立即执行,而是等待进一步的操作;
  • 要执行异步函数代表的操作,这就需要用到了另外一个关键字:await,它作用在操作返回值上,用来触发异步操作;

依据上面的描述,示例代码会打印:

hello Tokio

3.2 bpaf

这是一个多功能且易用的命令行参数解析工具。通过借助这个lib可以快速高效的编写命令行程序,由于我们的端口扫描器需要手动通过命令行输入IP和端口范围等参数,因此这无疑是一个不错的选择。

  • 仓库地址

  • 基本用法

// 导入 bpaf crate 中的 Bpaf trait
use bpaf::Bpaf;// 定义一个结构体 SpeedAndDistance,自动实现 Clone、Debug 和 Bpaf trait
#[derive(Clone, Debug, Bpaf)]
#[bpaf(options, version)] // 添加额外属性 options 和 version
struct SpeedAndDistance {speed: f64,     // 速度distance: f64,  // 距离
}fn main() {// 解析命令行参数并返回选项let opts = speed_and_distance().run();// 打印解析得到的选项信息println!("Options: {:?}", opts);
}
  • 通过结构体的方式定义了两个属性,分别是速度和距离;
  • 由于我们需要将这两个字段作为命令行输入的参数,因此这里使用了#[bpaf(options,version)]

示例代码中定义了两个参数,在运行时,通过下面的命令即可指定参数值执行程序

cargo run -- --speed 20.0 --distance 100.0

image-20240518093613844

需要注意的是,这个crate有两种不同的用法,过多内容请移步文档。

下面两个crate对本项目的实质性功能不会产生影响并且使用也相对简单,这里就只做简单的介绍,具体的示例就不再写了,感兴趣的可以自己学习一下。

3.3 ansi_term

这个小工具用于美化字段字符的。虽然它的有无并不会影响我们项目的实际功能,但是通过这个工具,我们可以给自己的项目画一个有颜色的炫酷字符图案logo,这看起来是一件很酷的事情。

3.4 prettytable-rs

用于将输出构建成终端表格的形式进行打印,并且可以指定表格颜色等信息。美化和规范输出。


4. 步入正题

开始正式编码之前,先分析一下大致的实施步骤。

我们的X-SCAN大致可以分为三个小块。

  • 命令行参数的定义解析:负责解析命令行参数
  • 端口扫描的函数:负责完成扫描的核心任务
  • Rust主函数:调用扫描函数并将结果组织返回

基于此,这里将按照这个步骤依次展开讲解;

4.1 参数定义

我们的X-SCAN一共需要三个参数,分别是:

  1. IP地址:Address
  2. 起始端口号:start_port
  3. 结束端口号:end_port
// 命令行参数定义
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Argument {#[bpaf(long, short, argument("Address"), fallback(IPFALLBACK))]/// 想要嗅探的地址,必须是有效的IPv4地址。将回退到127.0.0.1pub address: IpAddr,#[bpaf(long("start"),short('s'),guard(start_port_guard, "必须大于0"),fallback(1u16))]pub start_port: u16,#[bpaf(long("end"),short('e'),guard(end_port_guard, "必须小于或等于65535"),fallback(MAX))]pub end_port: u16,
}
  • 这里主要用到了bpaf,这个上面讲过了,但是这里有一些东西需要提一下;
  • 这里用到了guard作为字段的条件约束,指明该参数应该满足的规则,它需要指定一个校验函数;
  • 引入了longshort两个属性,用来指定参数的长格式和短格式两种风格;
  • fallback用来指定参数默认值,在用户没有显式指定参数时,它的值将用作默认值;

上面的代码中大概也注意到了,在指定IP地址参数时,我们用到了两个默认值的常量,下面是他们的定义:

const IPFALLBACK: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
const MAX: u16 = 65535;
  • 常量( MAXIPFALLBACK ):这些是用作默认值的预定义值。 MAX 设置结束端口的最大值,确保它不超过允许的最大端口号 (65535)。
  • IPFALLBACK 提供默认 IP 地址(127.0.0.1,这是本地主机),以防用户未指定 IP 地址。

4.2 扫描函数

这个scan 函数是一个异步函数,旨在检查给定 IP 地址上的特定端口是否打开。

/// 异步函数:扫描指定地址和端口
async fn scan(tx: Sender<u16>, start_port: u16, addr: IpAddr) {match TcpStream::connect(format!("{}:{}", addr, start_port)).await {Ok(_) => {print!(".");io::stdout().flush().unwrap();tx.send(start_port).unwrap();}Err(_) => {}}
}
  • 函数签名: async fn scan(tx: Sender<u16>, start_port: u16, addr: IpAddr) :这定义了一个名为 scan 的异步函数,它采用三个参数:
    • txSender<u16> 类型,用于将数据(在本例中为端口号)发送到程序的另一部分。
    • start_port :要检查的端口号;
    • addr :要检查端口的 IP 地址。
  • TcpStream::connect(format!("{}:{}", addr, start_port)).await :此行尝试建立到指定 addrstart_port 的 TCP 连接。使用 await 关键字是因为 TcpStream::connect 是一个异步操作,您需要等待它完成才能继续,这一点之前也说过了;
  • 使用match表达式来处理返回的不同结果,具体如下:
    • Ok(_):连接成功,捕获一个开放端口;
      • print!(".")这里用来在扫描过程中打印...,作为正在扫描的视觉提示;
      • io::stdout().flush().unwrap():通过刷新标准输出缓冲区确保点立即显示在屏幕上,达到实时加载的视觉效果;
      • tx.send(start_port).unwrap():通过 tx 通道返回开放端口号,以由程序的其他部分处理或记录。
    • Err(_):连接失败,表示本次连接的端口为关闭状态,不做任何操作;
  • 利用异步编程有效地处理可能长时间运行的网络操作,而不会阻止程序其他部分的执行。允许同时扫描多个端口,从而加快扫描过程。

4.3 结果处理

main 函数设置异步环境、收集参数并生成用于扫描指定范围内的每个端口的任务。通过表格整理返回结果并打印 ;

#[tokio::main]
async fn main() {print_infos();let opts = argument().run();let (tx, rx) = channel();for i in opts.start_port..opts.end_port {let tx = tx.clone();task::spawn(async move { scan(tx, i, opts.address).await });}let mut open_ports = vec![];drop(tx);for p in rx {open_ports.push(p);}println!("");open_ports.sort();let mut table = Table::new();table.add_row(Row::new(vec![Cell::new("Port").style_spec("Fg=blue"),Cell::new("Status").style_spec("Fg=blue"),]));for port in open_ports {table.add_row(Row::new(vec![Cell::new(&port.to_string()),Cell::new("is open"),]));}table.printstd();
}
  • #[tokio::main] :该属性宏将常规 main 函数转换为异步主函数。它设置 Tokio 运行时,这是运行异步代码所必需的。
  • let opts = arguments().run(); :此行调用 arguments() 函数。该函数构造并解析命令行参数,返回 Arguments 结构体存储在 opts 中。
  • let (tx, rx) = channel(); :这里创建了生产者、单消费者 通道。 tx 是发送者, rx 是接收者。该通道用于异步任务之间的通信。
  • 接着就是端口扫描的一个循环处理:
    • 第10行 :为每个端口生成一个新的异步任务。使用当前端口号 i 、克隆的发送者 tx 和目标 IP 地址 opts.address 调用 scan 函数。每个任务将尝试连接到其分配的端口并通过通道将结果发送回。
  • drop(tx); :显式删除原始发件人。这很重要,因为它标识将不再在此通道上发送消息,从而允许接收者在处理所有发送的消息后退出循环。
  • 对于结果的处理,这里创建了一个vec数组,此循环从通道接收消息。每条消息代表一个开放端口号并将其存入vec之中;
  • 对于23-27行,使用prettytable-rs提供的方法构建表格的表头,包括端口Port和开放状态Status;
  • 29-36行则是将结果添加到表格中并打印在终端。

4.4 打印版本信息

对于图案信息,大家可以去这个网站生成之后复制过来.https://patorjk.com/software/taag/

我们新增一个函数,用来在重新启动时打印X-SCAN的字符LOGO和版本等信息:

fn print_infos() {println!("{}",Red.paint(r#"__   __            _____    _____              _   _ \ \ / /           / ____|  / ____|     /\     | \ | |\ V /   ______  | (___   | |         /  \    |  \| |> <   |______|  \___ \  | |        / /\ \   | . ` |/ . \            ____) | | |____   / ____ \  | |\  |/_/ \_\          |_____/   \_____| /_/    \_\ |_| \_|author:代号0408version:0.1.0                                                      "#));
}

别忘了在main函数中调用;


5. 使用方式

cargo run -- --address 8.137.10.104 --start 1 --end 8888
  • address参数:指定要扫描的IP地址
  • start 参数:指定起始端口
  • end参数:指定结束端口

当然,你也可以对参数使用短格式来执行程序:

cargo run -- --address 49.232.219.30 -s 1 -e 10000

注意,使用参数的短格式形式时参数前面的短横线也需要调整为一条短横线(-),长格式参数使用两条(--);

假设我们不指定IP地址,那么它将会默认扫描本地127.0.0.1;

cargo run --   -s 1 -e 10000

  • 项目地址:https://github.com/08820048/X-SCAN

免责声明:X-SCAN工具仅供合法授权的网络安全测试和评估使用,作者不对任何非法或未经授权的使用行为承担责任。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/840935.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring AMQP 随笔 8 Retry MessageRecoverer ErrorHandler

0. 列位&#xff0c;响应式布局好麻烦的 … 有意思的&#xff0c;chrome devtool 在调试响应式的分辨率的时候&#xff0c;比如说在 宽度远远大于 768 的时候&#xff0c;按说浏览器也知道大概率是 web端方式打开&#xff0c;样式也是如此渲染&#xff0c;但一些事件(没有鼠标…

【IC设计】牛客网-序列检测习题总结

文章目录 状态机基础知识VL25 输入序列连续的序列检测VL26 含有无关项的序列检测VL27 不重叠序列检测VL28 输入序列不连续的序列检测参考资料 状态机基础知识 VL25 输入序列连续的序列检测 timescale 1ns/1ns module sequence_detect(input clk,input rst_n,input a,output re…

csdn的insCode怎么用IDE和linux终端

1.进入insCode&#xff0c;选择工作台 找到我的项目&#xff0c;没有项目的话可以新建一个。 选择在IDE中编辑&#xff0c;界面如下&#xff1a; 右边有个终端&#xff0c;点击即可出现linux的xterm终端。

边用边充电影响寿命吗?看看计算机指令组成与操作类型

计算机指令集体系结构之指令 指令由操作码和地址码字段组成。 操作码指明了指令要完成的操作。 长度可以固定&#xff1a;比如RISC&#xff08;reduced instruction set computer&#xff09;精简指令集计算机 与之对应的RISC&#xff08;复杂指令集计算机&#xff09;&…

福昕PDF使用技巧

因为突然间学校的企业版WPS突然很多功能就不能使用了&#xff0c;所以转向福昕PDF。 一、合并文件 添加需要合并的文件&#xff0c;可以使用ctrla等方式全选 找到最上方的“合并文件” 二、文本注释

IDEA打开项目报错

IDEA打开项目报错&#xff1a; Cannot read scheme C:\Users\xxxxxx\AppData\Roaming\JetBrains\IntelliJIdea2023.2\qaplug_profiles\Default.xmljava.lang.AbstractMethodError: Receiver class com.soldevelo.qaplug.scanner.AnalysisProfileManager$2 does not define or i…

【讲解下PDM,PDM是什么?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【汽车之家注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

借助Kong记录接口的请求和响应内容

和APISIX类似&#xff0c;Kong也是一个Api GateWay。 运行在调用Api之前&#xff0c;以插件的扩展方式为Api提供管理, 如 鉴权、限流、监控、健康检查等. Kong是基于Lua语言、Nginx以及OpenResty开发的&#xff0c;拥有动态路由、负载均衡、高可用、高性能、熔断&#xff08;基…

通过RAG架构LLM应用程序

在之前的博客文章中&#xff0c;我们已经描述了嵌入是如何工作的&#xff0c;以及RAG技术是什么。本节我们我们将使用 LangChain 库以及 RAG 和嵌入技术在 Python 中构建一个简单的 LLM 应用程序。 我们将使用 LangChain 库在 Python 中构建一个简单的 LLM 应用程序。LangChai…

自己手写一个单向链表【C风格】

//单链表 #include <iostream> #define MAX_SIZE 20 #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0typedef int ElemType;//元素的类型 typedef int Status;//返回状态typedef struct Node {ElemType data;//链表中保存的数据struct Node* next;//指向下…

【CSP CCF记录】201909-1 小明种苹果

题目 过程 #include<bits/stdc.h> using namespace std; int N,M; long long tree[1010]; int main() {cin>>N>>M;long long result0,max0;//result剩余苹果&#xff0c;max最大疏果个数 int id0;//id最大疏果的果树编号 for(int i1;i<N;i){long long b0…

构建php环境

目录 php简介 官网php安装包 选择下载稳定版本 &#xff08;建议使用此版本&#xff0c;文章以此版本为例&#xff09; 安装php解析环境 准备工作 安装依赖 zlib-devel 和 libxml2-devel包。 安装扩展工具库 安装 libmcrypt 安装 mhash 安装mcrypt 安装php 选项含…

Gin框架学习笔记(六)——gin中的日志使用

gin内置日志组件的使用 前言 在之前我们要使用Gin框架定义路由的时候我们一般会使用Default方法来实现&#xff0c;我们来看一下他的实现&#xff1a; func Default(opts ...OptionFunc) *Engine {debugPrintWARNINGDefault()engine : New()engine.Use(Logger(), Recovery())…

uniapp微信小程序解决type=“nickname“获取昵称,v-model绑定值为空问题!

解决获取 type"nickname"值为空问题 文章目录 解决获取 type"nickname"值为空问题效果图Demo解决方式通过表单收集内容通过 uni.createSelectorQuery 效果图 开发工具效果图&#xff0c;真机上还会显示键盘输入框 Demo 如果通过 v-model 结合 blur 获取不…

【Linux】写时拷贝技术COW (copy-on-write)

文章目录 Linux写时拷贝技术(copy-on-write)进程的概念进程的定义进程和程序的区别PCB的内部构成 程序是如何被加载变成进程的&#xff1f;写时复制&#xff08;Copy-On-Write, COW&#xff09;写时复制机制的原理写时拷贝的场景 fork与COWvfork与fork Linux写时拷贝技术(copy-…

VUE3 学习笔记(十)查看vue版本

命令&#xff1a; npm list vue(空) (在项目的根目录下执行以下命令即可查看项目所使用的vue版本) npm list vue version(空) npm info vue (全局查看vue版本号&#xff0c;详细) npm list vue -g(全局查看vue版本号&#xff0c;简单) npm view vue version(查看项目依赖的vue…

开源博客项目Blog .NET Core源码学习(26:App.Hosting项目结构分析-14)

后台管理页面的系统管理下主要包括用户管理、角色管理、按钮管理和菜单管理&#xff0c;其中创建用户时要指定角色&#xff0c;创建角色时需指定菜单权限&#xff0c;按钮管理也是基于各菜单项进行设置&#xff0c;只有菜单管理相对独立&#xff0c;因此本文学习并分析App.Host…

蓝桥杯【第15届省赛】Python B组 32.60 分

F 题列表越界访问了……省一但没什么好名次 测评链接&#xff1a;https://www.dotcpp.com/oj/train/1120/ C 语言网真是 ** 测评&#xff0c;时间限制和考试的不一样&#xff0c;E 题给我整时间超限&#xff1f; A&#xff1a;穿越时空之门 100&#x1f3c6; 【问题描述】 随…

使用梦畅闹钟,结合自定义bat、vbs脚本等实现定时功能

梦畅闹钟-每隔一段时间运行一次程序 休息五分钟bat脚本&#xff08;播放音乐视频&#xff0c;并锁屏&#xff09; chcp 65001 echo 回车开始休息5分钟 pause explorer "https://www.bilibili.com/video/BV1RT411S7Tk/?p47" timeout /t 3 /nobreak rundll32.exe use…