本文由掌控安全学院 -
1198950xxx
投稿
前言
这里介绍一些PyQt的基本使用,以及一些常用的组件,以及如何使用Qt Designer设计图形化界面。
一篇文章教会小白写图形化界面工具。
1.PyQt介绍与安装
PyQt的开发者是英国的“Riverbank Computing”公司。它提供了GPL(简单的说,以GPL协议发布到网上的素材,你可以使用,也可以更改,但是经过你更改然后再次发布的素材必须也遵守GPL协议,主要要求是必须开源,而且不能删减原作者的声明信息等)与商业协议两种授权方式,因此它可以免费地用于自由软件的开发。
PyQt是Python语言的GUI(Graphical User Interface,简称 GUI,又称图形用户接口)编程解决方案之一
可以用来代替Python内置的Tkinter。其它替代者还有PyGTK、wxPython等,与Qt一样,PyQt是一个自由软件
安装PyQt5
pip install pyqt5 -i https://mirrors.aliyun.com/pypi/simple/
2.PyQt5使用示例
import sys
fromPyQt5.QtWidgetsimportQApplication,QWidget
if __name__ =='__main__':
app =QApplication(sys.argv)
w =QWidget()
# 设置窗口标题
w.setWindowTitle("第一个PyQt")
# 展示窗口
w.show()
# 程序进行循环等待状态
app.exec()
3.PyQt5组件以及布局
这里的组件和布局比较简单,下面链接介绍比较全面
https://doc.itprojects.cn/0001.zhishi/python.0008.pyqt5rumen/index.html#/README
4.Qt Designer介绍
纯靠代码来编写界面,效率属实是有点底,介绍一个辅助设计图形化的软件 QT Designer。
可以直接拖动组件设计ui界面。
保存后的文件为ui文件
若要加载ui文件,则需要导入 uic 模块 , 它位于PyQt5 中
import sys
fromPyQt5.QtWidgetsimportQApplication
fromPyQt5import uic
if __name__ =='__main__':
app =QApplication(sys.argv)
ui = uic.loadUi("./untitled.ui")
# 展示窗口
ui.show()
app.exec()
不过使用.ui去调用就无法和py文件一起打包成exe,这里可以使用pyuic5将ui文件转换成python文件
在cmd中执行
pyuic5 -o .py .ui
pyuic5 -o tishi_ui.py tishi.ui
调用py文件
fromPyQt5.QtCoreimportQt
from.tishi_ui importUi_Form
fromPyQt5.QtWidgetsimportQWidget
classTiShi(QWidget,Ui_Form):
def __init__(self):
super(TiShi, self).__init__()
self.setupUi(self)# 使用 sjui.Ui_Form 类中的方法初始化 UI
self.setWindowFlags(Qt.WindowCloseButtonHint)
5.Pyqt5项目 Xray-Gui
这项目是通过pyqt5实现的一个Xray的图形界面,方便用户使用。
项目地址: https://github.com/buluorifu/Xray-Gui
构思
文件夹结构
效果图
使用Qt Designer设计界面
1. 打开Qt Designer,并设计好ui界面
保存好会生成.ui文件
2. 将ui文件转化为py文件
pyuic5 -o ***.py ***.ui
3.调用py文件打开界面
import sys
fromPyQt5.QtGuiimportQIcon
from configuration.box1 import BOX1
from configuration.box2 import BOX2
from configuration.box3 import BOX3
from configuration.homepage importUi_Form
fromPyQt5.QtWidgetsimportQWidget,QApplication,QStackedLayout
classMyWindow(QWidget,Ui_Form):
def __init__(self):
super(MyWindow, self).__init__()
self.setupUi(self)# 使用 sjui.Ui_Form 类中的方法初始化 UI
self.init_ui()
self.sub_window =None# 存储子窗口对象的属性
def init_ui(self):
self.qsl =QStackedLayout(self.groupBox_2)
self.box1 = BOX1()
self.box2 = BOX2()
self.box3 = BOX3()
self.qsl.addWidget(self.box1)
self.qsl.addWidget(self.box2)
self.qsl.addWidget(self.box3)
# 给按钮添加事件(即点击后要调用的函数)
self.pushButton.clicked.connect(self.btn_press1_clicked)
self.pushButton_2.clicked.connect(self.btn_press2_clicked)
self.pushButton_3.clicked.connect(self.btn_press3_clicked)
# 设置按钮1的初始样式
self.pushButton.setStyleSheet("background-color: #CAE1FF;")
def btn_press1_clicked(self):
self.qsl.setCurrentIndex(0)
self.pushButton.setStyleSheet("background-color: #CAE1FF;")
self.pushButton_2.setStyleSheet("")# 恢复按钮2的默认样式
self.pushButton_3.setStyleSheet("")# 恢复按钮3的默认样式
def btn_press2_clicked(self):
self.qsl.setCurrentIndex(1)
self.pushButton.setStyleSheet("")# 恢复按钮1的默认样式
self.pushButton_2.setStyleSheet("background-color: #CAE1FF;")
self.pushButton_3.setStyleSheet("")# 恢复按钮3的默认样式
def btn_press3_clicked(self):
self.qsl.setCurrentIndex(2)
self.pushButton.setStyleSheet("")# 恢复按钮1的默认样式
self.pushButton_2.setStyleSheet("")# 恢复按钮2的默认样式
self.pushButton_3.setStyleSheet("background-color: #CAE1FF;")
def closeEvent(self, event):
# 关闭其他窗口的代码
for widget inQApplication.topLevelWidgets():
if isinstance(widget,QWidget)and widget != self:
widget.close()
event.accept()
if __name__ =='__main__':
app =QApplication(sys.argv)
w =MyWindow()
icon =QIcon('./img/扫描.png')#添加图标
w.setWindowIcon(icon)
w.show()
sys.exit(app.exec_())
我们在box1、box2、box3各把ui里面的代码导入即可,以box1举例
fromPyQt5.QtWidgetsimportQWidget
from.box1_ui importUi_Form
class BOX1(QWidget,Ui_Form):
def __init__(self):
super(QWidget, self).__init__()
self.setupUi(self)# 使用 sjui.Ui_Form 类中的方法初始化 UI
self.init_ui()
4.功能调用介绍
self.init_ui()创建对象时自动调用
init_ui()里面主要为按钮连接的方法
读取文件功能,获取文件路径
fileName, fileType =QtWidgets.QFileDialog.getOpenFileName(None,"选取文件", os.getcwd(),"All Files(*.exe);")
self.lineEdit.setText(fileName)
创建子进程,这里不创建子进程会把整个程序阻塞
# 创建子进程
def process_creation(self):
self.process =QProcess()
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.readyReadStandardOutput.connect(self.handle_output)
self.process.finished.connect(self.handle_finished)
self.process.start(self.lineEdit.text())
# 命令输出
def handle_output(self):
try:
data = self.process.readAll().data().decode('utf-8').rstrip()
# 将命令行输出至文本框
self.textEdit.append(data)
except:
data = self.process.readAll().data().decode('latin-1').rstrip()
# 将命令行输出至文本框
self.textEdit.append(data)
这里可以对输出的文本进行优化一下颜色,不然是全黑色字体和xray原本输出的颜色不符
通过正则匹配,对不同字段进行不同颜色的输出
# 命令输出
def handle_output(self):
try:
data = self.process.readAll().data().decode('utf-8').strip()
lines = data.split('n')
for line in lines:
if"[INFO]"in line:
line = line.replace("[INFO]", f'<span style="color: blue;">[INFO]</span>')
elif"[Vuln: dirscan]"in line:
line = line.replace("[Vuln: dirscan]", f'<span style="color: red;">[Vuln: dirscan]</span>')
elif line.startswith('t'):
line = f'<span style="color: purple;"> {line}</span>'
elif re.match(r'^[u4e00-u9fff]', line):
line = f'<span style="color: red;">{line}</span>'
elif"requestSent"in line:
line = f'<span style="color: #FFBF00;">{line}</span>'
elif"All pending"in line:
line = f'<span style="color: #00FF7F;">{line}</span>'
self.textEdit.append(line)
except:
data = self.process.readAll().data().decode('latin-1').strip()
lines = data.split('n')
for line in lines:
if"[INFO]"in line:
line = line.replace("[INFO]", f'<span style="color: blue;">[INFO]</span>')
elif"[Vuln: dirscan]"in line:
line = line.replace("[Vuln: dirscan]", f'<span style="color: red;">[Vuln: dirscan]</span>')
elif line.startswith('t'):
line = f'<span style="color: purple;"> {line}</span>'
elif re.match(r'^[u4e00-u9fff]', line):
line = f'<span style="color: red;">{line}</span>'
elif"requestSent"in line:
line = f'<span style="color: #FFBF00;">{line}</span>'
elif"All pending"in line:
line = f'<span style="color: #00FF7F;">{line}</span>'
self.textEdit.append(line)
修改配置文件主要在于修改config.yaml文件,这里用import ruamel.yaml这个库。
# 确认修改基础配置
def basics_save(self):
if os.path.exists('file_address.yaml'):
with open('config.yaml','r', encoding='utf-8')as file:
yaml = ruamel.yaml.YAML()
config = yaml.load(file)
config['parallel']= int(self.spinBox.text())
config['http']['dial_timeout']= int(self.spinBox_2.text())
config['http']['max_redirect']= int(self.spinBox_3.text())
config['http']['max_qps']= int(self.spinBox_4.text())
with open('config.yaml','w', encoding='utf-8')as file:
yaml.dump(config, file)
self.open_tishiwindow(None,None)
else:
text ='未配置xray文件'
img ='./img/失败.png'
self.open_tishiwindow(text, img)
然后通过子进程去输出命令行,并调用handle_output方法输出到文本
这里通过获取界面的选项来拼接命令行
然后config界面和rad界面同理
def initiative_scan(self):
if os.path.exists('file_address.yaml'):
self.textEdit.clear()
self.process_kill()
ifnot hasattr(self,'is_first_click'):
if self.lineEdit_2.text()==""or self.lineEdit_2.text()=="默认则随机命名":
self.dict['name']= str(int(time.time()))
else:
self.dict['name']= self.lineEdit_2.text()
# 第一次按下按钮
self.is_first_click =True
self.process_kill()
with open('file_address.yaml','r', encoding='utf-8')as file:
yaml = ruamel.yaml.YAML()
config = yaml.load(file)
self.args = config['xray_address']
if self.radioButton_5.isChecked():
self.args = self.args +' --log-level debug'
if self.radioButton_6.isChecked():
self.args = self.args +' --log-level info'
if self.radioButton_7.isChecked():
self.args = self.args +' --log-level warn'
if self.radioButton_8.isChecked():
self.args = self.args +' --log-level error'
if self.radioButton_9.isChecked():
self.args = self.args +' --log-level fatal'
self.args = self.args +' webscan'
if self.radioButton_11.isChecked():
self.args = self.args +' --level medium'
if self.radioButton_12.isChecked():
self.args = self.args +' --level high'
if self.radioButton_13.isChecked():
self.args = self.args +' --level critical'
if self.dict['url']:
self.args = self.args +' --url '+ self.dict['url']
if self.dict['request']:
self.args = self.args +' --raw-request '+ self.dict['request']
if self.dict['url_list']:
self.args = self.args +' --url-file '+ self.dict['url_list']
if self.radioButton.isChecked():
self.args = self.args +' --html-output '+ self.dict['name']+'.html'
self.dict['name_all']= os.path.dirname(config['xray_address'])+'/'+ self.dict['name']+'.html'
if self.radioButton_2.isChecked():
self.args = self.args +' --json-output '+ self.dict['name']+'.txt'
self.dict['name_all']= os.path.dirname(config['xray_address'])+'/'+ self.dict['name']+'.txt'
self.pushButton_3.setText("关闭主动扫描")
self.process_creation()
self.process.start(self.args)
else:
# 第二次按下按钮
del self.is_first_click # 删除标记
self.pushButton_3.setText("开启主动扫描")
self.textEdit.clear()
self.process_kill()
else:
text ='未配置xray文件'
img ='./img/失败.png'
self.open_tishiwindow(text, img)
被动代理效果,红色即是扫出的漏洞
打开输出文件
# 查看扫描结果
def file_look(self):
try:
if self.dict['name_all']:
os.startfile(self.dict['name_all'])
else:
text ='没有输出文件'
img ='./img/失败.png'
self.open_tishiwindow(text, img)
except:
text ='没有输出文件'
img ='./img/失败.png'
self.open_tishiwindow(text, img)
加解密界面主要通过self.textEdit.textChanged.connect(self.encrypt_txt)去对输入的文本进行处理
这里可以创建一个线程,去执行加密操作,避免阻塞界面
def start_encrypt_txt(self):
t = threading.Thread(target=self.encrypt_txt)
t.daemon =True
t.start()
md5的界面主要通过字典匹配,如果匹配不到,就返回无法解密
found_match =False# 标记是否找到匹配的解密结果
with open('./dict/top1w-md5.txt','r', encoding='utf-8')as fp:
for line in fp:
if str_txt == line.strip():
# 如果找到了匹配的MD5哈希值,我们可以从原始文件中获取相应的单词
with open("./dict/top1w.txt",'r', encoding='utf-8')as wp:
for word_line in wp:
words = word_line.split()
for word in words:
if hashlib.md5(word.encode('utf-8')).hexdigest()== str_txt:
found_match =True
self.textEdit_6.setText(word)
break
if found_match:
break
ifnot found_match:
self.textEdit_6.setText('无法解密')
5.把python打包成exe文件
- 安装pyinstaller
首先我们要先安装Pyinstaller,直接在cmd使用pip命令
pip install pyinstaller
2、执行命令,从程序开始的地方打包,生成exe文件
末尾出现Building EXE from EXE-00.toc completed successfully.代表打包成功
打包后在当前路径dist目录下面生成exe文件
Pyinstaller-F -w -i 扫描.ico ui.py
一个图形化工具就写完了
结尾
感谢大家的观看,求个Star!https://github.com/buluorifu/Xray-Gui
原文始发于微信公众号(掌控安全EDU):使用pyqt5来开发图形化工具
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论