本章目标

加上这章,下一章应该就能结束了。

大家是否还记得GUI上的一个“复制全文”按钮和已经实现功能的“复制原文”按钮。

之所以有“复制全文”按钮,是因为有些服务器开着端口,但并不会输出该云端运行的服务,所以可以提供端口参照。

所以这一章就是实现:

1、当端口是开启状态,但无运行服务的说明输出时,匹配一个常见服务参照并跟随输出;

2、不影响 复制原文 按钮,实现复制全文按钮功能。

步骤实施

我找到了一篇博文:

端口与服务对照表

在目录下新建文件 PortText.py ,将博文里的对照表复制下来后,替换掉了里面的“端口”两字,保存下来的数据格式如下:

python安全应用编程入门 python写安全工具_python

检查了下中间的分割符号“:”,发现服务文字里没有这个符号,可以安心使用。

我需要实现一个函数,输入端口号,就能输出参照服务内容的文字说明。

def port_text(port):
    port_string = str(port)
    if str(port_string) not in TEXT:
        return ''
    text_list = TEXT.split('\n')  # 先根据 换行符 分割字符串
    for ele in text_list:
        ele_list = ele.split(':')
        if port_string == ele_list[0]:
            return ele_list[1]
    return ''

试着调用一下:

print(port_text(65301))

python安全应用编程入门 python写安全工具_字符串_02

成功。

到PortSearchGUI中引入该模块:

import PortText

再修改 logic 函数:

# 函数B 负责调用逻辑
def logic(ip, thread_line, port_start, port_end):
    search = PortSearch(ip=ip, thread_line=thread_line, port_start=port_start, port_end=port_end)
    result = search.run()
    string = ''
    string1 = ''
    str_len = len(result)
    report_box_edit.setPlainText('')
    if str_len > 0:  # 处理返回数据,输出到编辑框内
        for ele in result:
            string = string + '端口:' + str(ele[1]) + '  ,状态:开启,  输出信息:' + str(ele[3]) + "\n"
            if ele[3] == b'':  # 如果没有端口说明,则输出参考端口服务说明
                str_temp = '端口:' + str(ele[1]) + '  ,状态:开启,  输出信息:' + str(ele[3]) + \
                          '  ,参考端口服务说明:' + PortText.port_text(ele[1])
                string1 = string1 + str_temp + "\n"
                report_box_edit.appendPlainText(str_temp)  # 逐条添加信息
            else:
                str_temp = '端口:' + str(ele[1]) + '  ,状态:开启,  输出信息:' + str(ele[3])
                string1 = string1 + str_temp + "\n"
                report_box_edit.appendPlainText(str_temp)  # 逐条添加信息
    else:
        string = '所有端口均未开启'
        report_box_edit.setPlainText(string)
    G_LIST[0] = string  # 保存原文结果
    G_LIST[1] = string1  # 保存全文结果
    start_btn.setEnabled(True)
    window.setWindowTitle('端口嗅探器 v1.0')

运行之后效果如下:

python安全应用编程入门 python写安全工具_嗅探器_03

写一下 复制全文 按钮的响应函数和提示语:

MESSAGE = (
    'ip或者网址不得为空',
    '并发数不得小于1',
    '开始端口号不得小于0',
    '结束端口号需要大于开始端口号',
    '原文复制成功',
    '全文复制成功'
)


@Slot()
def copy_all():
    pyperclip.copy(G_LIST[1])
    show_tip(MESSAGE[5])

试运行了下,发现一个以前隐藏的性能问题,程序会崩溃。

崩溃时,输出内容是:

退出代码-1073741819 (0xC0000005)

这个问题困扰了我很久,查了很多资料,发现都不能解决问题。

问题基本上锁定为内存栈溢出导致。

经过将代码封装为类,逐步定位,终于工具运行比较稳定了,方法是将之前使用的 

self.report_box_edit.appendPlainText()

