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() |