通过Java实现一个端口扫描器

一、原理介绍

端口扫描顾名思义,就是扫描目的资源的端口,来发现目的资源是否对外开放某个端口,提供了某种网络服务。
在安全攻防领域经常通过端口扫描技术来发现关键资产对外提供服务的情况。红军为发现暴露的风险点,进行安全加固;蓝军为识别目标服务,以便识别资产的脆弱点,实施针对性网络攻击。

二、实现

功能介绍

  • 对指定ip进行指定端口进行扫描,获取目的ip的端口开放情况
  • 扫描模式:TCP全连接扫描、TCP半连接扫描(待实现)
  • 通过发送空信息,获取开放端口对应服务的应答,获取指纹信息,从而判断服务类别
  • 端口开放情况会通过日志打印出来

输入

  • ip地址或ip地址组,用逗号隔开
  • 待扫描的端口组:0-65535,左开右闭

输出

  • 通过日志打印端口开放情况

类说明

  • ScanApp 扫描器启动类
/**
 * 扫描器启动类
 */
public class ScanApp {
    public static void main(String[] args) {
        // 待扫描的ip地址或ip地址组
        String ips = null;
        ips = "14.29.192.196,14.116.188.121";
        // 待扫描的port范围
        String ports = "20-25";
        Scanner.start(ips,ports);


    }
}

  • Scanner 扫描管理类
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
 * 端口、指纹扫描器
 * 职责:
 * 1、解析输入的ip地址、端口
 * 2、新键扫描任务ScanJob扫描引擎进行端口扫描
 * 3、两种扫描方式:TCP全连接扫描、TCP半连接扫描
 *
 */
public class Scanner {
    // 日志
    private static Logger logger = Logger.getLogger("Scanner");
    // 使用多线程扫描
    private static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10,20,1000,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(5),
            Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    /**
     * 开始方法
     * @param ips 输入的待扫描ip列表
     * @param ports 输入的待扫描端口
     */
    public static void start(String ips, String ports) {
        // 解析端口
        String[] portArray = ports.split("-");
        int portStart = Integer.parseInt(portArray[0]);
        int portEnd = Integer.parseInt(portArray[1]);
        logger.info("[-] Ports: " + ports);
        // 解析ip地址,并扫描
        if (ips.indexOf(',') != -1) {
            String[] ipList = ips.split(",");
            logger.info("[-] IP list: " + ipList.toString());
            for (String ip : ipList) {
                scanAllPort(ip,portStart,portEnd);
            }
        }else if (ips.indexOf('/') != -1){
            // TODO 支持ip地址网段的解析
            String[] ipArray = ips.split("/");
            String ipAddress = ipArray[0];
            int mask = Integer.parseInt(ipArray[1]);
            String[] ipSplit = ipAddress.split(".");

        }else {
            scanAllPort(ips,portStart,portEnd);
        }
        // 扫描任务都完成后,程序停止
        try{
            while(true){
                if(poolExecutor.getActiveCount() == 0){
                    logger.info("[-] Scan job all finish");
                    System.exit(-1);
                    break;
                }
                Thread.sleep(1000);
            }
        }catch (Exception ex){
            logger.warning("End with exeception, ex: " + ex.getMessage());
        }
        System.exit(-1);





    }

    /**
     * 扫描某ip的对应端口
     * @param ip ip地址
     * @param portStart 开始扫描的的端口
     * @param portEnd 停止扫描的端口
     */
    public static void scanAllPort(String ip, int portStart, int portEnd){
        for (int port = portStart; port <= portEnd; port++){
            scan(ip,port);
        }
    }

    /**
     * 对ip:port进行扫描
     * @param ip ip地址
     * @param port 端口
     */
    public static void scan(String ip, int port){
        // 执行扫描任务
        poolExecutor.execute(new ScanJob(new ScanObject(ip,port),ScanEngine.TCP_FULL_CONNECT_SCAN));
    }
}
  • ScanEngine 扫描器引擎
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.Socket;
import java.util.logging.Logger;

/**
 * 扫描器引擎
 * 实现具体的扫描逻辑,提供扫描能力(TCP全连接和半连接扫描)
 */
public class ScanEngine {
    private static Logger logger = Logger.getLogger("TCPFullConnectScan");
    public static final String TCP_FULL_CONNECT_SCAN = "TCP_FULL_CONNECT_SCAN";
    public static final String TCP_HALF_CONNECT_SCAN = "TCP_HALF_CONNECT_SCAN";

