发现漏洞!Socket编程端口扫描小程序(完整源码)

1,265

目录

一、端口扫描技术

二、 单线程建立 Socket 连接

三、 基于 TCP Connect 端口扫描

四、 多线程 TCP 连接扫描

五、 小程序完整源码

一、端口扫描技术

我们都知道,网络中的每台机器都有IP地址,与IP地址密切相关的就是主机的端口,顺便考考你,知道端口号的范围吗?

没错,就是0~65535。在计算机网络中,使用两个字节供16位二进制数来表示端口号。对于这些端口号,一台主机它有开放哪些端口号呢?

比如常见的80,443两个端口号对应的就是HTTP,HTTPS服务,23 端口对应telnet远程管理,25 端口是SMTP服务等等。下面我们就来实现一个简易的端口扫描小程序。

扫描主机开放的端口是常见操作:攻(寻找目的主机开放的端口)与防(检测本机异常开放的端口)。基本的端口扫描可使用的技术:

  • 创建Socket连接:new Socket(ip,port)
  • TCP Connect 探测

本篇适用计算机网络、Java开发、Socket编程以及多线程入门的伙伴!可以结合上一篇《Java开发主机IP扫描神器》,进一步探测网络攻防有用信息!

二、 单线程建立 Socket 连接

现在开始小程序的简易开发流程,从单线程开始一步一步前进。

Socket socket=new Socket(host,port);

这里,我们通过创建一个Socket连接,这种方式原理就是与目标IP建立连接,监听端口,如果超过一定时间,说明该端口没有开放,无法成功连接。

无法连接的情况会抛出 IOException 异常,因此,可以在抛出异常的时候判定端口是关闭状态。

具体地实现很简单,我们把扫描端口的操作放到一个线程里面,不影响主程序的运行,然后对给出的端口范围比如 0到100,逐个进行Socket连接,这样就可以知道哪些端口的open的。

try {
     Socket socket=new Socket(host,port);
     socket.close();
     Platform.runLater(() -> {
          result.appendText("端口 " +port+ " is open.\n");
     });
} catch (IOException e) {
   result.appendText("端口 " +port+ " is closed.\n");
}

这是对我的主机部分端口扫描的结果。

下面是动图可以看一下过程。

三、 基于 TCP Connect 端口扫描

从动态图的扫描过程可以发现,这个速度明显不是人可以容忍的,就像一个网页加载半天一直没显示出来一样,所以,为了更好的用户体验,在此基础上进行优化改进,最重要的就是扫描速度!

上一个方法在遇到端口关闭时等待时间过长,时间成本过高。开始使用另一个方法,基于TCP Connect 的端口扫描。

Socket socket=new Socket();
socket.connect(new InetSocketAddress(host,port),200);
socket.close();

这时候,不会对每个ip和端口真正建立连接,而是使用“探测”的方式,通过InetSocketAddress类进行连接,超时时间设定200ms,明显这样速度至少可以提高几倍!

代码实现很简单,这样写就搞定啦

try {
    Socket socket=new Socket();
    socket.connect(new InetSocketAddress(host,port),200);

    socket.close();
    Platform.runLater(() -> {
        result.appendText("端口 " +port+ " is open.\n");
    });
} catch (IOException e) {
    result.appendText("端口 " +port+ " is closed.\n");
}

优化改进之后的对比扫描速度如下动图:

对比之下,扫描速度大幅度提升!

这时,速度的提升是相对而言,比如扫描1000个端口,你就知道有多慢了,还是等不及!

20秒竟然才扫描不到80个端口!粗略计算,1000个端口至少需要4分钟,这是无法接受的!

四、 多线程 TCP 连接扫描

这时候,只能使用后手了,多线程专场…… 嘿嘿

我们现在的小目标就是速度要快,多线程操作真不赖。这里我开启100个线程,来完成这项扫描的任务。

for (int i=0;i<100;i++) {
    readThread=new Thread(new ScanHandler(i, 100),"scanThread");
    readThread.start();
}

