Java实现端口扫描器

通过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


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

public static JFrame main=new JFrame("JAVA端口扫描"); //显示扫描结果 public static JTextArea Result=new JTextArea("",4,40); //滚动条面板 public static JScrollPane resultPane = new JScrollPane(Result,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); //输入主机名文本框 public static JTextField hostname=new JTextField("localhost",8); //输入ip地址前3位的输入框 public static JTextField fromip1=new JTextField("0",3); //输入ip地址4~6位的输入框 public static JTextField fromip2=new JTextField("0",3); //输入ip地址7~9位的输入框 public static JTextField fromip3=new JTextField("0",3); //输入起始ip地址最后4位的输入框 public static JTextField fromip4=new JTextField("0",3); //输入目标ip地址最后4位的输入框 public static JTextField toip=new JTextField("0",3); //输入最小端口的输入框 public static JTextField minPort=new JTextField("0",4); //输入最大端口的输入框 public static JTextField maxPort=new JTextField("1000",4); //输入最大线程数量的输入框 public static JTextField maxThread=new JTextField("100",3); //错误提示框 public static JDialog DLGError=new JDialog(main,"错误!"); public static JLabel DLGINFO=new JLabel(""); public static JLabel type=new JLabel("请选择:"); //扫描类型 public static JRadioButton radioIp = new JRadioButton("IP地址:"); public static JRadioButton radioHost = new JRadioButton("主机名:",true); //单选框组 public static ButtonGroup group = new ButtonGroup(); public static JLabel P1=new JLabel("端口范围:"); public static JLabel P2=new JLabel("~"); public static JLabel P3=new JLabel("~"); public static JLabel Pdot1 = new JLabel("."); public static JLabel Pdot2 = new JLabel("."); public static JLabel Pdot3 = new JLabel("."); public static JLabel TNUM=new JLabel("线程数:"); public static JLabel RST=new JLabel("扫描结果: "); public static JLabel con=new JLabel(" "); //定义按钮 public static JButton OK = new JButton("确定"); public static JButton Submit = new JButton("开始扫描"); public static JButton Cancel = new JButton("退出"); public static JButton saveButton = new JButton("保存扫描结果"); //菜单栏 public static JMenuBar myBar = new JMenuBar(); public static JMenu myMenu = new JMenu("文件(F)"); public static JMenuItem saveItem = new JMenuItem("保存扫描结果(S)"); public static JMenuItem exitItem = new JMenuItem("退出(Q)"); public static JMenu myMenu2 = new JMenu("帮助"); public static JMenuItem helpItem = new JMenuItem("阅读"); public static void main(String[] args){ main.setSize(500,400); main.setLocation(300,300); main.setResizable(false); main.setLayout(new GridBagLayout()); main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); DLGError.setSize(300,100); DLGError.setLocation(400,400); //添加“菜单栏” myMenu.add(saveItem); myMenu.add(exitItem); myMenu2.add(helpItem); myBar.add(myMenu); myBar.add(myMenu2); main.setJMenuBar(myBar); //设置热键 myMenu.setMnemonic('F'); saveItem.setMnemonic ('S'); //为“另存为”组件设置快捷键为ctrl+s saveItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_S,InputEvent.CTRL_MASK)); exitItem.setMnemonic('Q'); exitItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_E,InputEvent.CTRL_MASK)); //采用表格包型布局 Container mPanel = main.getContentPane(); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(10,0,0,10); c.gridx = 0; c.gridy = 0; c.gridwidth = 10; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(type,c); group.add(radioIp); group.add(radioHost); c.gridx = 0; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(radioIp,c); c.gridx = 1; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(fromip1,c); c.gridx = 2; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(Pdot1,c); c.gridx = 3; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(fromip2,c); c.gridx = 4; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(Pdot2,c); c.gridx = 5; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(fromip3,c); c.gridx = 6; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(Pdot3,c); c.gridx = 7; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(fromip4,c); c.gridx = 8; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(P2,c); c.gridx = 9; c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(toip,c); c.gridx = 0; c.gridy = 2; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(radioHost,c); c.gridx = 1; c.gridy = 2; c.gridwidth = 3; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(hostname,c); c.gridx = 0; c.gridy = 3; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(P1,c); c.gridx = 1; c.gridy = 3; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(minPort,c); c.gridx = 2; c.gridy = 3; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(P3,c); c.gridx = 3; c.gridy = 3; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(maxPort,c); c.gridx = 0; c.gridy = 4; c.gridwidth = 1; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(TNUM,c); c.gridx = 1; c.gridy = 4; c.gridwidth = 3; c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.CENTER; mPanel.add(maxThread,c);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值