import os import shutil import subprocess import tempfile import json import struct import hashlib import zlib from typing import Tuple, Optional import time class EXEEncryptor: """EXE文件加密器类 - 提供EXE文件加密、编译和验证功能""" def __init__(self): """初始化加密器""" pass # encryptor.py def encrypt_file(self, source_path: str, output_path: str, validator_path: str, db_config: dict) -> Tuple[ bool, str]: """重新实现:将原始exe作为资源嵌入验证器exe中""" try: # 读取原始exe内容(作为资源) with open(source_path, 'rb') as f: original_content = f.read() # 计算原始exe的哈希(用于验证完整性) file_hash = hashlib.sha256(original_content).hexdigest() # 创建资源数据(原始exe + 配置信息) resource_data = { 'original_exe': original_content.hex(), # 十六进制字符串 'original_size': len(original_content), 'file_hash': file_hash, 'db_config': db_config, 'encryption_time': int(time.time()) } # 将资源数据写入Python文件(作为字符串常量) resource_code = json.dumps(resource_data, separators=(',', ':')) # 读取验证器模板(validator_wrapper.py) # with open(validator_path, 'r', encoding='utf-8') as f: # 读取验证器模板 with open(validator_path, 'r', encoding='utf-8', errors='ignore') as f: validator_template = f.read() # 构造资源数据(确保是合法 JSON) resource_data = { 'original_exe': original_content.hex(), 'original_size': len(original_content), 'file_hash': file_hash, 'db_config': db_config, } # 插入资源数据到验证器模板中 final_validator_code = validator_template.replace( '# RESOURCE_DATA_PLACEHOLDER\nRESOURCE_DATA = None', f'RESOURCE_DATA = {json.dumps(resource_data)}' ) print("插入后的代码片段:") print(final_validator_code[:500]) # 打印前500字符 # 写入临时文件 temp_validator_py = os.path.join(tempfile.gettempdir(), 'temp_validator.py') with open(temp_validator_py, 'w', encoding='utf-8') as f: f.write(final_validator_code) # 调试:检查是否插入成功 with open(temp_validator_py, 'r', encoding='utf-8') as f: content = f.read() if 'RESOURCE_DATA = {"original_exe"' in content: print("✅ 资源数据已成功插入") else: print("❌ 资源数据未插入,请检查替换逻辑") # 使用PyInstaller将验证器编译为exe success, msg = self.compile_to_exe(temp_validator_py, output_path) if not success: return False, msg # 清理临时文件 if os.path.exists(temp_validator_py): os.remove(temp_validator_py) return True, f"壳程序编译成功:{output_path}" except Exception as e: return False, f"套壳失败:{str(e)}" def _create_encryption_header(self, original_size: int, db_config: dict, file_hash: str) -> bytes: """创建加密文件头信息""" header_data = { 'version': '2.0', 'original_size': original_size, 'encryption_time': int(time.time()), 'compression_level': 6, 'file_hash': file_hash, 'db_config': db_config } return json.dumps(header_data).encode('utf-8').ljust(256, b'\x00') def _simple_encrypt(self, data: bytes) -> bytes: """简化版XOR加密""" key = b'EXEProtector#2024' encrypted = bytearray(data) key_len = len(key) for i in range(len(encrypted)): encrypted[i] ^= key[i % key_len] return bytes(encrypted) def compile_to_exe(self, python_file: str, output_path: str = None) -> Tuple[bool, str]: """尝试将Python文件编译为EXE""" try: # 确保Python文件存在 py_file = python_file.replace('.exe', '.py') if not os.path.exists(py_file): return False, f"Python源文件不存在: {py_file}" print(f"Debug: 开始编译 {py_file} 到 {python_file}") # 检查PyInstaller try: result = subprocess.run(['pyinstaller', '--version'], capture_output=True, text=True, timeout=10) if result.returncode != 0: return self._fallback_to_python_file(py_file, python_file) except (subprocess.TimeoutExpired, FileNotFoundError): return self._fallback_to_python_file(py_file, python_file) # 获取文件信息 file_dir = os.path.dirname(os.path.abspath(py_file)) exe_name = os.path.basename(python_file).replace('.exe', '') # 切换到文件目录 original_cwd = os.getcwd() os.chdir(file_dir) try: # 创建规范文件内容 spec_content = self._create_spec_content(py_file, exe_name) spec_file = f'{exe_name}.spec' with open(spec_file, 'w', encoding='utf-8') as f: f.write(spec_content) print(f"Debug: 创建规范文件: {spec_file}") # 使用规范文件编译 cmd = ['pyinstaller', '--clean', '--noconfirm', spec_file] print(f"Debug: 执行编译命令: {' '.join(cmd)}") # 执行编译 result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) if result.returncode == 0: # 检查输出文件 output_exe = os.path.join('dist', f'{exe_name}.exe') if os.path.exists(output_exe): # 移动到目标位置 target_path = os.path.basename(python_file) final_output = output_path or os.path.basename(python_file).replace('.py', '.exe') os.makedirs(os.path.dirname(final_output), exist_ok=True) if os.path.exists(final_output): os.remove(final_output) shutil.move(output_exe, final_output) print(f"Debug: 编译成功,输出文件: {final_output}") return True, "EXE编译成功" else: print(f"Debug: 找不到输出文件: {output_exe}") return False, "编译完成但找不到输出文件" else: # 编译失败,保留Python文件 error_info = result.stderr[:500] if result.stderr else result.stdout[:500] print(f"Debug: 编译失败: {error_info}") return self._fallback_to_python_file(py_file, python_file, f"编译失败: {error_info}") finally: os.chdir(original_cwd) except subprocess.TimeoutExpired: print("Debug: 编译超时") return self._fallback_to_python_file(py_file, python_file, "编译超时") except Exception as e: print(f"Debug: 编译过程异常: {e}") return False, f"编译过程异常: {str(e)}" def _create_spec_content(self, py_file: str, exe_name: str) -> str: """创建PyInstaller规范文件内容""" return f'''# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['{os.path.basename(py_file)}'], pathex=[], binaries=[], datas=[], hiddenimports=[ 'mysql.connector', 'cryptography.fernet', 'tkinter', 'tkinter.messagebox', 'tkinter.scrolledtext', 'hashlib', 'subprocess', 'tempfile', 'threading', 'atexit', 'psutil', 'ctypes', 'win32api', 'win32con', 'win32security', 'win32process', ], 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, a.binaries, a.zipfiles, a.datas, [], name='{exe_name}', debug=False, bootloader_ignore_signals=False, strip=False, upx=False, upx_exclude=[], runtime_tmpdir=None, console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, uac_admin=False, uac_uiaccess=False, ) ''' def _fallback_to_python_file(self, py_file: str, target_file: str, reason: str = "PyInstaller不可用") -> Tuple[bool, str]: """回退到Python文件""" try: # 将.exe改为.py python_target = target_file.replace('.exe', '.py') if py_file != python_target: if os.path.exists(python_target): os.remove(python_target) shutil.copy2(py_file, python_target) message = f"{reason},使用Python文件: {python_target}" print(f"Debug: {message}") return True, message except Exception as e: error_msg = f"回退失败: {str(e)}" print(f"Debug: {error_msg}") return False, error_msg def _cleanup_build_files(self, exe_name: str): """清理编译产生的临时文件""" try: # 删除build目录 if os.path.exists('build'): shutil.rmtree('build', ignore_errors=True) # 删除dist目录 if os.path.exists('dist'): shutil.rmtree('dist', ignore_errors=True) # 删除spec文件 spec_file = f'{exe_name}.spec' if os.path.exists(spec_file): os.remove(spec_file) print("Debug: 清理临时文件完成") except Exception as e: print(f"Debug: 清理临时文件时出错: {e}")