网络安全实验一 Part 2 Windows环境下的扫描器程序

实验任务

  • 编写端口扫描程序

    1. 具有界面(使用QtCreator)
    2. 具有多线程处理能力
    3. 使用简单的connect确定端口是否开放即可
    4. 可以提前结束扫面,安全的结束线程
  • 效果示例(源码)

    1. 界面

      在这里插入图片描述

    2. 扫描局域网内开放的端口

      在这里插入图片描述

    3. 扫描百度

      在这里插入图片描述

    4. 多线程加速

      在这里插入图片描述

    5. 提前结束扫描

      在这里插入图片描述

      在这里插入图片描述

实验步骤一、熟悉QtCreator编程

实验步骤二、设计界面

  • 拖拉拽即可,注意一点就是控件的objectName属性值,通过该值可以在代码中访问、更新UI,如

    在这里插入图片描述

    更新结果显示区内容时,使用ui->resultArea访问该控件对象

    void MainWindow::updateResult(QString info, bool opened) {
        /* other code */
        
        /* 向结果区增加记录 */
        ui->resultArea->append(info + " " + (opened ? "opened" : "closed"));
        
       	/* other code */
    }
    

    也可以在代码中设置控件的一些属性,如

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        
        /* 设置每个IP输入框的长度限制 */
        ui->startIP_1->setMaxLength(3);
        ui->startIP_2->setMaxLength(3);
        ui->startIP_3->setMaxLength(3);
        ui->startIP_4->setMaxLength(3);
        ui->endIP_1->setMaxLength(3);
        ui->endIP_2->setMaxLength(3);
        ui->endIP_3->setMaxLength(3);
        ui->endIP_4->setMaxLength(3);
    
        /* other code */
    }
    

    将按钮的点击事件和处理函数绑定(Qt称为连接信号与槽)

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        /* other code */  
    	
        /* 绑定(连接)开始扫描和结束扫描按钮点击事件(信号)对应的处理函数(槽) */
        QObject::connect(ui->beginScan, &QPushButton::clicked, this, &MainWindow::scan);
        QObject::connect(ui->stopScan, &QPushButton::clicked, this, &MainWindow::terminateScan);
    }
    

实验步骤二、确定通信对象,声明信号和槽

  • UI中的按钮创建对应的槽 mainwindow.h

    class MainWindow : public QMainWindow
    {
    /* other code */
        
    /* 声明槽 */
    public slots :
        void scan(); /* 开始扫描 */
        void terminateScan(); /* 结束扫描 */
        void updateResult(QString info, bool open); /* 子线程扫描结束对应的槽 */
    };
    
  • 子线程扫描结束信号,及主线程对应的槽(上述第三个) scanthread.h

    class ScanThread : public QThread
    {
    /* other code */
        
    /* 声明信号 */
    signals:
        void scanOver(QString info, bool opened); /* 子线程扫描完成一个端口时发出信号,主线程更新UI */
    };
    

实验步骤三、主线程之参数校验

  • 合法IP地址、IP范围校验

  • 合法端口校验

  • 合理线程校验

    qDebug() << "检查线程\n";
    /* 获取控件参数 */
    int numOfThread = ui->threadCount->text().toInt();
    if(numOfThread == NULL || numOfThread < 0 || numOfThread > 1000) {
        QMessageBox::warning(
            this,
            tr("提示"),
            tr("请输入正确的线程数(max=1000)!"),
            QMessageBox::Ok);
        return ;
    }
    

实验步骤四、主线程之任务分配

  • 分配方式

    1. 待扫描的IP数量小于等于线程数:

      每个线程只需扫描一个IP地址,按端口数量分配任务;

      有可能出现刚好跨IP的情况(前一个IP的最后几个端口和下一个IP的前几个端口),方便起见,遇到这种情况时,前一个线程只需扫描完当前IP的最后几个端口即可;而原本在它工作范围之内的剩余IP均分给剩余线程

    2. 带扫描的IP数量大于线程数

      每个线程要扫描多个IP地址,则按IP数量分配任务,每个线程完成分配到的IP的所有端口的扫描

    3. 每次给当线程分配完任务后都动态调整剩余任务的分配

  • 分配实现

    if(numOfThread >= ipCount) {
            // 按线程分配,一个线程最多扫描一个IP
            int taskForSingleThread = totCount / numOfThread, dispatchTask = 0;
            for(int i = 0; i < numOfThread; i++) {
                // 计算出线程需要扫描的IP及端口
                currentIP = netAddr + QString::number(hostAddr + scannedIP, 10);
                currentPort = scannedPort % portCount + startPort;
    
                // 最后一个线程完成剩下的任务即可
                if(i == numOfThread - 1) taskForSingleThread = totCount - scannedPort;
    
                // 如果一个IP剩下的端口小于task,那么该线程少分配一些任务,扫描完当前IP即可
                // 而未分配到的任务将均分至剩下的线程中
                if(endPort - currentPort + 1 < taskForSingleThread) {
                    dispatchTask = endPort - currentPort + 1;
                } else {
                    dispatchTask = taskForSingleThread;
                }
    
                ScanThread* thread = new ScanThread(currentIP, 1, currentPort, dispatchTask);
                QObject::connect(thread, &ScanThread::scanOver, this, &MainWindow::updateResult);
                threadPool[i] = thread;
                thread->start();
                scannedPort += dispatchTask;
                scannedIP = scannedPort / portCount;
    
                // 修正平均工作量
                if((totCount - scannedPort) * 1.0 / (numOfThread - i - 1) > taskForSingleThread) {
                    taskForSingleThread++;
                }
            }
        } else {
            // 按IP分配,每个线程扫描多IP
            int taskForSingleThread = ipCount / numOfThread;
            for(int i = 0; i < numOfThread; i++) {
                currentIP = netAddr + QString::number(hostAddr + scannedIP, 10);
                if(i == numOfThread - 1) {
                    taskForSingleThread = ipCount - scannedIP;
                }
                ScanThread* thread = new ScanThread(currentIP, taskForSingleThread, startPort, portCount);
                QObject::connect(thread, &ScanThread::scanOver, this, &MainWindow::updateResult);
                /* 记录所有开启的线程 */
                threadPool[i] = thread;
                thread->start();
                scannedIP += taskForSingleThread;
    
                if((ipCount - scannedIP) * 1.0 / (numOfThread - i - 1) > taskForSingleThread) {
                    taskForSingleThread++;
                }
            }
        }
    }
    