方法,替换成了 

self.report_box_edit.setPlainText()

可能是因为减少了对其内存的访问次数,所以就稳定了许多,当然速度也变快了。

完整代码贴出来:

# coding=utf-8

from PySide2.QtWidgets import QApplication, QLineEdit, QLabel, QPlainTextEdit, QPushButton, QWidget  # 引入模块
from PySide2.QtWidgets import QMessageBox
from PySide2.QtWidgets import QGroupBox, QVBoxLayout, QHBoxLayout  # 布局容器
import sys
from PySide2.QtCore import Slot  # 插槽模块
from PortSearch import PortSearch
import PortText
import threading
import pyperclip
import time


class PortSearchGUI(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.resize(600, 480)    # 主窗体尺寸
        self.setWindowTitle('端口嗅探器 v1.0')  # 窗体名称

        self.ip_line_edit = QLineEdit()  # 修改父类
        self.ip_line_edit.setPlaceholderText('输入ip或者网址')
        self.ip_line_edit.setMinimumSize(180, 22)

        self.thread_label = QLabel('并发数:')
        self.thread_line_edit = QLineEdit()  # 修改父类
        self.thread_line_edit.setText('300')
        self.thread_line_edit.setMinimumSize(40, 22)

        self.port_label = QLabel('端口范围:')
        self.port_line_edit1 = QLineEdit()  # 修改父类
        self.port_line_edit1.setText('0')
        self.port_label2 = QLabel('~')
        self.port_line_edit2 = QLineEdit()  # 修改父类
        self.port_line_edit2.setText('65535')

        self.report_box_edit = QPlainTextEdit()  # 修改父类
        self.report_box_edit.setReadOnly(True)

        self.start_btn = QPushButton()  # 修改父类
        self.start_btn.setText('启动')
        self.start_btn.clicked.connect(self.collect_data)  # 建立连接

        self.copy_all_btn = QPushButton()  # 修改父类
        self.copy_all_btn.setText('复制全文')
        self.copy_all_btn.clicked.connect(self.copy_all)  # 建立连接

        self.copy_ori_btn = QPushButton()  # 修改父类
        self.copy_ori_btn.setText('复制原文')
        self.copy_ori_btn.clicked.connect(self.copy)  # 建立连接

        self.clear_btn = QPushButton()  # 修改父类
        self.clear_btn.setText('清空')
        self.clear_btn.clicked.connect(self.clear)  # 建立连接

        self.first_group_box = QGroupBox()  # 第一个分组框组控件
        self.first_group_box.setTitle('参数设置')
        self.second_group_box = QGroupBox()  # 第二个分组框组控件
        self.second_group_box.setTitle('端口开放情况')

        self.first_h_layout = QHBoxLayout()  # 第一个横向布局容器,属于第一个分组框控件
        self.second_h_layout = QHBoxLayout()  # 第二个横向布局容器,属于第一个分组框控件
        self.third_h_layout = QHBoxLayout()  # 第三个横向布局容器,属于第一个分组框控件

        self.first_v_layout = QVBoxLayout()  # 第一个纵向布局容器,属于第二个横向布局容器
        self.second_v_layout = QVBoxLayout()  # 第二个纵向布局容器,属于第二个横向布局容器

        self.first_h_layout.addWidget(self.ip_line_edit)
        self.first_h_layout.addWidget(self.thread_label)
        self.first_h_layout.addWidget(self.thread_line_edit)
        self.first_h_layout.addWidget(self.port_label)
        self.first_h_layout.addWidget(self.port_line_edit1)
        self.first_h_layout.addWidget(self.port_label2)
        self.first_h_layout.addWidget(self.port_line_edit2)
        self.first_h_layout.addWidget(self.start_btn)
        self.first_group_box.setLayout(self.first_h_layout)

        self.first_v_layout.addWidget(self.report_box_edit)

        self.second_v_layout.addWidget(self.copy_all_btn)
        self.second_v_layout.addWidget(self.copy_ori_btn)
        self.second_v_layout.addWidget(self.clear_btn)

        self.second_h_layout.addItem(self.first_v_layout)
        self.second_h_layout.addItem(self.second_v_layout)

        self.second_group_box.setLayout(self.second_h_layout)

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.first_group_box)
        self.layout.addWidget(self.second_group_box)
        self.setLayout(self.layout)

        self.MESSAGE = (
            'ip或者网址不得为空',
            '并发数不得小于1',
            '开始端口号不得小于0',
            '结束端口号需要大于开始端口号',
            '原文复制成功',
            '全文复制成功'
        )

        self.G_LIST = ['', '']

        self.ip = ''
        self.thread_line = 0
        self.port_start = 0
        self.port_end = 0

    def show_tip(self, message):  # 遇到问题,则丢到这里,抛到界面上
        tip = QMessageBox(self)
        tip.setWindowTitle('提示')
        tip.setText(message)
        tip.show()

        # 函数B 负责调用逻辑
    def logic(self):
        search = PortSearch(ip=self.ip, thread_line=self.thread_line, port_start=self.port_start, port_end=self.port_end)
        result = search.run()
        string = ''
        string1 = ''
        str_len = len(result)
        self.report_box_edit.setPlainText('')
        if str_len > 0:  # 处理返回数据,输出到编辑框内
            for ele in result:
                string = string + '端口:' + str(ele[1]) + '  ,状态:开启,  输出信息:' + str(ele[3]) + "\n"
                time.sleep(1)
                if ele[3] == b'':  # 如果没有端口说明,则输出参考端口服务说明
                    string1 = string1 + '端口:' + str(ele[1]) + ',状态:开启,输出信息:无' + ',参考端口服务说明:' + \
                              PortText.port_text(ele[1]) + "\n"
                else:
                    string1 = string1 + '端口:' + str(ele[1]) + ',状态:开启,输出信息:' + str(ele[3]) + "\n"
        else:
            string1 = '所有端口均未开启'
        self.G_LIST[0] = string  # 保存原文结果
        self.G_LIST[1] = string1  # 保存全文结果
        self.report_box_edit.setPlainText(string1)
        self.start_btn.setEnabled(True)
        self.setWindowTitle('端口嗅探器 v1.0')

    @Slot()
    def collect_data(self):
        self.ip = self.ip_line_edit.text()
        self.thread_line = int(self.thread_line_edit.text())
        self.port_start = int(self.port_line_edit1.text())
        self.port_end = int(self.port_line_edit2.text())
        if not self.ip:
            self.show_tip(self.MESSAGE[0])
            return
        if self.thread_line < 1:
            self.show_tip(self.MESSAGE[1])
            return
        if self.port_start < 0:
            self.show_tip(self.MESSAGE[2])
            return
        if self.port_end <= self.port_start:
            self.show_tip(self.MESSAGE[3])
            return
        thread = threading.Thread(target=self.logic)
        thread.start()  # 异步调用,避免工具卡死
        self.start_btn.setEnabled(False)  # 限制按钮,避免重复调用
        self.setWindowTitle('端口嗅探器 v1.0  ----  执行中')

    @Slot()
    def copy(self):
        pyperclip.copy(self.G_LIST[0])
        self.show_tip(self.MESSAGE[4])

    @Slot()
    def copy_all(self):
        pyperclip.copy(self.G_LIST[1])
        self.show_tip(self.MESSAGE[5])

    @Slot()
    def clear(self):
        self.report_box_edit.setPlainText('')


app = QApplication(sys.argv)     # 创建app

window = PortSearchGUI()    # 创建主窗体
window.show()    # 显示窗体

app.exec_()    # 启动app
sys.exit()

章节总结

本章目标完成。

下一章节还需要实现是工具打包成exe可执行文件,下一章应该就能结束本系列的文章了。