同时,开启多线程就需要注意线程安全问题,在这个任务,还需要关注每个线程负责的端口范围,避免重复扫描以及扫描结束判断。

使用原子变量来保证线程安全,计数正确解决上述问题。

static AtomicInteger portCount portCount=new AtomicInteger(0);

定义一个扫描线程的处理类ScanHandler,处理逻辑大概就是:每个线程有自己的标识号,根据标识号确定自己负责的端口号,每个线程都维护着已扫描的端口原子计数

    class ScanHandler implements Runnable{
        private int totalThreadNum;//用于端口扫描的总共线程数量,默认为10
        private int threadNo;//线程号,表示第几个线程
        private int startP=Integer.parseInt(startPort.getText());
        private int endP=Integer.parseInt(endPort.getText());
        private String host = targetIP.getText().trim();

        public ScanHandler(int threadNo) {
            this.totalThreadNum = 10;
            this.threadNo = threadNo;
        }

        public ScanHandler(int threadNo,int totalThreadNum) {
            this.totalThreadNum = totalThreadNum;
            this.threadNo = threadNo;
        }

        @Override
        public void run() {
            //startPort和endPort为成员变量,表示需要扫描的起止端口
            for (int i=startP+threadNo;i<=endP;i=i+totalThreadNum){
                int port=i;
                if (readThread.isInterrupted()){
                    readThread.interrupt();
                    break;
                }
                try {
                    Socket socket=new Socket();
                    socket.connect(new InetSocketAddress(host,port),200);
                    socket.close();
                    Platform.runLater(() -> {
                        result.appendText("端口 " +port+ " is open.\n");
                    });
                }catch (IOException e){
//                    result.appendText("端口 " +i+ " is closed.\n");
                }
                portCount.incrementAndGet();

            }
            if (portCount.get()==(endP-startP+1)){//判断扫描结束
                portCount.incrementAndGet();
                Platform.runLater(()->{
                    result.appendText("\n-------------多线程扫描结束-------------\n");
                });
            }
        }
    }

再来看看多线程操作的速度有多快,现在感觉很顺畅,1000个端口不到10秒就完成扫描!

是不是感觉挺完美了,但根据常识总是差点什么,其实就是一个进度条显示,就像我们下载东西一样,有个进度条和百分比看着心里有底,安排!

Java中就有可以直接使用的进度类,就是下面这行代码,然后再加到扫描线程里面,实时更新进度。

ProgressBar progressBar=new ProgressBar();

来看看效果,用多线程版本扫描5000个端口。

五、 小程序完整源码

完整源码,毫无保留,建议果断收藏,以免以后使用找不到!

/**
 * HostScannerFX.java
 * Copyright (c) 2021 Charzous
 * All right reserved.
 * @date 2021-06-07 下午 09:38
 */


import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicInteger;

public class PortScannerFX extends Application {
    private TextArea result = new TextArea();
    private TextField targetIP = new TextField();
    private TextField startPort = new TextField();
    private TextField endPort = new TextField();
    private Button scan = new Button("扫描");
    private Button quickScan = new Button("快速扫描");
    private Button threadScan = new Button("多线程扫描");
    private Button ex=new Button("退出");
    private Button stop=new Button("停止扫描");
    private Thread readThread;
    static AtomicInteger portCount;//用于统计已扫描的端口数量

    private ProgressBar progressBar=new ProgressBar();
    private Label bar=new Label("0%");

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane mainPane = new BorderPane();

        HBox barBox=new HBox();
        barBox.setSpacing(10);
        barBox.setPadding(new Insets(10, 0, 10, 0));
        progressBar.setPrefWidth(700);
        progressBar.setProgress(0);
        HBox.setHgrow(progressBar,Priority.ALWAYS);
        barBox.getChildren().addAll(bar,progressBar);

        VBox vBox = new VBox();
        vBox.setSpacing(10);
        vBox.setPadding(new Insets(10, 20, 10, 20));
//        vBox.setAlignment(Pos.CENTER);
        VBox.setVgrow(result, Priority.ALWAYS);
        vBox.getChildren().addAll(new Label("端口扫描结果:"), result,barBox);
        mainPane.setCenter(vBox);