实验步骤五、主线程之更新UI

  • 收到子线程传递的扫描结果,将其显示在相应区域

    void MainWindow::updateResult(QString info, bool opened) {
        /* 提前结束扫描的标志 */
        if(this->isOver) return ;
        
        /* 显示扫描结果 */
        ui->resultArea->append(info + " " + (opened ? "opened" : "closed"));
        
        /* 记录开启的端口 */
        if(opened) {
            this->openedPort.append(info);
        }
        this->finTask++;
        
        /* 所有任务完成 */ 
        if(this->finTask == this->totTask) { 
            this->endTime = QTime::currentTime();
            ui->resultArea->append("扫描结束,耗时: " + QString::number(this->startTime.msecsTo(this->endTime) / 1000.0));
            /* 显示所有开启的端口 */
            this->showAllOpenedPorts();
        }
    }
    
  • 最后显示所有开启的端口

    void MainWindow::showAllOpenedPorts() {
        ui->resultArea->append("-------------------------------------------\n开启的IP及端口号如下");
        int numOfOpenedPort = this->openedPort.size();
        for(int i = 0; i < numOfOpenedPort; i++) {
            ui->resultArea->append(this->openedPort.at(i));
        }
        ui->resultArea->append("-------------------------------------------\n本次扫描完成\n");
        qDebug() << "over";
    }
    

实验步骤六、主线程之结束子线程

  • 由于提供了提前终止扫描的按钮,需要适当的处理子线程

    void MainWindow::terminateScan() {
        
        /* 提示用户确定终止扫描 */
        int confirm = QMessageBox::warning(
                    this,
                    tr("提示"),
                    tr("您确定要提前结束扫描吗?"),
                    QMessageBox::Ok, QMessageBox::Cancel);
        if(confirm != QMessageBox::Ok) {
            return ;
        }
        
        /* 设置终止标志位,组织UI继续更新 */
        this->isOver = true;
        
        /* 遍历所有子线程 */
        for(int i = 0; i < 64; i++) {
            if(threadPool[i] != NULL) {
                /* 向子线程发出终止请求 */
                threadPool[i]->requestInterruption();
                /* 等待子线程退出 */
                threadPool[i]->wait();
                qDebug() << "stop" << threadPool[i] << "\n";
            }
        }
        ui->resultArea->append("-------------------------------------------\n本次扫描已终止\n");
        qDebug() << "stopped\n";
    }
    

实验步骤七、子线程之扫描端口

  • 主线程给子线程分配任务时,传递了以下参数

    1. ip:待扫描的起始IP
    2. ipCount:待扫描的IP数量
    3. port:待扫描的起始端口
    4. portCount:待扫描的端口数量
  • 扫描实现

    void ScanThread::run() {
    
        QTcpSocket* conn = new QTcpSocket();
    
        /* 获得网络号 */
        QString netAddr = ip.left(ip.lastIndexOf('.') + 1);
        /* 获得主机号 */
        int hostAddr = ip.right(ip.length() - ip.lastIndexOf('.') - 1).toInt();
    
        /* 遍历所有主机(IP) */
        QString curIP;
        int curPort;
        /* 每次循环开始时检查主线程是否发出了终止请求 */
        for(int i = 0; !isInterruptionRequested() && i < ipCount; i++) {
            /* 获得待扫描的IP */
            curIP = netAddr + QString::number(hostAddr + i);
            /* 扫描端口 */
            for(int j = 0; !isInterruptionRequested() && j < portCount; j++) {
                qDebug() << "线程" << QThread::currentThreadId() << " 开始扫描 " << curIP << ":" << port << "\n";
                curPort = port + j;
                /* 向该端口发送连接请求 */
                conn->connectToHost(curIP, curPort);
                /* 等待一段时间,得到连接结果 */
                bool res = conn->waitForConnected(1000);
                /* 在向主线程发送更新信号时,判断一下当前扫描是否已终止,这是由于信号的延迟会导致UI冲突 */
                if(!isInterruptionRequested()) {
                    /* 向主线程发送扫描完成的信号并传递结果 */
                    emit scanOver(curIP + ":" + QString::number(curPort), res);
                }
                if(res) {
                    qDebug() << "opened";
                } else {
                    qDebug() << "closed";
                }
                /* 断开连接 */
                conn->disconnectFromHost();
            }
        }
    
        qDebug() << "线程" << QThread::currentThreadId() << " 扫描完成" << "\n";
    }
    

总结

  • 时间比较紧,处理线程任务分配和提前终止最麻烦
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值