项目场景:
版本python3.97
系统:windows10
描述
支持拖放和手动导入文件、递归加密自写的模块代码
把py文件自动转为pyd,无法进行反编译,只能反汇编,
破解难度直接拉高
模块
pip install pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install pyarmor
pip install pyarmor-webui
cython安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple cython
软件
需要C => CPython => pyd
Visual Studio 安装教程参考
必须安装以下
一:C++桌面开发
二:MSVC v143 生成工具
三:windows10的SDK
准备好了吗,上代码↓↓
代码:
#!/usr/bin/python3
# encoding:UTF-8
import os
import re
import shutil
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit, QCheckBox, QFormLayout, QHBoxLayout, \
QVBoxLayout, QApplication, QListWidget, QLabel, QListWidgetItem, QFileDialog, QGroupBox
import sys
import Pretreatment
# Pretreatment是自己写的提示框,自行更改即可
class TableDemo(QWidget):
"""窗口类"""
def __init__(self):
super().__init__()
self.setFixedSize(600, 400)
self.setFont(QFont("黑体", 12))
self.setWindowTitle('Py文件编译')
# 打开拖放功能
self.setAcceptDrops(True)
self.dir_list = []
self.box_list = []
self.file_mulu = []
mulu_button = QPushButton("导入 ", self)
mulu_button.clicked.connect(self.click_mulu_button)
button1 = QPushButton("Pyinstaller", self)
button1.clicked.connect(self.clickButton1)
button2 = QPushButton(".py to .pyd", self)
button2.clicked.connect(self.clickButton2)
button3 = QPushButton("Pyarmor to exe", self)
button3.clicked.connect(self.clickButton3)
self.list_box = QListWidget(self)
self.list_box.setMaximumWidth(200)
self.list_box.setFont(QFont("微软雅黑", 11))
gridlayout1 = QFormLayout()
gridlayout1.addRow(mulu_button, button2)
vbox1 = QVBoxLayout()
vbox1.addLayout(gridlayout1)
vbox1.addWidget(button1)
vbox1.addWidget(button3)
vbox1.addWidget(self.list_box)
self.CheckBox1 = QCheckBox('编译为单个exe文件')
self.CheckBox2 = QCheckBox('编译为目录exe文件')
self.CheckBox5 = QCheckBox('不显示命令行窗口')
self.CheckBox6 = QCheckBox('显示命令行窗口')
self.CheckBox7 = QCheckBox('指定模块路径,;分隔符')
self.CheckBox8 = QCheckBox('ico图标')
self.CheckBox9 = QCheckBox('UPX加壳')
self.CheckBox12 = QCheckBox('编译前pyd加密')
self.CheckBox1.setChecked(True)
self.CheckBox2.setChecked(False)
self.CheckBox5.setChecked(True)
self.CheckBox6.setChecked(False)
self.CheckBox1.stateChanged.connect(self.CheckBox01)
self.CheckBox2.stateChanged.connect(self.CheckBox02)
self.CheckBox5.stateChanged.connect(self.CheckBox05)
self.CheckBox6.stateChanged.connect(self.CheckBox06)
self.CheckBox7.stateChanged.connect(self.CheckBox07)
self.CheckBox8.stateChanged.connect(self.CheckBox08)
self.CheckBox9.stateChanged.connect(self.CheckBox09)
label7 = QLabel('模块路径:')
label8 = QLabel('ico图标:')
label9 = QLabel('UPX路径:')
self.lineEdit7 = QLineEdit()
self.lineEdit8 = QLineEdit()
self.lineEdit9 = QLineEdit()
self.pyini = {'模块路径': '', 'ico图标': '', 'UPX': os.path.join(os.getcwd(), 'upx\\upx.exe'), '保存位置': os.path.join(os.getcwd(), 'py_exe')}
self.tmpe_path = os.path.join(os.getcwd(), 'tmp')
if os.path.exists(os.path.join(os.getcwd(), 'Py_ini.txt')):
with open(os.path.join(os.getcwd(), 'Py_ini.txt'), 'r', encoding='utf-8') as F01:
txt_list = F01.read().split('\n')
F01.close()
for i1 in txt_list:
if '=' in i1:
txt = i1.split('=')
if txt[0] in self.pyini.keys():
if len(txt[1]) > 2:
self.pyini[txt[0]] = txt[1]
self.mulu_check = False
if self.pyini['模块路径'] != '':
self.lineEdit7.setText(self.pyini['模块路径'])
self.CheckBox7.setChecked(True)
if self.pyini['ico图标'] != '':
self.lineEdit8.setText(self.pyini['ico图标'])
self.CheckBox8.setChecked(True)
if os.path.exists(self.pyini['UPX']):
self.lineEdit9.setText(self.pyini['UPX'])
self.CheckBox9.setChecked(True)
if not os.path.exists(self.pyini['保存位置']):
os.makedirs(self.pyini['保存位置'])
if not os.path.exists(self.tmpe_path):
os.makedirs(self.tmpe_path)
else:
shutil.rmtree(self.tmpe_path)
os.makedirs(self.tmpe_path)
self.mulu_check = True
vbox2 = QVBoxLayout()
vbox2.addWidget(self.CheckBox1)
vbox2.addWidget(self.CheckBox2)
vbox2.addWidget(self.CheckBox5)
vbox2.addWidget(self.CheckBox6)
gridlayout2 = QFormLayout()
gridlayout2.addRow(label7, self.lineEdit7)
gridlayout2.addRow(label8, self.lineEdit8)
gridlayout2.addRow(label9, self.lineEdit9)
vbox2.addWidget(self.CheckBox7)
vbox2.addWidget(self.CheckBox8)
vbox2.addWidget(self.CheckBox9)
vbox2.addWidget(self.CheckBox12)
vbox2.addLayout(gridlayout2)
group = QGroupBox('编译选择项', self)
group.setLayout(vbox2)
hbox10 = QHBoxLayout()
hbox10.addLayout(vbox1)
hbox10.addWidget(group)
self.setLayout(hbox10)
self.show()
def dragEnterEvent(self, evn):
# 鼠标拖入事件
# 鼠标放开函数事件
evn.accept()
def dropEvent(self, evn):
# 鼠标放开执行
# os.path.isdir 判断某一路径是否为目录
# os.path.isfile 判断某一路径是否为文件
self.list_box.clear()
self.box_list.clear()
self.dir_list.clear()
self.file_mulu = evn.mimeData().text().replace('file:///', '').split('\n')
dir_list1 = []
for i0 in self.file_mulu:
if i0 != '':
if os.path.isdir(i0):
dir_list1.append(i0)
else:
dir_list1.append(os.path.dirname(i0))
self.dir_list = list(dict.fromkeys(dir_list1))
for i0 in self.file_mulu:
if i0 != '':
if os.path.isdir(i0):
# 添加py文件到列表
for i1 in os.listdir(i0):
if os.path.isfile(os.path.join(i0, i1)):
if i1[-2:] == 'py':
if i1 in self.box_list:
Pretreatment.message_box('提示', i1 + '重复;')
continue
box = QCheckBox(os.path.basename(i1))
item = QListWidgetItem() # 实例化一个Item,QListWidget,不能直接加入QCheckBox
self.list_box.addItem(item) # 把QListWidgetItem加入QListWidget
self.list_box.setItemWidget(item, box) # 再把QCheckBox加入QListWidgetItem
self.box_list.append(box)
else:
if os.path.basename(i0)[-2:] == 'py':
if i0 in self.box_list:
Pretreatment.message_box('提示', i0 + '重复;')
continue
box = QCheckBox(os.path.basename(i0))
item = QListWidgetItem() # 实例化一个Item,QListWidget,不能直接加入QCheckBox
self.list_box.addItem(item) # 把QListWidgetItem加入QListWidget
self.list_box.setItemWidget(item, box) # 再把QCheckBox加入QListWidgetItem
self.box_list.append(box)
def click_mulu_button(self):
self.list_box.clear()
self.box_list.clear()
self.dir_list.clear()
self.file_mulu = QFileDialog.getOpenFileNames(self, "选择文件", "/", "python (*.py)")[0]
if len(self.file_mulu) > 0:
self.dir_list.append(os.path.dirname(self.file_mulu[0]))
for i1 in self.file_mulu:
if i1[-2:] == 'py':
if i1 in self.box_list:
Pretreatment.message_box('提示', i1 + '重复;')
continue
box = QCheckBox(os.path.basename(i1))
item = QListWidgetItem() # 实例化一个Item,QListWidget,不能直接加入QCheckBox
self.list_box.addItem(item) # 把QListWidgetItem加入QListWidget
self.list_box.setItemWidget(item, box) # 再把QCheckBox加入QListWidgetItem
self.box_list.append(box)
def get_imports(self, file_path):
mokuai_list = {'模块列表': [], '模块路径': [], '隐式模块': []}
if ';' in self.lineEdit7.text():
py_from_path = list(filter(None, self.lineEdit7.text().split(';')))
elif self.lineEdit7.text().strip() != '':
py_from_path = [self.lineEdit7.text().strip()]
else:
py_from_path = []
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
file.close()
imports = []
imports2 = []
imports3 = []
line_breaks = False
for line_txt in lines:
if line_breaks:
imports2.append(line_txt.strip().replace('\\', ''))
line_breaks = False
if line_txt.startswith('from') or line_txt.startswith('import'):
if line_txt.endswith('\\'):
line_breaks = True
if ' as ' in line_txt:
line_txt = re.sub('\s(as)\s\S*\n', '', line_txt)
if ' # ' in line_txt:
line_txt = re.sub('(#)\s\S*\n', '', line_txt)
if line_txt.startswith('from'):
mo_path = [line_txt.strip().split('import')[0].replace('from', '').replace(' ', '')]
imports2.extend(mo_path)
imports3.extend(mo_path)
for i2 in mo_path:
for i3 in py_from_path:
if os.path.exists(os.path.join(i3, i2 + '.py')):
imports.append(os.path.normpath(os.path.join(i3, i2 + '.py')))
elif line_txt.startswith('import'):
if ',' in line_txt:
mo_path = line_txt.replace('import', '').replace('\\', '').replace(' ', '').split(',')
imports2.extend(mo_path)
else:
mo_path = [line_txt.strip().replace('import', '').replace('\\', '').replace(' ', '')]
imports2.extend(mo_path)
for i2 in mo_path:
for i3 in py_from_path:
if os.path.exists(os.path.join(i3, i2 + '.py')):
imports.append(os.path.normpath(os.path.join(i3, i2 + '.py')))
mokuai_list['模块列表'].extend(list(filter(None, imports2)))
mokuai_list['模块路径'].extend(imports)
mokuai_list['隐式模块'].extend(imports3)
return mokuai_list
def get_all_imports(self, filepath):
# 递归模式
mokuai_list1 = []
path_list = [filepath]
while len(path_list) > 0:
path_list2 = []
for i1 in path_list:
import_path = self.get_imports(i1)
mokuai_list1.append(import_path)
path_list2.extend(import_path['模块路径'])
path_list = path_list2
return mokuai_list1
def remove_comments(self, file_path):
with open(file_path, 'r', encoding='utf-8') as F01:
txt_list = F01.readlines()
F01.close()
txt2_list = []
for i1 in range(len(txt_list)):
tx = txt_list[i1].strip().replace(' ', '').replace('\n', '').replace('\r', '').replace('\t', '')
if len(tx) > 0:
if tx == '#!/usr/bin/python3' or 'utf-8' in tx or tx[0] != '#':
txt2_list.append(txt_list[i1].replace('\n', ''))
def clickButton1(self):
box_checked = []
for i1 in self.box_list:
if i1.isChecked():
box_checked.append(i1.text())
i1.setChecked(False)
if len(box_checked) == 0:
return
self.hide()
self.pyini['模块路径'] = self.lineEdit7.text()
self.pyini['ico图标'] = self.lineEdit8.text()
self.pyini['UPX'] = self.lineEdit9.text()
if not os.path.exists(self.tmpe_path):
os.makedirs(self.tmpe_path)
else:
shutil.rmtree(self.tmpe_path)
os.makedirs(self.tmpe_path)
if not os.path.exists(self.pyini['保存位置']):
os.makedirs(self.pyini['保存位置'])
else:
shutil.rmtree(self.pyini['保存位置'])
os.makedirs(self.pyini['保存位置'])
with open(os.path.join(os.getcwd(), 'Py_ini.txt'), 'w', encoding='utf-8') as F01:
for key, val in self.pyini.items():
F01.write(key + '=' + val + '\n')
F01.close()
# 递归分析主程序的自写模块/模块路径
# 模块转为pyd
# 选择主程序,自动加密被调用的模块
for i1 in box_checked:
for dir_i2 in self.dir_list:
dir_path = os.path.join(dir_i2, i1)
if os.path.exists(dir_path):
if self.CheckBox12.isChecked():
mokuai_list = self.get_all_imports(dir_path)
txt_list = []
txt1_list = []
for i2 in mokuai_list:
txt_list.extend(i2['模块列表'])
txt1_list.extend(i2['模块路径'])
if len(txt1_list) > 0:
p_list = list(dict.fromkeys(txt1_list))
self.py_to_pyd(p_list)
txt_list = list(dict.fromkeys(txt_list))
if len(txt_list) > 0:
'\n'.join(list(dict.fromkeys(txt_list)))
txt2_list = self.get_imports(dir_path)['模块列表']
for i3 in txt2_list:
if i3 in txt_list:
txt_list.remove(i3)
# 在主程序模块后面追加其他模块
with open(dir_path, 'r', encoding='utf-8') as F01:
ptxt_list = F01.readlines()
F01.close()
for i4 in ptxt_list:
if i4.startswith('from') or i4.startswith('import'):
for i5 in txt_list:
ptxt_list.insert(ptxt_list.index(i4), 'import ' + i5 + '\n')
break
with open(os.path.join(self.tmpe_path, i1), 'w', encoding='utf-8') as F01:
for i6 in ptxt_list:
F01.write(i6)
F01.close()
# 清除源码注释
# self.remove_comments(os.path.join(self.tmpe_path, i1))
self.pyinstaller_exe(os.path.join(self.tmpe_path, i1))
else:
self.pyinstaller_exe(os.path.join(dir_i2, i1))
print('运行完成;')
self.show()
def clickButton2(self):
box_checked = []
for i1 in self.box_list:
if i1.isChecked():
box_checked.append(i1.text())
i1.setChecked(False)
if len(box_checked) == 0:
return
self.pyini['模块路径'] = self.lineEdit7.text()
self.pyini['ico图标'] = self.lineEdit8.text()
self.pyini['UPX'] = self.lineEdit9.text()
if not os.path.exists(self.tmpe_path):
os.makedirs(self.tmpe_path)
else:
shutil.rmtree(self.tmpe_path)
os.makedirs(self.tmpe_path)
if not os.path.exists(self.pyini['保存位置']):
os.makedirs(self.pyini['保存位置'])
else:
shutil.rmtree(self.pyini['保存位置'])
os.makedirs(self.pyini['保存位置'])
with open(os.path.join(os.getcwd(), 'Py_ini.txt'), 'w', encoding='utf-8') as F01:
for key, val in self.pyini.items():
F01.write(key + '=' + val + '\n')
F01.close()
p_list = []
for i1 in box_checked:
for dir_i2 in self.dir_list:
dir_path = os.path.join(dir_i2, i1)
if os.path.exists(dir_path):
p_list.append(dir_path)
self.py_to_pyd(p_list)
def clickButton3(self):
# ku_list = 取所有模块
# 文件夹/单文件,代码加密
# pyinstaller 导入 --hidden-import ku_list[0] 模块并编译
self.hide()
box_checked = []
for i1 in self.box_list:
if i1.isChecked():
box_checked.append(i1.text())
i1.setChecked(False)
if len(box_checked) == 0:
return
self.pyini['模块路径'] = self.lineEdit7.text()
self.pyini['ico图标'] = self.lineEdit8.text()
self.pyini['UPX'] = self.lineEdit9.text()
self.pyini['保存位置'] = self.lineEdit11.text()
if not os.path.exists(self.tmpe_path):
os.makedirs(self.tmpe_path)
else:
shutil.rmtree(self.tmpe_path)
os.makedirs(self.tmpe_path)
if not os.path.exists(self.pyini['保存位置']):
os.makedirs(self.pyini['保存位置'])
else:
shutil.rmtree(self.pyini['保存位置'])
os.makedirs(self.pyini['保存位置'])
with open(os.path.join(os.getcwd(), 'Py_ini.txt'), 'w', encoding='utf-8') as F01:
for key, val in self.pyini.items():
F01.write(key + '=' + val + '\n')
F01.close()
for i1 in box_checked:
for dir_i2 in self.dir_list:
dir_path = os.path.join(dir_i2, i1)
if os.path.exists(dir_path):
self.pyarmor_exe(dir_path)
break
self.show()
def pyinstaller_exe(self, py_path, hidden=None):
# ===================开始编译exe========================= #
pyinstaller_01 = r'"D:\Python\Python39\Scripts\pyinstaller.exe" '
if self.CheckBox1.isChecked():
pyinstaller_01 += '-F ' + py_path + ' '
if self.CheckBox2.isChecked():
pyinstaller_01 += '-D ' + py_path + ' '
if self.CheckBox5.isChecked():
pyinstaller_01 += '-w '
if self.CheckBox6.isChecked():
pyinstaller_01 += '-c '
if self.CheckBox7.isChecked():
pyinstaller_01 += '-p ' + self.lineEdit7.text() + ' '
if self.CheckBox8.isChecked():
if self.lineEdit8.text().endswith(".ico"):
pyinstaller_01 += '-i ' + self.lineEdit8.text() + ' '
else:
Pretreatment.message_box('提示', '图标非ico格式;')
return
if self.CheckBox9.isChecked():
pyinstaller_01 += '--upx-dir="' + self.lineEdit9.text() + '" '
pyinstaller_01 += r'--distpath "' + self.pyini['保存位置'] + '" '
if hidden is not None:
pyinstaller_01 += ' --hidden-import ' + " --hidden-import ".join(hidden['模块'])
pyinstaller_01 += r' --clean'
with open(os.path.join(self.tmpe_path, 'pyinstaller.bat'), 'w', encoding='utf-8') as F01:
F01.write('chcp 65001\ncd /d ' + self.pyini['保存位置'] + '\n' + pyinstaller_01)
F01.close()
py_exe = os.path.join(self.tmpe_path, 'pyinstaller.bat')
os.system(py_exe)
os.system("explorer.exe %s" % os.path.dirname(self.pyini['保存位置']))
def py_to_pyd(self, py_list):
py_list2 = []
for i1 in py_list:
if os.path.basename(i1) == 'Genesis.py':
shutil.copy(os.path.normpath(i1), os.path.join(self.tmpe_path, os.path.basename(i1)))
else:
py_list2.append(os.path.normpath(i1))
if len(py_list2) == 0:
return
py_txt = "#!/usr/bin/python3\n" \
"# -*- coding: utf-8 -*-\n" \
"import sys\n" \
"from distutils.core import setup\n" \
"from Cython.Build import cythonize\n" \
f"setup(ext_modules = cythonize(module_list={py_list2}, language_level=3))\n" \
"sys.exit()"
with open(os.path.join(self.tmpe_path, 'Py_code.py'), 'w', encoding='utf-8') as F01:
F01.write(py_txt)
F01.close()
py_exe = '\nD:\Python\Python39\python.exe '
write_bat = 'chcp 65001\ncd /d ' + self.tmpe_path + py_exe + os.path.join(self.tmpe_path, 'Py_code.py') \
+ ' build_ext --inplace\n' # del ' + os.path.join(self.tmpe_path, 'Py_code.py') + '\ndel %0'
with open(os.path.join(self.tmpe_path, 'Py_Shell.bat'), 'w', encoding='utf-8') as F01:
F01.write(write_bat)
F01.close()
os.system(os.path.join(self.tmpe_path, 'Py_Shell.bat'))
for i1 in py_list2:
if os.path.exists(os.path.join(os.path.dirname(i1), os.path.splitext(os.path.basename(i1))[0] + '.c')):
os.remove(os.path.join(os.path.dirname(i1), os.path.splitext(os.path.basename(i1))[0] + '.c'))
for i1 in os.listdir(self.tmpe_path):
if i1[-3:] == 'pyd':
os.rename(os.path.join(self.tmpe_path, i1), os.path.join(self.tmpe_path, i1.replace('.cp39-win_amd64', '')))
# os.system("explorer.exe %s" % os.path.dirname(self.pyini['保存位置']))
def pyarmor_exe(self, py_txt):
# 支持加密文件夹 pyarmor gen --platform windows.x86_64 E:\1
# -r 递归
# 递归取隐藏式模块列表
# 取路径E:\Py-Edit\
# 指定模块位置 --paths=E:\Py-Edit;
# path = os.path.normcase(r'E:\Py-Edit/Py编译/教程').split('\\')
# if len(path) >= 2:
# print(path[0], '\\', path[1], '\\')
tmpe2_path = os.path.join(os.path.dirname(self.tmpe_path), 'tmp2')
if not os.path.exists(tmpe2_path):
os.makedirs(tmpe2_path)
else:
shutil.rmtree(tmpe2_path)
os.makedirs(tmpe2_path)
imports_list = []
imports_path = []
for i1 in self.get_all_imports(py_txt):
imports_list.extend(i1['模块列表'])
if len(i1['模块路径']) > 0:
imports_path.extend(i1['模块路径'])
shutil.copy(py_txt, os.path.join(tmpe2_path, os.path.basename(py_txt)))
for i1 in imports_path:
shutil.copy(i1, os.path.join(tmpe2_path, os.path.basename(i1)))
imports_list.append('pyarmor_runtime_000000')
pyarmor_txt = f'pyarmor gen -O {self.tmpe_path} -i ' + tmpe2_path
os.system(pyarmor_txt)
tmp3_path = os.path.join(self.tmpe_path, os.path.basename(tmpe2_path))
for i1 in os.listdir(tmp3_path):
with open(os.path.join(tmp3_path, i1), 'r', encoding='utf-8') as F01:
txt_read = F01.read()
F01.close()
txt_read.replace('.pyarmor', 'pyarmor')
with open(os.path.join(tmp3_path, i1), 'w', encoding='utf-8') as F01:
F01.write(txt_read)
F01.close()
# 编译为exe
self.pyinstaller_exe(os.path.join(tmpe2_path, os.path.basename(py_txt)),
hidden={'模块': list(dict.fromkeys(imports_list))})
def search_files(self, file_path, pattern):
"""搜索指定py文件是否存在form隐式导入"""
matched_files = []
if file_path.endswith(".py"):
with open(file_path, "r", encoding='utf-8') as F01:
lines = F01.readlines()
for line_num, line in enumerate(lines, start=1):
match = re.search(pattern, line)
if match:
matched_files.append((file_path, line_num, match.group(1)))
else:
return []
imports = []
# 打印匹配的文件路径、行号和匹配的数据
for file_path, line_num, data in matched_files:
# print(f"File: {file_path}, Line: {line_num}")
# 过滤重复list
if data not in imports:
imports.append(data)
return imports
def CheckBox01(self):
if self.CheckBox1.isChecked():
self.CheckBox2.setChecked(False)
def CheckBox02(self):
if self.CheckBox2.isChecked():
self.CheckBox1.setChecked(False)
def CheckBox05(self):
if self.CheckBox5.isChecked():
self.CheckBox6.setChecked(False)
def CheckBox06(self):
if self.CheckBox6.isChecked():
self.CheckBox5.setChecked(False)
def CheckBox07(self):
if self.CheckBox7.isChecked() and self.mulu_check:
file_path = QFileDialog.getExistingDirectory(self, "选择模块目录", "E:/", QFileDialog.ShowDirsOnly)
self.lineEdit7.setText(file_path)
def CheckBox08(self):
if self.CheckBox8.isChecked() and self.mulu_check:
file_path, _ = QFileDialog.getOpenFileName(self, "选择ico图标", "", "All Files (*)")
self.lineEdit8.setText(file_path)
def CheckBox09(self):
if self.CheckBox9.isChecked() and self.mulu_check:
file_path, _ = QFileDialog.getOpenFileName(self, "选择UPX目录", "", "All Files (*)")
self.lineEdit9.setText(file_path)
if __name__ == '__main__':
app = QApplication(sys.argv)
ta = TableDemo()
sys.exit(app.exec_())