        startPort.setPrefWidth(60);
        endPort.setPrefWidth(60);
        HBox hBox1 = new HBox();
        hBox1.setSpacing(10);
        hBox1.setPadding(new Insets(10, 20, 10, 20));
        hBox1.setAlignment(Pos.CENTER);
        hBox1.getChildren().addAll(new Label("目标主机ip:"), targetIP, new Label("起始端口号:"), startPort, new Label("结束端口号:"),endPort);

        HBox hBox2 = new HBox();
        hBox2.setSpacing(10);
        hBox2.setPadding(new Insets(10, 20, 10, 20));
        hBox2.setAlignment(Pos.CENTER);

        hBox2.getChildren().addAll(scan,quickScan,threadScan,stop,ex);

        VBox vBox1 = new VBox();
        vBox1.setSpacing(10);
        vBox1.setPadding(new Insets(10, 20, 10, 20));
        vBox1.setAlignment(Pos.CENTER);
        vBox1.getChildren().addAll(hBox1, hBox2);
        mainPane.setBottom(vBox1);

        Scene scene = new Scene(mainPane, 800, 500);
        primaryStage.setScene(scene);
        primaryStage.setTitle("PortScannerFX");
        primaryStage.show();



        //扫描
        scan.setOnAction(event -> {
            String host = targetIP.getText().trim();
            int sp = Integer.parseInt(startPort.getText());
            int ep=Integer.parseInt(endPort.getText());
            readThread = new Thread(() -> {

                double num=0.0;
                for (int i = sp; i <= ep; i++) {
                    int port=i;
                    if (readThread.isInterrupted()){
                        readThread.interrupt();
//                        Thread.interrupted();
                        break;
                    }
                    try {
                        Socket socket=new Socket(host,port);
                        socket.close();
                        Platform.runLater(() -> {
                                result.appendText("端口 " +port+ " is open.\n");
                        });
                    } catch (IOException e) {
                        result.appendText("端口 " +port+ " is closed.\n");
                    }
                    num++;
                    double finalNum = num;
                    Platform.runLater(()->{
                        progressBar.setProgress(finalNum/(ep-sp+1));//进度条
                        bar.setText(""+ Integer.valueOf((int) (finalNum /(ep-sp+1)*100))+"%");
                    });
                }
                result.appendText("端口扫描结束!\n");
            },"scanThread");
            readThread.start();

        });

        //快速扫描
        quickScan.setOnAction(event -> {
            String host = targetIP.getText().trim();
            int sp = Integer.parseInt(startPort.getText());
            int ep=Integer.parseInt(endPort.getText());
            readThread = new Thread(() -> {

                double num=0;
                for (int i = sp; i <= ep; i++) {
                    if (readThread.isInterrupted()){
                        readThread.interrupt();
//                        Thread.interrupted();
                        break;
                    }
                    int port=i;
                    try {
                        Socket socket=new Socket();
                        socket.connect(new InetSocketAddress(host,port),200);

                        socket.close();
                        Platform.runLater(() -> {
                            result.appendText("端口 " +port+ " is open.\n");
                        });
                    } catch (IOException e) {
                        result.appendText("端口 " +port+ " is closed.\n");
                    }
                    num++;
                    double finalNum = num;
                    Platform.runLater(()->{
                        progressBar.setProgress(finalNum/(ep-sp+1));//进度条
                        bar.setText(""+ Integer.valueOf((int) (finalNum /(ep-sp+1)*100))+"%");
                    });
                }
                result.appendText("端口扫描结束!\n");
            },"scanThread");
            readThread.start();
        });

        threadScan.setOnAction(event -> {
            portCount=new AtomicInteger(0);
            int sp = Integer.parseInt(startPort.getText());
            int ep=Integer.parseInt(endPort.getText());
            for (int i=0;i<100;i++) {
                readThread=new Thread(new ScanHandler(i, 100),"scanThread");
                readThread.start();
            }
        });