    public static ScanObject scan(ScanObject object, String scanEngine){
        switch (scanEngine){
            case TCP_FULL_CONNECT_SCAN:
                return tcpFullConnectScan(object);
            case TCP_HALF_CONNECT_SCAN:
                return tcpHalfConnectScan(object);
        }
        return tcpFullConnectScan(object);
    }

    /**
     * tcp全连接扫描
     * @param object
     * @return
     */
    private static ScanObject tcpFullConnectScan(ScanObject object){
        try{
            // 全连接扫描,发现可用服务
            Socket socket = new Socket(object.getIp(),object.getPort());
            object.setOpen(true);
            object.setService();
            // 发送招手信息
            OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
            out.write("hello");
            out.flush();
            // 获取服务指纹
            BufferedReader re = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String serviceBanner = re.readLine();
            object.setBanner(serviceBanner);
            object.setService();
            logger.info("[-] Find service :"+ object);
            socket.close();
        } catch (ConnectException e) {
            // 打印连接失败的端口
//            logger.info("[-] Close: " + object.getIp() + ":" + object.getPort());

        } catch (Exception e){
            // 出现其他异常
            logger.info("[-] " + object.toString() + "end with unexecepted exeception:" + e.getMessage());
        }
        return object;

    }

    /**
     * TPC 半连接扫描
     * @param object
     * @return
     */
    private static ScanObject tcpHalfConnectScan(ScanObject object){
        // TODO 待实现tcp半连接扫描
        return object;
    }
}

  • ScanJob 扫描任务类
/**
 * 扫描任务类,执行具体的扫描任务
 */
public class ScanJob implements Runnable{
    // 扫描信息
    private ScanObject object;
    // 扫描类型
    private String scanType;

    public ScanJob(ScanObject object,String scanType) {
        this.object = object;
        this.scanType = scanType;

    }

    @Override
    public void run() {
        ScanEngine.scan(object, scanType);
    }
}

  • ScanObject 扫描结果实体类
import java.util.HashMap;
import java.util.Map;

/**
 * 扫描信息实体
 */
public class ScanObject {
     private String ip;
     private int port;
     private String service;
     private Boolean isOpen;
     private String banner;
     // 存放服务指纹和服务的对应关系 banner -> service
     private static Map<String,String> bannerMaps = new HashMap<>();
     // 存放常见端口与服务的对应关系 port -> service
     private static Map<Integer,String> portMaps = new HashMap<>();

     static {
         bannerMaps.put("ssh","SSH");
         portMaps.put(22,"SSH");
         bannerMaps.put("ftp","FTP");
         portMaps.put(21,"FTP");
         portMaps.put(20,"FTP");
         bannerMaps.put("smtp","SMTP");
         portMaps.put(25,"SMTP");
         bannerMaps.put("mysql","MySQL");
         portMaps.put(3389,"MySQL");
     }

    public ScanObject(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    public Boolean getOpen() {
        return isOpen;
    }

    public void setOpen(Boolean open) {
        isOpen = open;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getService() {
        return service;
    }

    public void setService() {
         // 先根据port判断服务类型
        if (portMaps.containsKey(this.port)){
            this.service = portMaps.get(this.port);
        }
        if (banner != null && !banner.equals("")){
            for (String key : bannerMaps.keySet()) {
              if (banner.toLowerCase().contains(key)) {
                  this.service = bannerMaps.get(key);
                  break;
              }
            }
        }
    }

    public String getBanner() {
        return banner;
    }

    public void setBanner(String banner) {
        this.banner = banner;

    }

    @Override
    public String toString() {
        return "ScanObject{" +
                "ip='" + ip + '\'' +
                ", port=" + port +
                ", service='" + service + '\'' +
                ", isOpen=" + isOpen +
                ", banner='" + banner + '\'' +
                '}';
    }
}

使用方式

  • 在ScanApp中的ips字符串中加入待扫描的ip地址(逗号隔开),在ports中输入扫描的端口范围(减号隔开)
  • 使用jdk8运行ScanApp

项目源码链接:

https://github.com/aa792978017/Scanner


如果这篇文章对你有帮助,欢迎点赞收藏;如果对文章有任何疑问,也欢迎留言提出,笔者会尽快回复。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