Exeprotector/encryptor.py

310 lines
10 KiB
Python
Raw Permalink Normal View History

2025-08-08 18:28:46 +08:00
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}")