290 lines
9.4 KiB
Python
290 lines
9.4 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
EXE文件验证程序 - 负责验证卡密并解密执行原程序
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import sys
|
|||
|
|
import os
|
|||
|
|
import tempfile
|
|||
|
|
import subprocess
|
|||
|
|
import mysql.connector
|
|||
|
|
import atexit
|
|||
|
|
from cryptography.fernet import Fernet
|
|||
|
|
from machine_code import get_machine_code
|
|||
|
|
from database import LicenseDatabase
|
|||
|
|
import mysql
|
|||
|
|
import json
|
|||
|
|
import ctypes
|
|||
|
|
from ctypes import wintypes
|
|||
|
|
import time
|
|||
|
|
|
|||
|
|
|
|||
|
|
class EXEValidator:
|
|||
|
|
def __init__(self):
|
|||
|
|
self.separator = b"<<EXE_ENCRYPTED_DATA>>"
|
|||
|
|
self.temp_files = []
|
|||
|
|
# 注册清理函数
|
|||
|
|
atexit.register(self.cleanup)
|
|||
|
|
|
|||
|
|
def cleanup(self):
|
|||
|
|
"""清理临时文件"""
|
|||
|
|
for temp_file in self.temp_files:
|
|||
|
|
try:
|
|||
|
|
if os.path.exists(temp_file):
|
|||
|
|
os.remove(temp_file)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def hide_console(self):
|
|||
|
|
"""隐藏控制台窗口(Windows系统)"""
|
|||
|
|
try:
|
|||
|
|
if sys.platform == "win32":
|
|||
|
|
kernel32 = ctypes.windll.kernel32
|
|||
|
|
user32 = ctypes.windll.user32
|
|||
|
|
SW_HIDE = 0
|
|||
|
|
hWnd = kernel32.GetConsoleWindow()
|
|||
|
|
if hWnd:
|
|||
|
|
user32.ShowWindow(hWnd, SW_HIDE)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def show_message_box(self, title, message, msg_type="info"):
|
|||
|
|
"""显示消息框"""
|
|||
|
|
try:
|
|||
|
|
if sys.platform == "win32":
|
|||
|
|
import ctypes
|
|||
|
|
if msg_type == "error":
|
|||
|
|
icon = 0x10 # MB_ICONERROR
|
|||
|
|
elif msg_type == "warning":
|
|||
|
|
icon = 0x30 # MB_ICONWARNING
|
|||
|
|
else:
|
|||
|
|
icon = 0x40 # MB_ICONINFORMATION
|
|||
|
|
|
|||
|
|
ctypes.windll.user32.MessageBoxW(0, message, title, icon)
|
|||
|
|
else:
|
|||
|
|
print(f"{title}: {message}")
|
|||
|
|
except:
|
|||
|
|
print(f"{title}: {message}")
|
|||
|
|
|
|||
|
|
def get_license_key_input(self):
|
|||
|
|
"""获取用户输入的卡密"""
|
|||
|
|
try:
|
|||
|
|
if sys.platform == "win32":
|
|||
|
|
# Windows系统使用InputBox
|
|||
|
|
import ctypes
|
|||
|
|
from ctypes import wintypes
|
|||
|
|
|
|||
|
|
# 使用VBScript创建输入框
|
|||
|
|
vbs_code = '''
|
|||
|
|
Dim userInput
|
|||
|
|
userInput = InputBox("请输入您的授权卡密:", "软件授权验证", "")
|
|||
|
|
WScript.Echo userInput
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
# 创建临时VBS文件
|
|||
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.vbs', delete=False) as f:
|
|||
|
|
f.write(vbs_code)
|
|||
|
|
vbs_path = f.name
|
|||
|
|
|
|||
|
|
self.temp_files.append(vbs_path)
|
|||
|
|
|
|||
|
|
# 执行VBS脚本获取输入
|
|||
|
|
result = subprocess.run(['cscript', '//NoLogo', vbs_path],
|
|||
|
|
capture_output=True, text=True)
|
|||
|
|
|
|||
|
|
if result.returncode == 0:
|
|||
|
|
user_input = result.stdout.strip()
|
|||
|
|
if user_input:
|
|||
|
|
return user_input
|
|||
|
|
|
|||
|
|
return None
|
|||
|
|
else:
|
|||
|
|
# 非Windows系统使用命令行输入
|
|||
|
|
return input("请输入您的授权卡密: ").strip()
|
|||
|
|
except:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def extract_encrypted_data(self, exe_path):
|
|||
|
|
"""从加密的EXE文件中提取数据"""
|
|||
|
|
try:
|
|||
|
|
with open(exe_path, 'rb') as f:
|
|||
|
|
data = f.read()
|
|||
|
|
|
|||
|
|
# 查找分隔符位置
|
|||
|
|
separators = []
|
|||
|
|
start = 0
|
|||
|
|
while True:
|
|||
|
|
pos = data.find(self.separator, start)
|
|||
|
|
if pos == -1:
|
|||
|
|
break
|
|||
|
|
separators.append(pos)
|
|||
|
|
start = pos + len(self.separator)
|
|||
|
|
|
|||
|
|
if len(separators) < 3:
|
|||
|
|
return None, "文件格式错误:分隔符不足"
|
|||
|
|
|
|||
|
|
# 提取各部分数据
|
|||
|
|
# 结构: [验证程序, 分隔符, 数据库配置, 分隔符, 密钥, 分隔符, 加密数据]
|
|||
|
|
db_config_start = separators[0] + len(self.separator)
|
|||
|
|
db_config_end = separators[1]
|
|||
|
|
|
|||
|
|
key_start = separators[1] + len(self.separator)
|
|||
|
|
key_end = separators[2]
|
|||
|
|
|
|||
|
|
encrypted_data_start = separators[2] + len(self.separator)
|
|||
|
|
|
|||
|
|
db_config_str = data[db_config_start:db_config_end].decode('utf-8')
|
|||
|
|
key = data[key_start:key_end]
|
|||
|
|
encrypted_data = data[encrypted_data_start:]
|
|||
|
|
|
|||
|
|
# 解析数据库配置
|
|||
|
|
config_parts = db_config_str.split('|')
|
|||
|
|
if len(config_parts) != 4:
|
|||
|
|
return None, "数据库配置格式错误"
|
|||
|
|
|
|||
|
|
db_config = {
|
|||
|
|
'host': config_parts[0],
|
|||
|
|
'database': config_parts[1],
|
|||
|
|
'user': config_parts[2],
|
|||
|
|
'password': config_parts[3]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'db_config': db_config,
|
|||
|
|
'key': key,
|
|||
|
|
'encrypted_data': encrypted_data
|
|||
|
|
}, "成功"
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return None, f"提取数据失败: {str(e)}"
|
|||
|
|
|
|||
|
|
def validate_license(self, db_config, license_key):
|
|||
|
|
"""验证卡密"""
|
|||
|
|
try:
|
|||
|
|
# 获取机器码
|
|||
|
|
machine_code = get_machine_code()
|
|||
|
|
|
|||
|
|
# 连接数据库
|
|||
|
|
db = LicenseDatabase(
|
|||
|
|
db_config['host'],
|
|||
|
|
db_config['database'],
|
|||
|
|
db_config['user'],
|
|||
|
|
db_config['password']
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not db.connect():
|
|||
|
|
return False, "无法连接到授权服务器"
|
|||
|
|
|
|||
|
|
# 验证卡密
|
|||
|
|
success, message = db.validate_key(license_key, machine_code)
|
|||
|
|
db.close()
|
|||
|
|
|
|||
|
|
return success, message
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return False, f"验证过程出错: {str(e)}"
|
|||
|
|
|
|||
|
|
def decrypt_and_run(self, key, encrypted_data):
|
|||
|
|
"""解密并运行原程序"""
|
|||
|
|
try:
|
|||
|
|
# 解密数据
|
|||
|
|
fernet = Fernet(key)
|
|||
|
|
decrypted_data = fernet.decrypt(encrypted_data)
|
|||
|
|
|
|||
|
|
# 创建临时文件
|
|||
|
|
with tempfile.NamedTemporaryFile(suffix='.exe', delete=False) as temp_file:
|
|||
|
|
temp_file.write(decrypted_data)
|
|||
|
|
temp_exe_path = temp_file.name
|
|||
|
|
|
|||
|
|
self.temp_files.append(temp_exe_path)
|
|||
|
|
|
|||
|
|
# 设置可执行权限
|
|||
|
|
if os.name == 'nt':
|
|||
|
|
# Windows系统
|
|||
|
|
import stat
|
|||
|
|
os.chmod(temp_exe_path, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
|
|||
|
|
|
|||
|
|
# 执行程序
|
|||
|
|
subprocess.Popen([temp_exe_path],
|
|||
|
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0)
|
|||
|
|
|
|||
|
|
return True, "程序启动成功"
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
return False, f"解密或执行失败: {str(e)}"
|
|||
|
|
|
|||
|
|
def run(self):
|
|||
|
|
"""主运行函数"""
|
|||
|
|
# 隐藏控制台窗口
|
|||
|
|
self.hide_console()
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 获取当前程序路径
|
|||
|
|
if getattr(sys, 'frozen', False):
|
|||
|
|
exe_path = sys.executable
|
|||
|
|
else:
|
|||
|
|
exe_path = os.path.abspath(__file__)
|
|||
|
|
|
|||
|
|
# 提取加密数据
|
|||
|
|
extracted_data, message = self.extract_encrypted_data(exe_path)
|
|||
|
|
if not extracted_data:
|
|||
|
|
self.show_message_box("错误", f"读取授权信息失败: {message}", "error")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 最多尝试3次输入卡密
|
|||
|
|
max_attempts = 3
|
|||
|
|
for attempt in range(max_attempts):
|
|||
|
|
# 获取用户输入的卡密
|
|||
|
|
license_key = self.get_license_key_input()
|
|||
|
|
if not license_key:
|
|||
|
|
if attempt < max_attempts - 1:
|
|||
|
|
self.show_message_box("提示", "请输入有效的卡密", "warning")
|
|||
|
|
continue
|
|||
|
|
else:
|
|||
|
|
self.show_message_box("错误", "未输入卡密,程序将退出", "error")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 验证卡密
|
|||
|
|
success, message = self.validate_license(extracted_data['db_config'], license_key)
|
|||
|
|
|
|||
|
|
if success:
|
|||
|
|
# 验证成功,解密并运行程序
|
|||
|
|
run_success, run_message = self.decrypt_and_run(
|
|||
|
|
extracted_data['key'],
|
|||
|
|
extracted_data['encrypted_data']
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if run_success:
|
|||
|
|
# 给程序一些时间启动
|
|||
|
|
time.sleep(1)
|
|||
|
|
return True
|
|||
|
|
else:
|
|||
|
|
self.show_message_box("错误", f"程序启动失败: {run_message}", "error")
|
|||
|
|
return False
|
|||
|
|
else:
|
|||
|
|
# 验证失败
|
|||
|
|
if attempt < max_attempts - 1:
|
|||
|
|
self.show_message_box("验证失败", f"{message}\n\n剩余尝试次数: {max_attempts - attempt - 1}",
|
|||
|
|
"warning")
|
|||
|
|
else:
|
|||
|
|
self.show_message_box("验证失败", f"{message}\n\n已达到最大尝试次数,程序将退出", "error")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
self.show_message_box("系统错误", f"程序运行出错: {str(e)}", "error")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
"""主函数"""
|
|||
|
|
validator = EXEValidator()
|
|||
|
|
success = validator.run()
|
|||
|
|
|
|||
|
|
if not success:
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|