        stop.setOnAction(event -> {
            interrupt("scanThread");
        });

        //退出
        ex.setOnAction(event -> {
            exit();
        });
        primaryStage.setOnCloseRequest(event -> {
            exit();
        });
    }

    public void interrupt(String threadName){
        ThreadGroup currentGroup=Thread.currentThread().getThreadGroup();
        int noThreads=currentGroup.activeCount();
        Thread[] lstThreads=new Thread[noThreads];
        currentGroup.enumerate(lstThreads);
        for (int i=0;i<noThreads;i++){
            if (lstThreads[i].getName().equals(threadName))
                lstThreads[i].interrupt();
        }
    }

    public void exit(){
        System.exit(0);
    }



    class ScanHandler implements Runnable{
        private int totalThreadNum;//用于端口扫描的总共线程数量,默认为10
        private int threadNo;//线程号,表示第几个线程
        private int startP=Integer.parseInt(startPort.getText());
        private int endP=Integer.parseInt(endPort.getText());
        private String host = targetIP.getText().trim();

        public ScanHandler(int threadNo) {
            this.totalThreadNum = 10;
            this.threadNo = threadNo;
        }

        public ScanHandler(int threadNo,int totalThreadNum) {
            this.totalThreadNum = totalThreadNum;
            this.threadNo = threadNo;
        }

        @Override
        public void run() {
            //startPort和endPort为成员变量,表示需要扫描的起止端口
            for (int i=startP+threadNo;i<=endP;i=i+totalThreadNum){
                int port=i;
                if (readThread.isInterrupted()){
                    readThread.interrupt();
                    break;
                }
                try {
                    Socket socket=new Socket();
                    socket.connect(new InetSocketAddress(host,port),200);
                    socket.close();
                    Platform.runLater(() -> {
                        result.appendText("端口 " +port+ " is open.\n");
                    });
                }catch (IOException e){
//                    result.appendText("端口 " +i+ " is closed.\n");
                }
                portCount.incrementAndGet();
                Platform.runLater(()->{
                    bar.setText(""+ Integer.valueOf((int) ((portCount.doubleValue())/(endP-startP+1)*100))+"%");//进度比
                    progressBar.setProgress((portCount.doubleValue())/(endP-startP+1));//进度条
                });
            }
            if (portCount.get()==(endP-startP+1)){//判断扫描结束
                portCount.incrementAndGet();
                Platform.runLater(()->{
                    result.appendText("\n-------------多线程扫描结束-------------\n");
                });
            }
        }
    }


    public static void main(String[] args) {
        launch();
    }
}


完整源码,毫无保留,建议果断收藏,以免以后使用找不到,赶紧自己动手开发一个简易端口扫描小程序!

今天用Java开发技术:Socket编程端口扫描小程序,零基础Socket编程详细教程,这篇内容是不是简单、有趣、有收获呢?欢迎交流学习!

最后想跟大家说的是,学习Java必备的知识有哪些呢?很多粉丝私信我Java学习的路线,我推荐了这套知识图谱,粉丝们都觉得质量很不错!

学习Java开发,Socket网络编程等知识,里面有许多有趣的小程序可以做,最近我也在跟着这一套 《Java 工程师学习成长知识图谱》进行体系的学习,是CSDN官方推出的,质量很不错!

其中包含了Java专业体系结构完整详细,推荐给大家学习使用,有兴趣可以扫码查看,最近我也在学习当中,当然,我的文章会记录学习,欢迎大家阅读,比如我的专栏《Socket网络编程》、《Java宝藏》。

展开就是这样的,尺寸870mm*560mm排版好看,内容很充实。推荐给有需要的伙伴,一起来学习Java开发!

**如果觉得不错欢迎“一键三连”哦,点赞收藏关注,评论提问建议,欢迎交流学习!一起加油进步,我们下篇见! 更多详细内容可以到我的CSDN博客查看!
**

本篇内容首发我的CSDN博客:csdn-czh.blog.csdn.net/article/det…