上传文件至 /
This commit is contained in:
parent
fe91390e49
commit
3211d7d999
751
main.py
Normal file
751
main.py
Normal file
@ -0,0 +1,751 @@
|
||||
"""
|
||||
作者:太一
|
||||
微信:taiyi1224
|
||||
邮箱:shoubo1224@qq.com
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
import os
|
||||
import json
|
||||
import pyperclip
|
||||
from database import LicenseDatabase
|
||||
from encryptor import EXEEncryptor
|
||||
from machine_code import get_machine_code
|
||||
from tkinter import font as tkfont
|
||||
|
||||
|
||||
|
||||
def set_dark_theme(root):
|
||||
style = ttk.Style(root)
|
||||
# 仅 Windows 支持 'vista',其它系统可改为 'clam'
|
||||
style.theme_use('clam')
|
||||
style.configure('.', background='#2e2e2e', foreground='#ffffff',
|
||||
fieldbackground='#3c3f41', selectbackground='#0078d4',
|
||||
insertbackground='#ffffff', borderwidth=1,
|
||||
focuscolor='none')
|
||||
style.map('.', background=[('active', '#0078d4')])
|
||||
style.configure('TButton', padding=6, relief='flat',
|
||||
background='#0078d4', foreground='#ffffff')
|
||||
style.map('TButton', background=[('active', '#106ebe')])
|
||||
style.configure('TLabel', background='#2e2e2e', foreground='#ffffff')
|
||||
style.configure('TEntry', fieldbackground='#3c3f41', foreground='#ffffff',
|
||||
insertbackground='#ffffff', relief='flat', padding=5)
|
||||
style.configure('Treeview', background='#252526', foreground='#ffffff',
|
||||
fieldbackground='#252526')
|
||||
style.configure('Treeview.Heading', background='#3c3f41', foreground='#ffffff')
|
||||
|
||||
class EXEEncryptionTool(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("EXE文件加密保护系统")
|
||||
self.geometry("800x600")
|
||||
self.minsize(800, 600)
|
||||
# set_dark_theme(self)
|
||||
|
||||
# 数据库配置
|
||||
self.db_config = {
|
||||
'host': '',
|
||||
'database': 'license_system',
|
||||
'user': '',
|
||||
'password': ''
|
||||
}
|
||||
|
||||
# 初始化数据库连接
|
||||
self.db = None
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
# 加载保存的配置
|
||||
self.load_config()
|
||||
|
||||
def create_widgets(self):
|
||||
"""创建界面组件"""
|
||||
# 创建标签页
|
||||
tab_control = ttk.Notebook(self)
|
||||
|
||||
# 数据库配置标签页
|
||||
self.tab_db_config = ttk.Frame(tab_control)
|
||||
tab_control.add(self.tab_db_config, text="数据库配置")
|
||||
|
||||
# 卡密生成标签页
|
||||
self.tab_key_gen = ttk.Frame(tab_control)
|
||||
tab_control.add(self.tab_key_gen, text="卡密生成")
|
||||
|
||||
# EXE加密标签页
|
||||
self.tab_encrypt = ttk.Frame(tab_control)
|
||||
tab_control.add(self.tab_encrypt, text="EXE加密")
|
||||
|
||||
# 卡密管理标签页
|
||||
self.tab_key_manage = ttk.Frame(tab_control)
|
||||
tab_control.add(self.tab_key_manage, text="卡密管理")
|
||||
|
||||
|
||||
tab_control.pack(expand=1, fill="both")
|
||||
|
||||
# 初始化各个标签页
|
||||
self.init_db_config_tab()
|
||||
self.init_key_gen_tab()
|
||||
self.init_encrypt_tab()
|
||||
self.init_key_manage_tab()
|
||||
|
||||
|
||||
|
||||
# 状态栏
|
||||
self.status_var = tk.StringVar(value="就绪")
|
||||
status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
|
||||
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
# def unbind_selected_key(self):
|
||||
# key_code = self.get_selected_key_code()
|
||||
# if not key_code:
|
||||
# return
|
||||
# if messagebox.askyesno("确认", f"确定解除卡密 {key_code} 与当前机器的绑定?"):
|
||||
# ok, msg = self.db.unbind_key(key_code)
|
||||
# messagebox.showinfo("结果", msg)
|
||||
# self.load_all_keys()
|
||||
|
||||
def unbind_selected_key(self):
|
||||
"""后台解除卡密与当前机器的绑定"""
|
||||
key_code = self.get_selected_key_code()
|
||||
if not key_code:
|
||||
return
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
messagebox.showerror("错误", "请先连接数据库")
|
||||
return
|
||||
if messagebox.askyesno("确认", f"确定解除卡密 {key_code} 与当前机器的绑定?"):
|
||||
ok, msg = self.db.unbind_key(key_code)
|
||||
messagebox.showinfo("结果", msg)
|
||||
self.load_all_keys() # 刷新列表
|
||||
def init_db_config_tab(self):
|
||||
"""初始化数据库配置标签页"""
|
||||
frame = ttk.LabelFrame(self.tab_db_config, text="数据库连接设置")
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# 表单布局
|
||||
grid_frame = ttk.Frame(frame)
|
||||
grid_frame.pack(padx=10, pady=10, fill=tk.X)
|
||||
|
||||
# 主机
|
||||
ttk.Label(grid_frame, text="主机:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
|
||||
self.entry_db_host = ttk.Entry(grid_frame)
|
||||
self.entry_db_host.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
|
||||
|
||||
# 数据库名
|
||||
ttk.Label(grid_frame, text="数据库:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
|
||||
self.entry_db_name = ttk.Entry(grid_frame)
|
||||
self.entry_db_name.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
|
||||
self.entry_db_name.insert(0, "")
|
||||
|
||||
# 用户名
|
||||
ttk.Label(grid_frame, text="用户名:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
|
||||
self.entry_db_user = ttk.Entry(grid_frame)
|
||||
self.entry_db_user.grid(row=2, column=1, padx=5, pady=5, sticky=tk.EW)
|
||||
|
||||
# 密码
|
||||
ttk.Label(grid_frame, text="密码:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
|
||||
self.entry_db_password = ttk.Entry(grid_frame, show="*")
|
||||
self.entry_db_password.grid(row=3, column=1, padx=5, pady=5, sticky=tk.EW)
|
||||
|
||||
# 按钮
|
||||
button_frame = ttk.Frame(frame)
|
||||
button_frame.pack(padx=10, pady=10, fill=tk.X)
|
||||
|
||||
self.btn_connect_db = ttk.Button(button_frame, text="连接数据库", command=self.connect_db)
|
||||
self.btn_connect_db.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.btn_create_tables = ttk.Button(button_frame, text="创建数据库表", command=self.create_db_tables)
|
||||
self.btn_create_tables.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.btn_save_db_config = ttk.Button(button_frame, text="保存配置", command=self.save_db_config)
|
||||
self.btn_save_db_config.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# 连接状态
|
||||
self.label_db_status = ttk.Label(frame, text="未连接数据库", foreground="red")
|
||||
self.label_db_status.pack(anchor=tk.W, padx=10, pady=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = ttk.LabelFrame(frame, text="操作日志")
|
||||
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
self.text_db_log = scrolledtext.ScrolledText(log_frame, height=10)
|
||||
self.text_db_log.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
self.text_db_log.config(state=tk.DISABLED)
|
||||
|
||||
# 设置网格权重
|
||||
grid_frame.columnconfigure(1, weight=1)
|
||||
|
||||
def init_key_gen_tab(self):
|
||||
"""初始化卡密生成标签页"""
|
||||
frame = ttk.LabelFrame(self.tab_key_gen, text="卡密生成设置")
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# 配置区域
|
||||
config_frame = ttk.Frame(frame)
|
||||
config_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||
|
||||
# 有效期
|
||||
ttk.Label(config_frame, text="有效期(天):").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
|
||||
self.entry_valid_days = ttk.Entry(config_frame, width=10)
|
||||
self.entry_valid_days.grid(row=0, column=1, padx=5, pady=5)
|
||||
self.entry_valid_days.insert(0, "30")
|
||||
|
||||
# 生成数量
|
||||
ttk.Label(config_frame, text="生成数量:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
|
||||
self.entry_key_count = ttk.Entry(config_frame, width=10)
|
||||
self.entry_key_count.grid(row=0, column=3, padx=5, pady=5)
|
||||
self.entry_key_count.insert(0, "1")
|
||||
|
||||
# 生成按钮
|
||||
self.btn_generate_keys = ttk.Button(config_frame, text="生成卡密", command=self.generate_keys)
|
||||
self.btn_generate_keys.grid(row=0, column=4, padx=20, pady=5)
|
||||
|
||||
# 卡密列表
|
||||
list_frame = ttk.LabelFrame(frame, text="生成的卡密")
|
||||
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
self.text_keys = scrolledtext.ScrolledText(list_frame)
|
||||
self.text_keys.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# 操作按钮
|
||||
button_frame = ttk.Frame(frame)
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||
|
||||
self.btn_copy_keys = ttk.Button(button_frame, text="复制所有卡密", command=self.copy_keys)
|
||||
self.btn_copy_keys.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.btn_export_keys = ttk.Button(button_frame, text="导出卡密到文件", command=self.export_keys)
|
||||
self.btn_export_keys.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 设置网格权重
|
||||
config_frame.columnconfigure(5, weight=1)
|
||||
|
||||
def init_encrypt_tab(self):
|
||||
"""初始化EXE加密标签页"""
|
||||
frame = ttk.LabelFrame(self.tab_encrypt, text="EXE文件加密")
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# 源文件
|
||||
source_frame = ttk.Frame(frame)
|
||||
source_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
ttk.Label(source_frame, text="源EXE文件:").pack(side=tk.LEFT, padx=5)
|
||||
self.entry_source_file = ttk.Entry(source_frame)
|
||||
self.entry_source_file.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||
self.btn_browse_source = ttk.Button(source_frame, text="浏览...", command=self.browse_source_file)
|
||||
self.btn_browse_source.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 目标文件
|
||||
dest_frame = ttk.Frame(frame)
|
||||
dest_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
ttk.Label(dest_frame, text="加密后文件:").pack(side=tk.LEFT, padx=5)
|
||||
self.entry_dest_file = ttk.Entry(dest_frame)
|
||||
self.entry_dest_file.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||
self.btn_browse_dest = ttk.Button(dest_frame, text="浏览...", command=self.browse_dest_file)
|
||||
self.btn_browse_dest.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 验证程序
|
||||
validator_frame = ttk.Frame(frame)
|
||||
validator_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
ttk.Label(validator_frame, text="验证程序:").pack(side=tk.LEFT, padx=5)
|
||||
self.entry_validator = ttk.Entry(validator_frame)
|
||||
self.entry_validator.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||
self.btn_browse_validator = ttk.Button(validator_frame, text="浏览...", command=self.browse_validator)
|
||||
self.btn_browse_validator.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 加密按钮
|
||||
self.btn_encrypt = ttk.Button(frame, text="加密EXE文件", command=self.encrypt_exe)
|
||||
self.btn_encrypt.pack(pady=10)
|
||||
|
||||
# 加密日志
|
||||
log_frame = ttk.LabelFrame(frame, text="加密日志")
|
||||
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
self.text_encrypt_log = scrolledtext.ScrolledText(log_frame, height=10)
|
||||
self.text_encrypt_log.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
self.text_encrypt_log.config(state=tk.DISABLED)
|
||||
|
||||
def init_key_manage_tab(self):
|
||||
"""初始化卡密管理标签页"""
|
||||
frame = ttk.LabelFrame(self.tab_key_manage, text="卡密管理")
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# 搜索区域
|
||||
search_frame = ttk.Frame(frame)
|
||||
search_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
ttk.Label(search_frame, text="搜索卡密:").pack(side=tk.LEFT, padx=5)
|
||||
self.entry_key_search = ttk.Entry(search_frame)
|
||||
self.entry_key_search.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
|
||||
self.btn_search_keys = ttk.Button(search_frame, text="搜索", command=self.search_keys)
|
||||
self.btn_search_keys.pack(side=tk.LEFT, padx=5)
|
||||
self.btn_refresh_keys = ttk.Button(search_frame, text="刷新", command=self.load_all_keys)
|
||||
self.btn_refresh_keys.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 卡密列表
|
||||
columns = ("id", "key_code", "machine_code", "start_time", "end_time", "status", "created_at")
|
||||
self.tree_keys = ttk.Treeview(frame, columns=columns, show="headings")
|
||||
|
||||
# 设置列标题
|
||||
self.tree_keys.heading("id", text="ID")
|
||||
self.tree_keys.heading("key_code", text="卡密")
|
||||
self.tree_keys.heading("machine_code", text="机器码")
|
||||
self.tree_keys.heading("start_time", text="开始时间")
|
||||
self.tree_keys.heading("end_time", text="结束时间")
|
||||
self.tree_keys.heading("status", text="状态")
|
||||
self.tree_keys.heading("created_at", text="创建时间")
|
||||
|
||||
# 设置列宽
|
||||
self.tree_keys.column("id", width=50)
|
||||
self.tree_keys.column("key_code", width=150)
|
||||
self.tree_keys.column("machine_code", width=120)
|
||||
self.tree_keys.column("start_time", width=120)
|
||||
self.tree_keys.column("end_time", width=120)
|
||||
self.tree_keys.column("status", width=80)
|
||||
self.tree_keys.column("created_at", width=120)
|
||||
|
||||
# 添加滚动条
|
||||
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.tree_keys.yview)
|
||||
self.tree_keys.configure(yscroll=scrollbar.set)
|
||||
|
||||
self.tree_keys.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=5)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=5)
|
||||
|
||||
# 操作按钮
|
||||
button_frame = ttk.Frame(frame)
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=10)
|
||||
|
||||
self.btn_ban_key = ttk.Button(button_frame, text="封禁选中卡密", command=self.ban_selected_key)
|
||||
self.btn_ban_key.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.btn_unban_key = ttk.Button(button_frame, text="解封选中卡密", command=self.unban_selected_key)
|
||||
self.btn_unban_key.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.btn_release_key = ttk.Button(button_frame, text="释放选中卡密", command=self.release_selected_key)
|
||||
self.btn_release_key.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.btn_delete_key = ttk.Button(button_frame, text="删除选中卡密", command=self.delete_selected_key)
|
||||
self.btn_delete_key.pack(side=tk.LEFT, padx=5)
|
||||
# ✅ 新增:解绑卡密按钮
|
||||
self.btn_unbind = ttk.Button(button_frame, text="解绑卡密", command=self.unbind_selected_key)
|
||||
self.btn_unbind.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 数据库相关方法
|
||||
def log(self, text, widget=None):
|
||||
"""添加日志信息"""
|
||||
if not widget:
|
||||
widget = self.text_db_log
|
||||
|
||||
widget.config(state=tk.NORMAL)
|
||||
widget.insert(tk.END, text + "\n")
|
||||
widget.see(tk.END)
|
||||
widget.config(state=tk.DISABLED)
|
||||
self.update_idletasks()
|
||||
|
||||
def connect_db(self):
|
||||
"""连接到数据库"""
|
||||
self.db_config['host'] = self.entry_db_host.get()
|
||||
self.db_config['database'] = self.entry_db_name.get()
|
||||
self.db_config['user'] = self.entry_db_user.get()
|
||||
self.db_config['password'] = self.entry_db_password.get()
|
||||
|
||||
self.log(f"尝试连接到数据库: {self.db_config['host']}/{self.db_config['database']}")
|
||||
|
||||
self.db = LicenseDatabase(
|
||||
self.db_config['host'],
|
||||
self.db_config['database'],
|
||||
self.db_config['user'],
|
||||
self.db_config['password']
|
||||
)
|
||||
|
||||
if self.db.connect():
|
||||
self.label_db_status.config(text="已连接到数据库", foreground="green")
|
||||
self.log("数据库连接成功")
|
||||
messagebox.showinfo("成功", "数据库连接成功")
|
||||
# 连接成功后加载卡密列表
|
||||
self.load_all_keys()
|
||||
else:
|
||||
self.label_db_status.config(text="数据库连接失败", foreground="red")
|
||||
self.log("数据库连接失败")
|
||||
messagebox.showerror("错误", "无法连接到数据库,请检查配置")
|
||||
|
||||
def create_db_tables(self):
|
||||
"""创建数据库表"""
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
if not self.connect_db():
|
||||
return
|
||||
|
||||
self.log("尝试创建数据库表...")
|
||||
if self.db.create_tables():
|
||||
self.log("数据库表创建成功")
|
||||
messagebox.showinfo("成功", "数据库表创建成功")
|
||||
else:
|
||||
self.log("数据库表创建失败")
|
||||
messagebox.showerror("错误", "数据库表创建失败")
|
||||
|
||||
def save_db_config(self):
|
||||
"""保存数据库配置"""
|
||||
self.db_config['host'] = self.entry_db_host.get()
|
||||
self.db_config['database'] = self.entry_db_name.get()
|
||||
self.db_config['user'] = self.entry_db_user.get()
|
||||
self.db_config['password'] = self.entry_db_password.get()
|
||||
|
||||
try:
|
||||
with open('db_config.json', 'w') as f:
|
||||
json.dump(self.db_config, f)
|
||||
|
||||
self.log("数据库配置保存成功")
|
||||
messagebox.showinfo("成功", "数据库配置已保存")
|
||||
except Exception as e:
|
||||
self.log(f"保存配置失败: {str(e)}")
|
||||
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
|
||||
|
||||
def load_config(self):
|
||||
"""加载保存的配置"""
|
||||
try:
|
||||
if os.path.exists('db_config.json'):
|
||||
with open('db_config.json', 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
self.entry_db_host.insert(0, config.get('host', ''))
|
||||
self.entry_db_name.insert(0, config.get('database', 'license_system'))
|
||||
self.entry_db_user.insert(0, config.get('user', ''))
|
||||
self.entry_db_password.insert(0, config.get('password', ''))
|
||||
except Exception as e:
|
||||
print(f"加载配置失败: {e}")
|
||||
|
||||
# 卡密生成相关方法
|
||||
def generate_keys(self):
|
||||
"""生成卡密"""
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
messagebox.showerror("错误", "请先连接数据库")
|
||||
return
|
||||
|
||||
try:
|
||||
days = int(self.entry_valid_days.get())
|
||||
count = int(self.entry_key_count.get())
|
||||
|
||||
if days <= 0 or count <= 0:
|
||||
messagebox.showerror("错误", "有效期和数量必须为正数")
|
||||
return
|
||||
|
||||
self.text_keys.delete(1.0, tk.END)
|
||||
self.log(f"开始生成 {count} 个有效期为 {days} 天的卡密...", self.text_keys)
|
||||
|
||||
keys = []
|
||||
for i in range(count):
|
||||
key = self.db.generate_key(days)
|
||||
if key:
|
||||
keys.append(key)
|
||||
self.text_keys.insert(tk.END, key + "\n")
|
||||
self.text_keys.see(tk.END)
|
||||
self.update_idletasks()
|
||||
|
||||
self.log(f"\n成功生成 {len(keys)} 个卡密", self.text_keys)
|
||||
messagebox.showinfo("成功", f"成功生成 {len(keys)} 个卡密")
|
||||
# 刷新卡密列表
|
||||
self.load_all_keys()
|
||||
|
||||
except ValueError:
|
||||
messagebox.showerror("错误", "请输入有效的数字")
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"生成卡密失败: {str(e)}")
|
||||
|
||||
def copy_keys(self):
|
||||
"""复制卡密到剪贴板"""
|
||||
keys = self.text_keys.get(1.0, tk.END).strip()
|
||||
if keys:
|
||||
pyperclip.copy(keys)
|
||||
messagebox.showinfo("成功", "卡密已复制到剪贴板")
|
||||
else:
|
||||
messagebox.showinfo("提示", "没有可复制的卡密")
|
||||
|
||||
def export_keys(self):
|
||||
"""导出卡密到文件"""
|
||||
keys = self.text_keys.get(1.0, tk.END).strip()
|
||||
if not keys:
|
||||
messagebox.showinfo("提示", "没有可导出的卡密")
|
||||
return
|
||||
|
||||
file_path = filedialog.asksaveasfilename(
|
||||
defaultextension=".txt",
|
||||
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
|
||||
)
|
||||
|
||||
if file_path:
|
||||
try:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(keys)
|
||||
messagebox.showinfo("成功", f"卡密已导出到 {file_path}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"导出失败: {str(e)}")
|
||||
|
||||
# EXE加密相关方法
|
||||
def browse_source_file(self):
|
||||
"""浏览源文件"""
|
||||
file_path = filedialog.askopenfilename(
|
||||
filetypes=[("EXE文件", "*.exe"), ("所有文件", "*.*")]
|
||||
)
|
||||
if file_path:
|
||||
self.entry_source_file.delete(0, tk.END)
|
||||
self.entry_source_file.insert(0, file_path)
|
||||
|
||||
# 自动填充目标文件路径
|
||||
dir_name, file_name = os.path.split(file_path)
|
||||
name, ext = os.path.splitext(file_name)
|
||||
dest_file = os.path.join(dir_name, f"{name}_encrypted{ext}")
|
||||
self.entry_dest_file.delete(0, tk.END)
|
||||
self.entry_dest_file.insert(0, dest_file)
|
||||
|
||||
# 自动填充验证器路径(使用新的validator_wrapper.py)
|
||||
validator_path = os.path.join(os.path.dirname(__file__), "validator_wrapper.py")
|
||||
self.entry_validator.delete(0, tk.END)
|
||||
self.entry_validator.insert(0, validator_path)
|
||||
|
||||
def browse_dest_file(self):
|
||||
"""浏览目标文件"""
|
||||
file_path = filedialog.asksaveasfilename(
|
||||
defaultextension=".exe",
|
||||
filetypes=[("EXE文件", "*.exe"), ("文本文件", "*.py"), ("所有文件", "*.*")]
|
||||
)
|
||||
if file_path:
|
||||
self.entry_dest_file.delete(0, tk.END)
|
||||
self.entry_dest_file.insert(0, file_path)
|
||||
|
||||
def browse_validator(self):
|
||||
"""浏览验证程序"""
|
||||
file_path = filedialog.askopenfilename(
|
||||
filetypes=[("Python程序", "*.*"), ("EXE文件", "*.exe"), ("所有文件", "*.*")]
|
||||
)
|
||||
if file_path:
|
||||
self.entry_validator.delete(0, tk.END)
|
||||
self.entry_validator.insert(0, file_path)
|
||||
|
||||
def encrypt_exe(self):
|
||||
"""加密EXE文件"""
|
||||
source_path = self.entry_source_file.get()
|
||||
dest_path = self.entry_dest_file.get()
|
||||
validator_path = self.entry_validator.get()
|
||||
|
||||
# 检查文件路径
|
||||
if not source_path or not os.path.exists(source_path):
|
||||
messagebox.showerror("错误", "请选择有效的源文件")
|
||||
return
|
||||
|
||||
if not dest_path:
|
||||
messagebox.showerror("错误", "请选择目标文件路径")
|
||||
return
|
||||
|
||||
if not validator_path or not os.path.exists(validator_path):
|
||||
messagebox.showerror("错误", "请选择有效的验证程序")
|
||||
return
|
||||
|
||||
# 检查数据库连接
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
if not self.connect_db():
|
||||
return
|
||||
|
||||
# 执行加密
|
||||
self.log("开始加密文件...", self.text_encrypt_log)
|
||||
self.log(f"源文件: {source_path}", self.text_encrypt_log)
|
||||
|
||||
try:
|
||||
encryptor = EXEEncryptor()
|
||||
success, msg = encryptor.encrypt_file(
|
||||
source_path,
|
||||
dest_path,
|
||||
validator_path,
|
||||
self.db_config
|
||||
)
|
||||
|
||||
self.log(msg, self.text_encrypt_log)
|
||||
|
||||
if success:
|
||||
self.log(f"加密后的文件已保存到: {dest_path}", self.text_encrypt_log)
|
||||
messagebox.showinfo("成功", f"文件加密成功\n保存到: {dest_path}")
|
||||
else:
|
||||
messagebox.showerror("错误", msg)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"加密过程出错: {str(e)}"
|
||||
self.log(error_msg, self.text_encrypt_log)
|
||||
messagebox.showerror("错误", error_msg)
|
||||
|
||||
# 卡密管理相关方法
|
||||
def load_all_keys(self):
|
||||
"""加载所有卡密"""
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
return
|
||||
|
||||
# 清空现有列表
|
||||
for item in self.tree_keys.get_children():
|
||||
self.tree_keys.delete(item)
|
||||
|
||||
try:
|
||||
keys = self.db.get_all_keys()
|
||||
for key in keys:
|
||||
# 格式化日期时间
|
||||
start_time = key['start_time'].strftime("%Y-%m-%d") if key['start_time'] else ""
|
||||
end_time = key['end_time'].strftime("%Y-%m-%d") if key['end_time'] else ""
|
||||
created_at = key['created_at'].strftime("%Y-%m-%d") if key['created_at'] else ""
|
||||
|
||||
self.tree_keys.insert("", tk.END, values=(
|
||||
key['id'],
|
||||
key['key_code'],
|
||||
key['machine_code'] or "",
|
||||
start_time,
|
||||
end_time,
|
||||
key['status'],
|
||||
created_at
|
||||
))
|
||||
|
||||
# 根据状态设置行颜色
|
||||
item = self.tree_keys.get_children()[-1]
|
||||
if key['status'] == 'active':
|
||||
self.tree_keys.item(item, tags=('active',))
|
||||
elif key['status'] == 'expired':
|
||||
self.tree_keys.item(item, tags=('expired',))
|
||||
elif key['status'] == 'banned':
|
||||
self.tree_keys.item(item, tags=('banned',))
|
||||
|
||||
# 设置标签样式
|
||||
self.tree_keys.tag_configure('active', foreground='green')
|
||||
self.tree_keys.tag_configure('expired', foreground='gray')
|
||||
self.tree_keys.tag_configure('banned', foreground='red')
|
||||
|
||||
except Exception as e:
|
||||
print(f"加载卡密列表失败: {e}")
|
||||
|
||||
def search_keys(self):
|
||||
"""搜索卡密"""
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
return
|
||||
|
||||
search_text = self.entry_key_search.get().strip().lower()
|
||||
if not search_text:
|
||||
self.load_all_keys()
|
||||
return
|
||||
|
||||
# 清空现有列表
|
||||
for item in self.tree_keys.get_children():
|
||||
self.tree_keys.delete(item)
|
||||
|
||||
try:
|
||||
keys = self.db.get_all_keys()
|
||||
for key in keys:
|
||||
# 检查是否匹配搜索文本
|
||||
if (search_text in key['key_code'].lower() or
|
||||
search_text in key['status'].lower() or
|
||||
(key['machine_code'] and search_text in key['machine_code'].lower())):
|
||||
# 格式化日期时间
|
||||
start_time = key['start_time'].strftime("%Y-%m-%d") if key['start_time'] else ""
|
||||
end_time = key['end_time'].strftime("%Y-%m-%d") if key['end_time'] else ""
|
||||
created_at = key['created_at'].strftime("%Y-%m-%d") if key['created_at'] else ""
|
||||
|
||||
self.tree_keys.insert("", tk.END, values=(
|
||||
key['id'],
|
||||
key['key_code'],
|
||||
key['machine_code'] or "",
|
||||
start_time,
|
||||
end_time,
|
||||
key['status'],
|
||||
created_at
|
||||
))
|
||||
|
||||
except Exception as e:
|
||||
print(f"搜索卡密失败: {e}")
|
||||
|
||||
def get_selected_key_code(self):
|
||||
"""获取选中的卡密"""
|
||||
selected_items = self.tree_keys.selection()
|
||||
if not selected_items:
|
||||
messagebox.showinfo("提示", "请先选择一个卡密")
|
||||
return None
|
||||
|
||||
item = selected_items[0]
|
||||
key_code = self.tree_keys.item(item, "values")[1]
|
||||
return key_code
|
||||
|
||||
def ban_selected_key(self):
|
||||
"""封禁选中的卡密"""
|
||||
key_code = self.get_selected_key_code()
|
||||
if not key_code:
|
||||
return
|
||||
|
||||
if messagebox.askyesno("确认", f"确定要封禁卡密 {key_code} 吗?"):
|
||||
if self.db.update_key_status(key_code, 'banned'):
|
||||
messagebox.showinfo("成功", "卡密已封禁")
|
||||
self.load_all_keys()
|
||||
else:
|
||||
messagebox.showerror("错误", "封禁卡密失败")
|
||||
|
||||
def unban_selected_key(self):
|
||||
"""解封选中的卡密"""
|
||||
key_code = self.get_selected_key_code()
|
||||
if not key_code:
|
||||
return
|
||||
|
||||
if messagebox.askyesno("确认", f"确定要解封卡密 {key_code} 吗?"):
|
||||
# 检查原状态是未使用还是已激活
|
||||
try:
|
||||
selected_items = self.tree_keys.selection()
|
||||
item = selected_items[0]
|
||||
original_status = self.tree_keys.item(item, "values")[5]
|
||||
|
||||
new_status = 'unused' if original_status == 'banned' and not self.tree_keys.item(item, "values")[
|
||||
2] else 'active'
|
||||
|
||||
if self.db.update_key_status(key_code, new_status):
|
||||
messagebox.showinfo("成功", "卡密已解封")
|
||||
self.load_all_keys()
|
||||
else:
|
||||
messagebox.showerror("错误", "解封卡密失败")
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"操作失败: {str(e)}")
|
||||
|
||||
def release_selected_key(self):
|
||||
"""释放选中的已使用激活码"""
|
||||
key_code = self.get_selected_key_code()
|
||||
if not key_code:
|
||||
return
|
||||
|
||||
# 获取选择项的状态信息
|
||||
selected_items = self.tree_keys.selection()
|
||||
if selected_items:
|
||||
item = selected_items[0]
|
||||
status = self.tree_keys.item(item, "values")[5]
|
||||
|
||||
if status != 'active':
|
||||
messagebox.showwarning("警告", "只能释放处于'激活'状态的激活码")
|
||||
return
|
||||
|
||||
if messagebox.askyesno("确认释放",
|
||||
f"确定要释放激活码 '{key_code}' 吗?\n\n释放后:\n1. 该激活码将变为未使用状态\n2. 机器码将被清空\n3. 可以重新在任何机器上使用\n\n此操作不可撤销!"):
|
||||
if not self.db or not self.db.connection.is_connected():
|
||||
if not self.connect_db():
|
||||
return
|
||||
|
||||
success, msg = self.db.release_key(key_code)
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"激活码 '{key_code}' 已释放成功\n{msg}")
|
||||
self.load_all_keys() # 刷新列表
|
||||
else:
|
||||
messagebox.showerror("失败", msg)
|
||||
|
||||
def delete_selected_key(self):
|
||||
"""删除选中的卡密"""
|
||||
key_code = self.get_selected_key_code()
|
||||
if not key_code:
|
||||
return
|
||||
|
||||
if messagebox.askyesno("确认", f"确定要删除卡密 {key_code} 吗?\n此操作不可恢复!"):
|
||||
# 实际项目中应该实现delete_key方法
|
||||
messagebox.showinfo("提示", "为安全起见,当前版本不允许删除激活码,建议使用封禁或释放功能")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = EXEEncryptionTool()
|
||||
app.mainloop()
|
||||
38
main.spec
Normal file
38
main.spec
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='main',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
656
validator_wrapper.py
Normal file
656
validator_wrapper.py
Normal file
@ -0,0 +1,656 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
EXE Wrapper Validator - 外层验证程序
|
||||
用于验证卡密后启动原始exe文件,包含防护措施
|
||||
(已整合:自动本地缓存验证 + UI 美化 + 修复本地缓存解密)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
import json
|
||||
import hashlib
|
||||
import tempfile
|
||||
import subprocess
|
||||
import mysql.connector
|
||||
from datetime import datetime
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, ttk
|
||||
import threading
|
||||
import time
|
||||
import psutil
|
||||
import ctypes
|
||||
|
||||
import win32con
|
||||
import os, json, base64
|
||||
from cryptography.fernet import Fernet # pip install cryptography
|
||||
|
||||
from machine_code import get_machine_code
|
||||
|
||||
LICENSE_FILE = os.path.join(tempfile.gettempdir(), '.lic_cache')
|
||||
|
||||
def _get_fernet() -> Fernet:
|
||||
"""用机器码作为密钥,保证同一机器才能解密"""
|
||||
machine_key = get_machine_code().encode()[:32].ljust(32, b'0')
|
||||
return Fernet(base64.urlsafe_b64encode(machine_key))
|
||||
|
||||
def save_license(key: str):
|
||||
"""加密保存卡密"""
|
||||
data = {'key': key, 'machine': get_machine_code()}
|
||||
with open(LICENSE_FILE, 'wb') as f:
|
||||
f.write(_get_fernet().encrypt(json.dumps(data).encode()))
|
||||
|
||||
def load_license() -> str | None:
|
||||
"""解密读取卡密,若文件不存在/被篡改返回 None"""
|
||||
try:
|
||||
with open(LICENSE_FILE, 'rb') as f:
|
||||
data = json.loads(_get_fernet().decrypt(f.read()).decode())
|
||||
return data['key'] if data.get('machine') == get_machine_code() else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# RESOURCE_DATA_PLACEHOLDER
|
||||
RESOURCE_DATA = None
|
||||
class EXEWrapperValidator:
|
||||
"""EXE外层验证器 - 验证卡密后启动原始程序"""
|
||||
|
||||
def __init__(self):
|
||||
self.temp_dir = None
|
||||
self.original_exe_path = None
|
||||
self.process_handle = None
|
||||
self.anti_debug_enabled = True
|
||||
self.anti_vm_enabled = True
|
||||
self.process_monitoring = True
|
||||
|
||||
def show_license_dialog(self):
|
||||
"""显示许可证激活对话框(美化版)"""
|
||||
def style_button(btn, bg_color, hover_color):
|
||||
def on_enter(e): btn['bg'] = hover_color
|
||||
def on_leave(e): btn['bg'] = bg_color
|
||||
btn.bind("<Enter>", on_enter)
|
||||
btn.bind("<Leave>", on_leave)
|
||||
|
||||
def on_activate():
|
||||
key = key_entry.get().strip()
|
||||
if not key:
|
||||
messagebox.showerror("错误", "请输入激活码")
|
||||
return
|
||||
activate_btn.config(state=tk.DISABLED, text="验证中...")
|
||||
status_label.config(text="正在验证激活码...", fg="blue")
|
||||
root.update()
|
||||
|
||||
def validate_thread():
|
||||
success, msg = self.validate_license(key, self.get_machine_code())
|
||||
root.after(0, lambda: self.handle_validation_result(success, msg, root, key))
|
||||
threading.Thread(target=validate_thread, daemon=True).start()
|
||||
|
||||
machine_code = self.get_machine_code()
|
||||
root = tk.Tk()
|
||||
root.title("软件许可证验证")
|
||||
root.geometry("520x460")
|
||||
root.configure(bg="#ffffff")
|
||||
root.resizable(False, False)
|
||||
|
||||
# 居中窗口
|
||||
root.update_idletasks()
|
||||
x = (root.winfo_screenwidth() - root.winfo_width()) // 2
|
||||
y = (root.winfo_screenheight() - root.winfo_height()) // 2
|
||||
root.geometry(f"520x460+{x}+{y}")
|
||||
|
||||
# 标题
|
||||
tk.Label(root, text="🔐 软件许可证验证",
|
||||
font=("Microsoft YaHei", 18, "bold"),
|
||||
bg="#ffffff", fg="#2c3e50").pack(pady=12)
|
||||
|
||||
# 机器码框架
|
||||
machine_frame = tk.Frame(root, bg="#ffffff")
|
||||
machine_frame.pack(fill=tk.X, padx=30, pady=6)
|
||||
|
||||
tk.Label(machine_frame, text="机器码:", bg="#ffffff",
|
||||
font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W)
|
||||
|
||||
machine_entry = tk.Entry(machine_frame, state="readonly", width=45,
|
||||
font=("Consolas", 10), bg="#f2f4f7", relief=tk.SOLID, bd=1)
|
||||
machine_entry.insert(0, machine_code)
|
||||
machine_entry.pack(fill=tk.X, pady=6)
|
||||
|
||||
# 复制机器码按钮
|
||||
copy_btn = tk.Button(machine_frame, text="复制机器码",
|
||||
command=lambda: self.copy_to_clipboard(machine_code),
|
||||
bg="#3498db", fg="white", font=("Microsoft YaHei", 9),
|
||||
relief=tk.FLAT, padx=8, cursor="hand2")
|
||||
style_button(copy_btn, "#3498db", "#5dade2")
|
||||
copy_btn.pack(anchor=tk.E, pady=2)
|
||||
|
||||
# 激活码框架
|
||||
key_frame = tk.Frame(root, bg="#ffffff")
|
||||
key_frame.pack(fill=tk.X, padx=30, pady=10)
|
||||
|
||||
tk.Label(key_frame, text="激活码:", bg="#ffffff",
|
||||
font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W)
|
||||
|
||||
key_entry = tk.Entry(key_frame, width=40, font=("Consolas", 12),
|
||||
bg="#fbfbfb", relief=tk.SOLID, bd=2,
|
||||
highlightcolor="#3498db", highlightthickness=2)
|
||||
key_entry.pack(fill=tk.X, pady=6)
|
||||
|
||||
# 说明文字
|
||||
info_label = tk.Label(root, text="请输入激活码格式:XXXXX-XXXXX-XXXXX-XXXXX",
|
||||
bg="#ffffff", fg="#7f8c8d", font=("Microsoft YaHei", 9))
|
||||
info_label.pack(pady=4)
|
||||
|
||||
status_label = tk.Label(root, text="", bg="#ffffff", font=("Microsoft YaHei", 9))
|
||||
status_label.pack(pady=6)
|
||||
|
||||
# 按钮框架
|
||||
button_frame = tk.Frame(root, bg="#ffffff")
|
||||
button_frame.pack(fill=tk.X, padx=30, pady=12)
|
||||
|
||||
activate_btn = tk.Button(button_frame, text="验证并启动", command=on_activate,
|
||||
bg="#27ae60", fg="white", padx=26, pady=8,
|
||||
font=("Microsoft YaHei", 11, "bold"),
|
||||
relief=tk.FLAT, cursor="hand2")
|
||||
style_button(activate_btn, "#27ae60", "#2ecc71")
|
||||
activate_btn.pack(side=tk.LEFT, padx=8)
|
||||
|
||||
cancel_btn = tk.Button(button_frame, text="退出",
|
||||
command=lambda: [root.destroy(), sys.exit(0)],
|
||||
bg="#e74c3c", fg="white", padx=26, pady=8,
|
||||
font=("Microsoft YaHei", 11, "bold"),
|
||||
relief=tk.FLAT, cursor="hand2")
|
||||
style_button(cancel_btn, "#e74c3c", "#ff6b6b")
|
||||
cancel_btn.pack(side=tk.RIGHT, padx=8)
|
||||
|
||||
key_entry.focus()
|
||||
root.bind('<Return>', lambda e: on_activate())
|
||||
root.bind('<Escape>', lambda e: [root.destroy(), sys.exit(0)])
|
||||
root.protocol("WM_DELETE_WINDOW", lambda: [root.destroy(), sys.exit(0)])
|
||||
root.mainloop()
|
||||
|
||||
def handle_validation_result(self, success, msg, root,key):
|
||||
"""处理验证结果"""
|
||||
if success:
|
||||
messagebox.showinfo("验证成功", "激活码验证成功!正在启动程序...")
|
||||
save_license(key) # <== 新增
|
||||
root.destroy()
|
||||
|
||||
# 提取并运行原始程序
|
||||
extracted_path = self.extract_original_program()
|
||||
if extracted_path:
|
||||
self.launch_program_with_protection(extracted_path)
|
||||
else:
|
||||
messagebox.showerror("错误", "无法提取原始程序")
|
||||
sys.exit(1)
|
||||
else:
|
||||
messagebox.showerror("验证失败", msg)
|
||||
# 重置按钮状态
|
||||
for widget in root.winfo_children():
|
||||
if isinstance(widget, tk.Frame):
|
||||
for child in widget.winfo_children():
|
||||
if isinstance(child, tk.Button) and "验证" in child.cget("text"):
|
||||
child.config(state=tk.NORMAL, text="验证并启动")
|
||||
break
|
||||
|
||||
def copy_to_clipboard(self, text):
|
||||
"""复制文本到剪贴板"""
|
||||
try:
|
||||
import pyperclip
|
||||
pyperclip.copy(text)
|
||||
messagebox.showinfo("成功", "机器码已复制到剪贴板")
|
||||
except ImportError:
|
||||
# 使用tkinter的回退方法
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
root.clipboard_clear()
|
||||
root.clipboard_append(text)
|
||||
root.update()
|
||||
root.destroy()
|
||||
messagebox.showinfo("成功", "机器码已复制到剪贴板")
|
||||
except Exception as e:
|
||||
messagebox.showwarning("警告", f"无法复制到剪贴板: {str(e)}")
|
||||
|
||||
def get_machine_code(self):
|
||||
"""生成唯一机器码"""
|
||||
try:
|
||||
if os.name == 'nt': # Windows
|
||||
return self.get_windows_machine_code()
|
||||
else: # Linux/Mac
|
||||
return self.get_unix_machine_code()
|
||||
except Exception as e:
|
||||
# 回退方法
|
||||
import platform
|
||||
import uuid
|
||||
unique = f"{platform.node()}{uuid.getnode()}{platform.processor()}"
|
||||
return hashlib.md5(unique.encode("utf-8")).hexdigest()[:16].upper()
|
||||
|
||||
def get_windows_machine_code(self):
|
||||
"""获取Windows机器码"""
|
||||
try:
|
||||
# 尝试获取主板序列号
|
||||
try:
|
||||
result = subprocess.check_output(
|
||||
'wmic baseboard get serialnumber',
|
||||
shell=True, stderr=subprocess.STDOUT
|
||||
).decode().strip()
|
||||
|
||||
if "SerialNumber" in result:
|
||||
serial = result.split("\n")[1].strip()
|
||||
if serial and serial != "To Be Filled By O.E.M.":
|
||||
return hashlib.md5(serial.encode()).hexdigest()[:16].upper()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 尝试获取CPU ID
|
||||
try:
|
||||
result = subprocess.check_output(
|
||||
'wmic cpu get processorid',
|
||||
shell=True, stderr=subprocess.STDOUT
|
||||
).decode().strip()
|
||||
|
||||
if "ProcessorId" in result:
|
||||
cpu_id = result.split("\n")[1].strip()
|
||||
if cpu_id:
|
||||
return hashlib.md5(cpu_id.encode()).hexdigest()[:16].upper()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 回退
|
||||
import platform
|
||||
import uuid
|
||||
unique = f"{platform.node()}{uuid.getnode()}"
|
||||
return hashlib.md5(unique.encode()).hexdigest()[:16].upper()
|
||||
|
||||
except Exception as e:
|
||||
import platform
|
||||
import uuid
|
||||
unique = f"{platform.node()}{uuid.getnode()}"
|
||||
return hashlib.md5(unique.encode()).hexdigest()[:16].upper()
|
||||
|
||||
def get_unix_machine_code(self):
|
||||
"""获取Unix/Linux机器码"""
|
||||
try:
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
# 尝试获取MAC地址
|
||||
mac = uuid.getnode()
|
||||
mac_str = ':'.join(['{:02x}'.format((mac >> elements) & 0xff)
|
||||
for elements in range(0, 2 * 6, 2)][::-1])
|
||||
|
||||
# 尝试获取machine-id或hostname
|
||||
try:
|
||||
with open('/etc/machine-id', 'r') as f:
|
||||
machine_id = f.read().strip()
|
||||
unique = f"{mac_str}{machine_id}"
|
||||
except:
|
||||
import platform
|
||||
unique = f"{mac_str}{platform.node()}"
|
||||
|
||||
return hashlib.md5(unique.encode()).hexdigest()[:16].upper()
|
||||
|
||||
except Exception as e:
|
||||
import platform
|
||||
import uuid
|
||||
unique = f"{platform.node()}{uuid.getnode()}"
|
||||
return hashlib.md5(unique.encode()).hexdigest()[:16].upper()
|
||||
|
||||
def load_resource_data(self):
|
||||
"""从嵌入的 RESOURCE_DATA 中加载原始EXE和配置"""
|
||||
try:
|
||||
if RESOURCE_DATA is None:
|
||||
return False, "未找到嵌入的资源数据", None
|
||||
|
||||
# 提取原始exe内容
|
||||
original_hex = RESOURCE_DATA['original_exe']
|
||||
original_content = bytes.fromhex(original_hex)
|
||||
|
||||
# 提取配置
|
||||
config = {
|
||||
'original_size': RESOURCE_DATA['original_size'],
|
||||
'file_hash': RESOURCE_DATA['file_hash'],
|
||||
'db_config': RESOURCE_DATA['db_config']
|
||||
}
|
||||
|
||||
return True, config, original_content
|
||||
|
||||
except Exception as e:
|
||||
return False, f"加载资源失败: {str(e)}", None
|
||||
|
||||
def validate_license(self, license_key, machine_code):
|
||||
"""验证许可证"""
|
||||
try:
|
||||
# 验证密钥格式
|
||||
parts = license_key.split('-')
|
||||
if len(parts) != 4:
|
||||
return False, "激活码格式错误,应为:XXXXX-XXXXX-XXXXX-XXXXX"
|
||||
|
||||
for part in parts:
|
||||
if len(part) != 5:
|
||||
return False, "激活码格式错误,每段应为5个字符"
|
||||
|
||||
# 加载嵌入的资源数据
|
||||
success, config, original_content = self.load_resource_data()
|
||||
if not success:
|
||||
return False, f"无法读取配置:{config}"
|
||||
|
||||
# 提取数据库配置
|
||||
db_config = config.get('db_config', {})
|
||||
host = db_config.get('host', 'localhost')
|
||||
database = db_config.get('database', 'license_system')
|
||||
user = db_config.get('user', '')
|
||||
password = db_config.get('password', '')
|
||||
|
||||
# 连接MySQL数据库
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=host,
|
||||
user=user,
|
||||
password=password,
|
||||
database=database,
|
||||
connection_timeout=10,
|
||||
autocommit=True
|
||||
)
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
except mysql.connector.Error as e:
|
||||
return False, f"数据库连接失败:{str(e)}"
|
||||
|
||||
try:
|
||||
# 检查许可证是否存在且有效
|
||||
query = "SELECT * FROM license_keys WHERE key_code = %s"
|
||||
cursor.execute(query, (license_key,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
return False, "激活码不存在"
|
||||
|
||||
if result['status'] == 'banned':
|
||||
return False, "激活码已被封禁"
|
||||
|
||||
if result['end_time'] < datetime.now():
|
||||
cursor.execute(
|
||||
"UPDATE license_keys SET status = 'expired' WHERE key_code = %s",
|
||||
(license_key,)
|
||||
)
|
||||
return False, "激活码已过期"
|
||||
|
||||
if result['status'] == 'active':
|
||||
if result['machine_code'] != machine_code:
|
||||
return False, f"此激活码已在其他设备上使用(设备ID:{result['machine_code'][:8]}...)"
|
||||
else:
|
||||
return True, "激活验证成功"
|
||||
|
||||
if result['status'] == 'unused':
|
||||
cursor.execute("""
|
||||
UPDATE license_keys
|
||||
SET status = 'active', machine_code = %s, start_time = NOW()
|
||||
WHERE key_code = %s AND status = 'unused'
|
||||
""", (machine_code, license_key))
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
return False, "激活码已被其他用户使用"
|
||||
|
||||
return True, "激活成功"
|
||||
|
||||
return False, f"激活码状态异常:{result['status']}"
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
except Exception as e:
|
||||
return False, f"验证过程出错:{str(e)}"
|
||||
|
||||
|
||||
|
||||
def extract_original_program(self):
|
||||
"""从嵌入资源中提取原始exe到临时文件"""
|
||||
try:
|
||||
success, config, original_content = self.load_resource_data()
|
||||
if not success:
|
||||
messagebox.showerror("错误", config)
|
||||
return None
|
||||
|
||||
# 验证完整性
|
||||
expected_hash = config['file_hash']
|
||||
actual_hash = hashlib.sha256(original_content).hexdigest()
|
||||
if expected_hash != actual_hash:
|
||||
messagebox.showerror("错误", "原始文件校验失败")
|
||||
return None
|
||||
|
||||
# 创建临时文件
|
||||
temp_dir = tempfile.gettempdir()
|
||||
temp_file_path = os.path.join(temp_dir, f"original_program_{os.getpid()}.exe")
|
||||
|
||||
with open(temp_file_path, 'wb') as f:
|
||||
f.write(original_content)
|
||||
|
||||
# 设置可执行权限
|
||||
if os.name != 'nt':
|
||||
os.chmod(temp_file_path, 0o755)
|
||||
|
||||
self.original_exe_path = temp_file_path
|
||||
return temp_file_path
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"提取原始程序失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def _simple_decrypt(self, data):
|
||||
"""简化版XOR解密"""
|
||||
key = b'EXEProtector#2024'
|
||||
decrypted = bytearray(data)
|
||||
key_len = len(key)
|
||||
|
||||
for i in range(len(decrypted)):
|
||||
decrypted[i] ^= key[i % key_len]
|
||||
|
||||
return bytes(decrypted)
|
||||
|
||||
def _decompress_data(self, data):
|
||||
"""解压缩数据"""
|
||||
import zlib
|
||||
return zlib.decompress(data)
|
||||
|
||||
def launch_program_with_protection(self, program_path):
|
||||
"""启动程序并应用防护措施"""
|
||||
try:
|
||||
# 应用防护措施
|
||||
self.apply_protection_measures()
|
||||
|
||||
# 启动程序
|
||||
if os.name == 'nt': # Windows
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
startupinfo.wShowWindow = win32con.SW_SHOW # ✅ 正确来源
|
||||
|
||||
process = subprocess.Popen([program_path], startupinfo=startupinfo)
|
||||
|
||||
# process = subprocess.Popen([program_path], startupinfo=startupinfo)
|
||||
self.process_handle = process.pid
|
||||
|
||||
# 启动监控线程
|
||||
if self.process_monitoring:
|
||||
threading.Thread(target=self.monitor_process,
|
||||
args=(process.pid,), daemon=True).start()
|
||||
|
||||
# 等待程序启动
|
||||
time.sleep(1)
|
||||
|
||||
# 退出验证器
|
||||
sys.exit(0)
|
||||
|
||||
else: # Linux/Mac
|
||||
subprocess.Popen([program_path])
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"启动程序失败: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
def apply_protection_measures(self):
|
||||
"""应用防护措施"""
|
||||
if self.anti_debug_enabled:
|
||||
self.enable_anti_debug()
|
||||
|
||||
if self.anti_vm_enabled:
|
||||
self.enable_anti_vm()
|
||||
|
||||
def enable_anti_debug(self):
|
||||
"""启用反调试保护"""
|
||||
try:
|
||||
if os.name == 'nt': # Windows
|
||||
# 检查调试器
|
||||
if ctypes.windll.kernel32.IsDebuggerPresent():
|
||||
sys.exit(0)
|
||||
|
||||
# 设置调试标志
|
||||
ctypes.windll.kernel32.SetProcessDEPPolicy(0x00000001)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def enable_anti_vm(self):
|
||||
"""启用反虚拟机保护"""
|
||||
try:
|
||||
if os.name == 'nt': # Windows
|
||||
# 检查常见的虚拟机进程
|
||||
vm_processes = [
|
||||
'vmsrvc.exe', 'vmusrvc.exe', 'vmtoolsd.exe',
|
||||
'vboxservice.exe', 'vboxtray.exe', 'vboxcontrol.exe',
|
||||
'vmwaretray.exe', 'vmwareuser.exe', 'vmusrvc.exe'
|
||||
]
|
||||
|
||||
for proc in psutil.process_iter(['name']):
|
||||
try:
|
||||
if proc.info['name'].lower() in vm_processes:
|
||||
sys.exit(0)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def monitor_process(self, pid):
|
||||
"""监控进程状态"""
|
||||
try:
|
||||
while True:
|
||||
time.sleep(5) # 每5秒检查一次
|
||||
|
||||
# 检查进程是否还在运行
|
||||
try:
|
||||
process = psutil.Process(pid)
|
||||
if not process.is_running():
|
||||
break
|
||||
except psutil.NoSuchProcess:
|
||||
break
|
||||
|
||||
# 检查是否有调试器附加
|
||||
if self.anti_debug_enabled:
|
||||
try:
|
||||
if ctypes.windll.kernel32.IsDebuggerPresent():
|
||||
process.terminate()
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
# 清理临时文件
|
||||
self.cleanup_temp_files()
|
||||
|
||||
def cleanup_temp_files(self):
|
||||
"""清理临时文件"""
|
||||
try:
|
||||
if self.original_exe_path and os.path.exists(self.original_exe_path):
|
||||
os.remove(self.original_exe_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
import requests, socket, os, json, tempfile, base64, time
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
CACHE_FILE = LICENSE_FILE
|
||||
|
||||
def online_check(host='taiyiagi.xyz', port=3306, timeout=3):
|
||||
"""简单检测能否连上数据库服务器"""
|
||||
try:
|
||||
socket.create_connection((host, port), timeout)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def verify_with_fallback(validator, key):
|
||||
"""有网:联网复检;无网:本地缓存兜底"""
|
||||
db_config = validator.load_resource_data()[1]['db_config']
|
||||
host = db_config.get('host', 'localhost')
|
||||
port = int(db_config.get('port', 3306))
|
||||
if online_check(host, port):
|
||||
# 有网:强制联网验证
|
||||
return validator.validate_license(key, validator.get_machine_code())
|
||||
else:
|
||||
# 无网:尝试使用本地缓存(LICENSE_FILE)
|
||||
try:
|
||||
if os.path.exists(LICENSE_FILE):
|
||||
with open(LICENSE_FILE, 'rb') as f:
|
||||
data = json.loads(_get_fernet().decrypt(f.read()).decode())
|
||||
cached_key = data.get('key')
|
||||
if cached_key:
|
||||
return validator.validate_license(cached_key, validator.get_machine_code())
|
||||
except Exception:
|
||||
pass
|
||||
return False, "网络不可用且本地缓存失效"
|
||||
|
||||
|
||||
def main():
|
||||
validator = EXEWrapperValidator()
|
||||
|
||||
# 控制台调试模式(方便调试)
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '--console':
|
||||
print("调试模式")
|
||||
machine_code = validator.get_machine_code()
|
||||
print(f"机器码: {machine_code}")
|
||||
license_key = input("请输入激活码: ").strip()
|
||||
if license_key:
|
||||
success, msg = validator.validate_license(license_key, machine_code)
|
||||
print(f"验证结果: {success} - {msg}")
|
||||
if success:
|
||||
save_license(license_key)
|
||||
extracted = validator.extract_original_program()
|
||||
if extracted:
|
||||
validator.launch_program_with_protection(extracted)
|
||||
return
|
||||
|
||||
# 1) 启动时优先加载本地缓存卡密(自动验证)
|
||||
cached_key = load_license()
|
||||
if cached_key:
|
||||
success, msg = validator.validate_license(cached_key, validator.get_machine_code())
|
||||
if success:
|
||||
extracted = validator.extract_original_program()
|
||||
if extracted:
|
||||
validator.launch_program_with_protection(extracted)
|
||||
return
|
||||
else:
|
||||
# 本地缓存不可用,删除并提示
|
||||
try:
|
||||
os.remove(LICENSE_FILE)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
# 尝试用GUI提示(如果可用)
|
||||
tmp = tk.Tk(); tmp.withdraw()
|
||||
messagebox.showwarning("提示", f"本地授权失效:{msg}")
|
||||
tmp.destroy()
|
||||
except:
|
||||
print("本地授权失效:", msg)
|
||||
|
||||
# 2) 无有效授权,弹出 GUI 输入框
|
||||
validator.show_license_dialog()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
50
validator_wrapper.spec
Normal file
50
validator_wrapper.spec
Normal file
@ -0,0 +1,50 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['validator_wrapper.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='validator_wrapper',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='validator_wrapper',
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user