【写在前面】
本人测试过,确实有效,截图见下方。
开源分享出来,供有兴趣的朋友参考。 平时不怎么登这个论坛,留言可能几个月才回一次。
【功能简介】
- 三引擎联网:VirusTotal + 微步在线 + 魔盾安全
- 智能解包:自动提取 .xp3/.dll/.exe 中的可读字符串
- 本地深度检测:YARA规则 + 加壳检测 + 数字签名验证
- 详细HTML报告:高风险文件逐个说明原因,小白也能看懂
- 游戏目录识别:自动识别游戏文件,大幅降低误报
【使用方法】
▎第一步:安装Python依赖
打开命令行,输入: pip install requests pefile colorama
▎第二步:创建文件夹结构
你的文件夹/ ├── main.py # 主程序(放外面) └── 天晴启动器/ # 工具文件夹(放里面) ├── strings.exe ├── sigcheck.exe └── diec.exe
三个exe下载地址:
- strings.exe:https://download.sysinternals.com/files/Strings.zip
- sigcheck.exe:https://download.sysinternals.com/files/Sigcheck.zip
- diec.exe:https://github.com/horsicq/Detect-It-Easy/releases
▎第三步:保存代码
将下方代码复制,保存为 main.py
▎第四步:运行扫描
双击 main.py,按提示操作:
- 选择扫描深度(推荐 3)
- 选择是否联网(推荐 y)
- 拖入游戏文件夹,回车
- 等待扫描完成,自动打开报告
可选:以管理员身份运行一次,选择 [4] 安装右键菜单, 之后右键点击文件/文件夹即可直接扫描。
【联网配置】(可选)
如果想要联网查询功能,需要申请两个API Key。
申请地址:
- VirusTotal:https://www.virustotal.com/
- 微步在线:https://x.threatbook.cn/
配置文件 apikey_config.py(第一次运行会自动生成): VT_API_KEY = "你的VirusTotal密钥" THREATBOOK_API_KEY = "你的微步在线密钥"
注意:
- 不配置也不影响本地扫描
- 只上传文件哈希值,不上传文件内容
【扫描模式说明】
[1] 轻度 - 查壳+签名(速度最快) [2] 中度 - 轻度+字符串+YARA(标准模式,推荐) [3] 深度 - 中度+解包分析(最全面,速度较慢) 代码(可以直接复制到空的TXT文件然后改成py运行,代码全公开,支持杀软扫描,第一次发补贴子,有点不会复制代码)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
天晴安全分析台 v6.2 Final - 完整稳定版
功能:游戏安全分析 + AI辅助编程 + 病毒挖掘 + 启动自检 + 右键菜单
+ YARA规则 + 智能解包 + 缓存优化 + 三引擎联网查询(VT+微步+魔盾)
+ 游戏目录识别 + 白名单机制 + 详细报告(高风险逐条说明)
Developer: 天晴
"""
import sys
import os
import ctypes
import hashlib
import subprocess
import shutil
import time
import json
import re
import webbrowser
import threading
import concurrent.futures
import tempfile
import uuid
import atexit
import signal
import gc
from pathlib import Path
from functools import wraps
from contextlib import contextmanager
from collections import namedtuple
# ================================================================
# 常量定义
# ================================================================
SCRIPT_DIR = Path(__file__).parent.absolute()
TOOLS_DIR = SCRIPT_DIR / "天晴启动器"
NOTES_DIR = SCRIPT_DIR / "游戏笔记"
AI_OUTPUT_DIR = SCRIPT_DIR / "AI生成"
VIRUS_LIB_DIR = SCRIPT_DIR / "毒库大全"
PATCH_LIB_DIR = SCRIPT_DIR / "补丁库"
BASELINE_DIR = SCRIPT_DIR / "baselines"
HASH_CACHE_FILE = SCRIPT_DIR / "hash_cache.json"
DISCLAIMER_FILE = SCRIPT_DIR / ".disclaimer_agreed"
API_CONFIG = SCRIPT_DIR / "apikey_config.py"
WHITELIST_FILE = BASELINE_DIR / "game_whitelist.json"
# 配置参数
MAX_EXTRACT_SIZE = 20 * 1024 * 1024
YARA_MAX_SIZE = 5 * 1024 * 1024
CACHE_EXPIRE_DAYS = 7
MAX_CACHE_ENTRIES = 2000
SCAN_TIMEOUT = 60
# 文件过滤
SKIP_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".mp3", ".wav", ".flac", ".ogg", ".mp4", ".avi", ".mkv", ".mov"}
TARGET_EXTS = {".exe", ".dll", ".scr", ".xp3", ".ks", ".tjs", ".py", ".lua", ".txt", ".json", ".xml", ".ini", ".rpy", ".dat", ".arc", ".pak"}
# 游戏关键词(用于降低误报)
GAME_KEYWORDS = [
'game', 'nobleworks', '柚子', '夏空', '天色', 'kr', 'xp3',
'voice', 'bgm', 'bgimage', 'fgimage', 'video', 'patch',
'yuzu', 'sabbat', 'islenauts', 'parquet', 'limelight', 'dracuriot',
'汉化', 'chs', 'crack', 'DRACU-RIOT'
]
# 无害字符串(游戏常见)
SAFE_STRINGS = ['http://', 'https://', 'CreateProcess', 'cmd.exe',
'powershell', 'base64', 'eval(', 'loadplugin']
# 必要工具
REQUIRED_TOOLS = {
"strings.exe": ("Strings", "https://download.sysinternals.com/files/Strings.zip"),
"sigcheck.exe": ("Sigcheck", "https://download.sysinternals.com/files/Sigcheck.zip"),
"diec.exe": ("Detect It Easy", "https://github.com/horsicq/Detect-It-Easy/releases"),
}
# 扫描模式
SCAN_MODES = {
"1": {"name": "轻度扫描", "check_packer": True, "check_strings": False, "check_signature": True, "unpack": False},
"2": {"name": "中度扫描", "check_packer": True, "check_strings": True, "check_signature": True, "unpack": False},
"3": {"name": "深度扫描", "check_packer": True, "check_strings": True, "check_signature": True, "unpack": True},
}
# 风险模式
RISK_PATTERNS = [
(r'cmd\.exe', "执行CMD命令"),
(r'powershell', "执行PowerShell"),
(r'https?://', "HTTP请求"),
(r'base64', "Base64编码"),
(r'CreateProcess', "创建进程"),
(r'eval\(', "动态执行代码"),
]
TRUSTED_SIGNERS = ["Microsoft Corporation", "Adobe Inc.", "Valve", "Epic Games", "Google Inc."]
# API列表
API_KEY_LIST = [
("DEEPSEEK_API_KEY", "DeepSeek AI", "https://platform.deepseek.com/"),
("VT_API_KEY", "VirusTotal", "https://www.virustotal.com/"),
("THREATBOOK_API_KEY", "微步在线", "https://x.threatbook.cn/"),
]
# 全局锁
_cache_lock = threading.Lock()
_pending_lock = threading.Lock()
_print_lock = threading.Lock()
# ================================================================
# 安全打印
# ================================================================
def safe_print(*args, **kwargs):
with _print_lock:
print(*args, **kwargs)
# ================================================================
# 辅助函数
# ================================================================
def ensure_dir(path):
path.mkdir(parents=True, exist_ok=True)
return path
def get_file_hash(filepath):
try:
sha = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha.update(chunk)
return sha.hexdigest()
except:
return "读取失败"
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
def run_as_admin():
if not is_admin():
script = Path(__file__).absolute()
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{script}"', str(script.parent), 1)
sys.exit()
def is_game_file(filepath):
"""判断是否为游戏文件(用于降低误报)"""
path_lower = str(filepath).lower()
name_lower = filepath.name.lower()
for kw in GAME_KEYWORDS:
if kw in path_lower or kw in name_lower:
return True
if filepath.suffix.lower() in {'.xp3', '.arc', '.pak', '.dat'}:
return True
return False
def load_whitelist():
if WHITELIST_FILE.exists():
try:
with open(WHITELIST_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {}
def save_whitelist(whitelist):
try:
with open(WHITELIST_FILE, 'w', encoding='utf-8') as f:
json.dump(whitelist, f, indent=2, ensure_ascii=False)
except:
pass
def add_to_whitelist(filename, sha256, reason="用户确认"):
wl = load_whitelist()
wl[filename] = {"sha256": sha256, "reason": reason, "time": time.time()}
save_whitelist(wl)
# ================================================================
# 创建目录
# ================================================================
for d in [TOOLS_DIR, NOTES_DIR, AI_OUTPUT_DIR, VIRUS_LIB_DIR, PATCH_LIB_DIR, BASELINE_DIR]:
ensure_dir(d)
# ================================================================
# 初始化YARA规则
# ================================================================
def init_yara_rules():
yara_dir = TOOLS_DIR / "yara_rules"
if not yara_dir.exists():
yara_dir.mkdir(parents=True, exist_ok=True)
rules = {
"malware.yar": 'rule Malware {\n strings:\n $s1 = "VirtualAlloc"\n $s2 = "VirtualProtect"\n $s3 = "WriteProcessMemory"\n $s4 = "CreateRemoteThread"\n condition: any of them\n}',
"script.yar": 'rule Script {\n strings:\n $s1 = "eval("\n $s2 = "os.system"\n $s3 = "subprocess"\n $s4 = "socket."\n condition: any of them\n}',
}
for name, content in rules.items():
(yara_dir / name).write_text(content, encoding='utf-8')
return yara_dir
init_yara_rules()
# ================================================================
# 自检
# ================================================================
def self_check():
safe_print("\n" + "=" * 60)
safe_print("[自检] 天晴安全分析台 - 完整版")
safe_print("=" * 60)
missing = []
for fn, (name, url) in REQUIRED_TOOLS.items():
if (TOOLS_DIR / fn).exists():
safe_print(f" [是] {name}")
else:
safe_print(f" [否] {name}")
missing.append((name, fn, url))
if missing:
safe_print("\n[错误] 缺少必要工具:")
for name, fn, url in missing:
safe_print(f" • {name}: {url}")
safe_print(f"\n请下载后放入: {TOOLS_DIR}")
input("\n按回车退出...")
sys.exit(1)
wl = load_whitelist()
safe_print(f"\n[白名单] {len(wl)} 个文件")
safe_print("\n[是] 自检通过")
safe_print("=" * 60)
return True
# ================================================================
# 免责声明
# ================================================================
DISCLAIMER = """
[法律声明] 天晴安全分析台
1. 本工具仅供安全研究、学习交流使用
2. 仅通过文件哈希值查询在线数据库,不上传文件内容
3. 使用后果由使用者自行承担
4. 结果仅供参考,不替代专业杀毒软件
"""
def check_disclaimer():
if DISCLAIMER_FILE.exists():
return True
print(DISCLAIMER)
print("\n[提示] 首次使用需确认免责声明")
choice = input("同意?(y/N): ").strip().lower()
if choice == 'y':
DISCLAIMER_FILE.write_text(time.strftime("%Y-%m-%d %H:%M:%S"), encoding='utf-8')
print("[是] 已记录")
return True
return False
# ================================================================
# 缓存管理
# ================================================================
_cache = None
def get_cache():
global _cache
with _cache_lock:
if _cache is not None:
return _cache.copy()
if HASH_CACHE_FILE.exists():
try:
with open(HASH_CACHE_FILE, 'r', encoding='utf-8') as f:
_cache = json.load(f)
return _cache.copy() if _cache else {}
except:
pass
_cache = {}
return {}
def update_cache(sha, data):
with _cache_lock:
global _cache
if _cache is None:
_cache = get_cache()
_cache[sha] = data
if len(_cache) > MAX_CACHE_ENTRIES:
_cache = dict(list(_cache.items())[-MAX_CACHE_ENTRIES:])
try:
with open(HASH_CACHE_FILE, 'w', encoding='utf-8') as f:
json.dump(_cache, f, indent=2)
except:
pass
# ================================================================
# API Key管理
# ================================================================
def load_api_keys():
if not API_CONFIG.exists():
lines = ["# 天晴安全分析台 - API Key配置", ""]
for kn, name, url in API_KEY_LIST:
lines += [f"# {name} - 获取: {url}", f'{kn} = ""', ""]
API_CONFIG.write_text("\n".join(lines), encoding='utf-8')
keys = {}
for kn, _, _ in API_KEY_LIST:
env_val = os.environ.get(kn)
if env_val:
keys[kn] = env_val
try:
import importlib.util
spec = importlib.util.spec_from_file_location("apikey_config", API_CONFIG)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
for kn, _, _ in API_KEY_LIST:
if kn not in keys:
v = getattr(mod, kn, "")
if v:
keys[kn] = v
except:
pass
return keys
def manage_api_keys():
keys = load_api_keys()
while True:
os.system('cls' if os.name == 'nt' else 'clear')
print("\n" + "=" * 50)
print(" API Key 配置")
print("=" * 50)
print("\n[状态] 当前配置:")
for i, (kn, name, url) in enumerate(API_KEY_LIST, 1):
status = "[已配置]" if keys.get(kn) else "[未配置]"
print(f" [{i}] {status} {name}")
print(f" 获取: {url}")
print("\n [E] 编辑配置文件")
print(" [Q] 返回")
c = input("\n选择: ").strip().lower()
if c == 'q':
return
elif c == 'e':
os.system(f'notepad "{API_CONFIG}"')
keys = load_api_keys()
input("\n按回车继续...")
# ================================================================
# 工具执行
# ================================================================
def run_tool(name, args, timeout=10):
tool = TOOLS_DIR / name
if not tool.exists():
return None
try:
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
si.wShowWindow = 6
r = subprocess.run([str(tool)] + args, capture_output=True, text=True, timeout=timeout,
encoding='utf-8', errors='ignore', startupinfo=si,
creationflags=0x08000000 if os.name == 'nt' else 0)
return r.stdout.strip() if r.returncode == 0 else None
except subprocess.TimeoutExpired:
return None
except:
return None
def check_packer(filepath):
try:
o = run_tool("diec.exe", ["-e", str(filepath)], 10)
return o[:80] if o and "not packed" not in o.lower() else None
except:
return None
def check_signature(filepath):
try:
o = run_tool("sigcheck.exe", ["-n", str(filepath)], 10)
if not o:
return False, "检查失败"
if "Signed" in o:
for line in o.split('\n'):
if "Signer:" in line:
return True, line.split(':', 1)[1].strip()
return True, "有签名"
return False, "无签名"
except:
return False, "检查异常"
def scan_strings(filepath):
findings = []
try:
if filepath.stat().st_size > 20 * 1024 * 1024:
return findings
except:
return findings
try:
content = ""
if filepath.suffix.lower() in {".txt", ".ks", ".tjs", ".py", ".json", ".xml"}:
try:
content = filepath.read_text(encoding='utf-8', errors='ignore').lower()
except:
pass
if not content:
o = run_tool("strings.exe", [str(filepath)], 30)
if o:
content = o.lower()
for pattern, desc in RISK_PATTERNS:
try:
if re.search(pattern, content, re.IGNORECASE):
findings.append(desc)
except:
continue
return list(dict.fromkeys(findings))
except:
return []
# ================================================================
# 病毒分类
# ================================================================
VIRUS_TYPES = [
("trojan", "木马", "窃取信息、远程控制"),
("ransom", "勒索软件", "加密文件勒索赎金"),
("worm", "蠕虫", "自我复制传播"),
("backdoor", "后门", "远程访问"),
("miner", "挖矿病毒", "占用CPU挖矿"),
("stealer", "窃密木马", "窃取账号密码"),
("downloader", "下载器", "下载更多病毒"),
("rootkit", "Rootkit", "深度隐藏"),
]
def classify_virus(name):
name_lower = name.lower()
for kw, vt, bh in VIRUS_TYPES:
if kw in name_lower:
return vt, bh
return "恶意软件", "未知恶意行为"
# ================================================================
# 三引擎联网查询
# ================================================================
class RateLimiter:
def __init__(self, rate, period):
self.rate = rate
self.period = period
self.calls = []
self._lock = threading.Lock()
def wait(self):
with self._lock:
now = time.time()
self.calls = [t for t in self.calls if now - t < self.period]
if len(self.calls) < self.rate:
self.calls.append(now)
return
wait_time = self.period - (now - self.calls[0])
if wait_time > 0:
time.sleep(wait_time)
self.calls.append(time.time())
vt_limiter = RateLimiter(4, 60)
tb_limiter = RateLimiter(5, 60)
def query_vt(sha, api_key):
if not api_key:
return None
try:
vt_limiter.wait()
r = requests.get(
f"https://www.virustotal.com/api/v3/files/{sha}",
headers={"x-apikey": api_key},
timeout=20
)
if r.status_code == 200:
attr = r.json().get("data", {}).get("attributes", {})
stats = attr.get("last_analysis_stats", {})
mal = stats.get("malicious", 0)
total = sum(stats.values())
virus_names = []
for en, ed in attr.get("last_analysis_results", {}).items():
if ed.get("category") == "malicious":
rn = ed.get("result", "")
if rn:
virus_names.append(rn)
return {
"engine": "VirusTotal",
"verdict": "malicious" if mal > 0 else "clean",
"score": mal,
"total": total,
"virus_names": virus_names[:5],
"detail": f"{mal}/{total}",
}
except Exception as e:
pass
return None
def query_threatbook(sha, api_key):
if not api_key:
return None
try:
tb_limiter.wait()
r = requests.get(
"https://api.threatbook.cn/v3/file/report",
params={"apikey": api_key, "resource": sha},
timeout=20
)
if r.status_code == 200:
data = r.json()
summary = data.get("data", {}).get("summary", {})
level = summary.get("threat_level", "")
verdict = "unknown"
if level == "malicious":
verdict = "malicious"
elif level == "suspicious":
verdict = "suspicious"
elif level == "clean":
verdict = "clean"
return {
"engine": "微步在线",
"verdict": verdict,
"detail": f"威胁等级: {level}",
}
except Exception as e:
pass
return None
def query_maldun(sha):
try:
r = requests.get(
f"https://www.maldun.com/analysis/{sha}",
headers={"User-Agent": "TianQing-Security/6.0"},
timeout=20
)
if r.status_code == 200:
text = r.text.lower()
if "malicious" in text:
return {
"engine": "魔盾安全",
"verdict": "malicious",
"detail": "检测到恶意",
}
elif "suspicious" in text:
return {
"engine": "魔盾安全",
"verdict": "suspicious",
"detail": "检测到可疑",
}
else:
return {
"engine": "魔盾安全",
"verdict": "clean",
"detail": "未发现恶意",
}
except Exception as e:
pass
return None
# ================================================================
# 智能解包
# ================================================================
def extract_strings(filepath, output_dir):
try:
with open(filepath, 'rb') as f:
data = f.read(MAX_EXTRACT_SIZE)
strings = []
current = b''
for b in data:
if 32 <= b < 127 or b in (9, 10, 13):
current += bytes([b])
else:
if len(current) >= 4:
strings.append(current.decode('ascii', errors='ignore'))
current = b''
if strings:
(output_dir / "strings.txt").write_text('\n'.join(strings), encoding='utf-8', errors='ignore')
return True
except:
return False
def find_game_root(filepath):
markers = {"game.exe", "data.xp3", "system.dat"}
for p in [filepath] + list(filepath.parents)[:5]:
try:
if markers & {f.name.lower() for f in p.iterdir() if f.is_file()}:
return p
except:
continue
return filepath.parent
def unpack_file(filepath, game_root):
wd = Path(tempfile.gettempdir()) / f"TQ_{uuid.uuid4().hex[:8]}"
try:
wd.mkdir(parents=True, exist_ok=True)
output_dir = wd / "extracted"
output_dir.mkdir()
if extract_strings(filepath, output_dir):
return True, (output_dir, game_root, wd)
except Exception as e:
pass
try:
shutil.rmtree(wd, ignore_errors=True)
except:
pass
return False, None
# ================================================================
# 文件分析
# ================================================================
def analyze_file(filepath, config, pending_list):
issues = []
risk = 0
result = {
"name": filepath.name,
"sha256": get_file_hash(filepath),
"packer": None,
"signature": None,
"risk": 0,
"issues": []
}
# 检查白名单
whitelist = load_whitelist()
if filepath.name in whitelist:
wl_item = whitelist[filepath.name]
if wl_item.get("sha256") == result["sha256"]:
safe_print(f" [白名单] {filepath.name} - {wl_item.get('reason', '已知安全')}")
result["risk"] = 0
result["issues"] = ["[白名单] 已知安全文件"]
return result, 0
# 判断是否为游戏文件
is_game = is_game_file(filepath)
try:
if config.get("check_packer"):
p = check_packer(filepath)
if p:
issues.append(f"加壳: {p}")
result["packer"] = p
if is_game:
risk += 1
else:
risk += 2
if config.get("check_strings"):
strings = scan_strings(filepath)
if strings:
real_risk_strings = [s for s in strings if s not in SAFE_STRINGS]
if real_risk_strings:
issues.append(f"可疑: {','.join(real_risk_strings[:3])}")
risk += min(len(real_risk_strings), 5)
if config.get("check_signature"):
signed, info = check_signature(filepath)
result["signature"] = info
if not signed:
if not is_game:
risk += 1
issues.append("无签名")
elif any(t in info for t in TRUSTED_SIGNERS):
risk = max(0, risk - 3)
except Exception as e:
safe_print(f" [分析] 异常: {e}")
result["risk"] = min(risk, 15)
result["issues"] = issues
# 游戏文件降级
if is_game and result["risk"] > 5:
result["risk"] = min(result["risk"], 5)
if "降低误报" not in str(result["issues"]):
result["issues"].append("(游戏文件,降低误报)")
# 只对非游戏文件且风险>5的入库
if result["risk"] > 5 and filepath.suffix.lower() in {'.exe', '.dll'} and not is_game:
with _pending_lock:
pending_list.append({
"filename": filepath.name,
"filepath": str(filepath),
"risk": result["risk"],
"sha256": result["sha256"],
})
return result, risk
# ================================================================
# 自动入库
# ================================================================
def auto_add_to_baseline(pending_list):
if not pending_list:
return
with _pending_lock:
exe_list = [p for p in pending_list if p['filename'].lower().endswith(('.exe', '.dll'))]
if not exe_list:
return
auto_bl_file = BASELINE_DIR / "auto_safe.json"
bl = []
if auto_bl_file.exists():
try:
with open(auto_bl_file, 'r') as f:
bl = json.load(f)
except:
pass
added = 0
for item in exe_list:
if not any(e.get("sha256") == item['sha256'] for e in bl):
bl.append({
"sha256": item['sha256'],
"name": item['filename'],
"risk": item['risk'],
"time": time.time(),
"source": "auto_detected"
})
added += 1
safe_print(f" [自动入库] {item['filename']} (风险: {item['risk']})")
if added > 0:
with open(auto_bl_file, 'w') as f:
json.dump(bl, f, indent=2)
# ================================================================
# 完整扫描
# ================================================================
def full_scan(filepath, config, pending_list, state, api_keys):
result = None
risk = 0
def _scan():
nonlocal result, risk
try:
r, rk = analyze_file(filepath, config, pending_list)
result = r
risk = rk
except Exception as e:
safe_print(f" [错误] 分析异常: {e}")
result = {"name": filepath.name, "sha256": "", "risk": 0, "issues": ["分析异常"]}
risk = 0
scan_thread = threading.Thread(target=_scan)
scan_thread.daemon = True
scan_thread.start()
scan_thread.join(timeout=SCAN_TIMEOUT)
if scan_thread.is_alive():
safe_print(f" [超时] {filepath.name} 扫描超时,跳过")
return {"name": filepath.name, "sha256": "", "risk": 0, "issues": ["扫描超时"]}, 0
if result is None:
return {"name": filepath.name, "sha256": "", "risk": 0, "issues": ["扫描失败"]}, 0
sha = result.get("sha256", "")
# 三引擎联网查询
if sha and sha != "读取失败" and config.get("online", False):
if filepath.suffix.lower() in {'.exe', '.dll'}:
safe_print(f"\n ╔══════════════════════════════════════════════════╗")
safe_print(f" ║ [联网] 三引擎查询: {filepath.name}")
safe_print(f" ║ [哈希] {sha[:16]}...")
safe_print(f" ╚══════════════════════════════════════════════════╝")
cache = get_cache()
cached = cache.get(sha)
if cached and time.time() - cached.get('time', 0) < CACHE_EXPIRE_DAYS * 86400:
safe_print(f" [缓存] 命中")
if cached.get("virus"):
result["virus_details"] = cached["virus"]
if cached.get("engines"):
result["engine_results"] = cached["engines"]
else:
engine_results = []
virus_list = []
vt_key = api_keys.get("VT_API_KEY")
if vt_key:
safe_print(f" [1/3] VirusTotal 查询...")
vt_result = query_vt(sha, vt_key)
if vt_result:
engine_results.append(vt_result)
safe_print(f" [VT] 结果: {vt_result['detail']}")
for vn in vt_result.get('virus_names', [])[:3]:
vt_type, vt_bh = classify_virus(vn)
virus_list.append({"name": vn, "type": vt_type, "behavior": vt_bh, "engine": "VirusTotal"})
safe_print(f" • {vn}")
tb_key = api_keys.get("THREATBOOK_API_KEY")
if tb_key:
safe_print(f" [2/3] 微步在线 查询...")
tb_result = query_threatbook(sha, tb_key)
if tb_result:
engine_results.append(tb_result)
safe_print(f" [微步] 结果: {tb_result['detail']}")
safe_print(f" [3/3] 魔盾安全 查询...")
md_result = query_maldun(sha)
if md_result:
engine_results.append(md_result)
safe_print(f" [魔盾] 结果: {md_result['detail']}")
if engine_results:
result["engine_results"] = engine_results
max_engine_bonus = 0
for er in engine_results:
if er.get('verdict') == 'malicious':
max_engine_bonus = max(max_engine_bonus, 8)
elif er.get('verdict') == 'suspicious':
max_engine_bonus = max(max_engine_bonus, 3)
if max_engine_bonus > 0:
risk = min(risk + max_engine_bonus, 15)
result["risk"] = risk
if virus_list:
result["virus_details"] = virus_list
update_cache(sha, {
"virus": virus_list,
"engines": engine_results,
"time": time.time()
})
safe_print(f" [汇总] {len(engine_results)} 个引擎检出")
# 深度扫描解包
if config.get("unpack") and filepath.suffix.lower() in {".xp3", ".exe", ".dll"}:
try:
if filepath.stat().st_size > 100 * 1024 * 1024:
pass
else:
safe_print(f"\n ╔══════════════════════════════════════════════════╗")
safe_print(f" ║ [解包] 分析: {filepath.name}")
safe_print(f" ╚══════════════════════════════════════════════════╝")
game_root = find_game_root(filepath)
success, unpack_result = unpack_file(filepath, game_root)
if success and unpack_result:
od, gr, wd = unpack_result
try:
script_files = [f for f in od.rglob("*") if f.suffix.lower() in {".ks", ".tjs", ".txt"}]
if script_files:
safe_print(f" [解包] 提取到 {len(script_files)} 个脚本")
except:
pass
try:
shutil.rmtree(wd, ignore_errors=True)
except:
pass
except Exception as e:
pass
gc.collect()
return result, result.get("risk", 0)
# ================================================================
# 扫描流程
# ================================================================
def collect_targets(folder):
targets = []
skipped = 0
for fp in folder.rglob("*"):
if not fp.is_file():
continue
if fp.suffix.lower() in SKIP_EXTS:
skipped += 1
elif fp.suffix.lower() in TARGET_EXTS:
targets.append(fp)
return targets, skipped
def scan_folder(folder, config, state, api_keys):
safe_print(f"\n{'='*60}")
safe_print(f"[目录] {folder}")
safe_print(f"{'='*60}")
try:
targets, skipped = collect_targets(folder)
except Exception as e:
safe_print(f"[错误] 遍历目录失败: {e}")
return
if skipped:
safe_print(f"[跳过] {skipped} 个多媒体文件")
if not targets:
safe_print("[完成] 无文件")
return
safe_print(f"\n[统计] 发现 {len(targets)} 个待扫描文件")
safe_print(f"\n[配置] 扫描模式: {config['name']}")
safe_print(f"[配置] 联网查询: {'启用' if config.get('online') else '禁用'}")
if config.get('online'):
vt_key = api_keys.get("VT_API_KEY")
tb_key = api_keys.get("THREATBOOK_API_KEY")
safe_print(f"[引擎] VirusTotal: {'已配置' if vt_key else '未配置'}")
safe_print(f"[引擎] 微步在线: {'已配置' if tb_key else '未配置'}")
safe_print(f"[引擎] 魔盾安全: 无需配置")
max_workers = min(3, len(targets)) if config.get("online") else min(4, len(targets))
safe_print(f"[并发] {max_workers} 线程")
safe_print(f"{'='*60}\n")
pb_count = 0
pb_total = len(targets)
all_results = []
pending_list = []
start_time = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as ex:
futures = {ex.submit(full_scan, fp, config, pending_list, state, api_keys): fp for fp in targets}
for fu in concurrent.futures.as_completed(futures):
pb_count += 1
try:
result, risk = fu.result(timeout=SCAN_TIMEOUT)
all_results.append(result)
except concurrent.futures.TimeoutError:
safe_print(f"\n[超时] 文件扫描超时")
all_results.append({"name": "超时", "risk": 0})
except Exception as e:
safe_print(f"\n[错误] 扫描异常: {e}")
all_results.append({"name": "错误", "risk": 0})
pct = pb_count * 100 // pb_total
bar = "█" * (pct // 2) + "░" * (50 - pct // 2)
safe_print(f"\r[进度] [{bar}] {pb_count}/{pb_total} ({pct}%)", end="")
safe_print(f"\n\n[耗时] {int(time.time() - start_time)} 秒")
auto_add_to_baseline(pending_list)
# 生成详细报告
generate_detailed_report(folder, all_results, start_time)
try:
import winsound
winsound.Beep(1000, 500)
except:
pass
def scan_single(filepath, config, state, api_keys):
safe_print(f"\n{'='*60}")
safe_print(f"[文件] {filepath.name}")
safe_print(f"{'='*60}")
safe_print(f"[配置] 扫描模式: {config['name']}")
safe_print(f"[配置] 联网查询: {'启用' if config.get('online') else '禁用'}")
pending_list = []
result, risk = full_scan(filepath, config, pending_list, state, api_keys)
safe_print(f"\n{'='*60}")
safe_print("[结果]")
safe_print(f"{'='*60}")
if result.get("issues"):
safe_print("\n[风险] 发现问题:")
for i in result["issues"][:5]:
safe_print(f" • {i}")
else:
safe_print("\n[安全] 未发现风险")
safe_print(f"\n[评分] {risk}/15")
if result.get("engine_results"):
safe_print("\n[引擎] 检测结果:")
for er in result["engine_results"]:
icon = "🔴" if er.get('verdict') == 'malicious' else "🟡" if er.get('verdict') == 'suspicious' else "🟢"
safe_print(f" {icon} {er.get('engine')}: {er.get('detail', '')}")
if result.get("virus_details"):
safe_print("\n[病毒] 详情:")
for v in result["virus_details"][:3]:
safe_print(f" • {v.get('name')}")
safe_print(f" 类型: {v.get('type')}")
safe_print(f" 行为: {v.get('behavior')}")
auto_add_to_baseline(pending_list)
if risk <= 5 and filepath.suffix.lower() in {'.exe', '.dll'}:
choice = input("\n此文件安全,是否加入白名单?(y/N): ").strip().lower()
if choice == 'y':
add_to_whitelist(filepath.name, get_file_hash(filepath), "用户确认安全")
safe_print("[是] 已加入白名单")
input("\n按回车继续...")
# ================================================================
# 详细报告生成(高风险逐条说明)
# ================================================================
def generate_detailed_report(folder, all_results, start_time):
"""生成详细报告 - 高风险文件逐个说明原因"""
# 风险等级定义
RISK_LEVELS = {
0: {"name": "安全", "icon": "✅", "color": "green", "desc": "未发现任何问题"},
1: {"name": "低风险", "icon": "⚠️", "color": "orange", "desc": "有轻微可疑特征,但通常是正常的"},
2: {"name": "中风险", "icon": "🔴", "color": "red", "desc": "有多个可疑特征,建议谨慎处理"},
3: {"name": "高风险", "icon": "🔴🔴", "color": "darkred", "desc": "确认或高度疑似恶意软件,建议立即处理"}
}
def translate_risk_reason(risk_score, issues, packer, signature, has_virus, engine_results):
reasons = []
if risk_score >= 12:
reasons.append("🔴 非常危险!多个杀毒引擎确认为病毒")
elif risk_score >= 8:
reasons.append("🟠 比较危险!有明确的恶意行为特征")
elif risk_score >= 5:
reasons.append("🟡 有点可疑,需要留意")
for issue in issues:
if "加壳" in issue:
packer_name = issue.replace("加壳: ", "")
reasons.append(f"📦 文件被加壳压缩({packer_name})—— 正常软件很少这样做,病毒常用此方式隐藏自己")
elif "可疑" in issue:
suspicious_items = issue.replace("可疑: ", "").split(",")
reasons.append(f"🔍 包含可疑代码特征:{', '.join(suspicious_items[:3])}")
elif "无签名" in issue:
reasons.append("📄 文件没有数字签名 —— 无法验证发布者身份,来源不明")
elif "游戏文件" in issue:
reasons.append("🎮 这是游戏相关文件,已降低误报等级")
if packer and packer != "无" and not any("加壳" in i for i in issues):
reasons.append(f"📦 文件被加壳压缩({packer})—— 正常软件很少这样做")
if signature and "无签名" in signature and not any("无签名" in i for i in issues):
reasons.append("📄 文件没有数字签名 —— 无法验证发布者身份")
if has_virus:
reasons.append("🦠 多个杀毒引擎检测到病毒/木马")
# 引擎检测结果
if engine_results:
for er in engine_results:
verdict = er.get('verdict', 'unknown')
if verdict == 'malicious':
reasons.append(f"🌐 {er.get('engine')} 在线引擎检测:确认为恶意软件")
elif verdict == 'suspicious':
reasons.append(f"🌐 {er.get('engine')} 在线引擎检测:可疑")
elif verdict == 'clean':
reasons.append(f"🌐 {er.get('engine')} 在线引擎检测:安全")
if not reasons:
reasons.append(f"⚠️ 风险评分 {risk_score}/15,但具体原因未知")
return reasons
ts = time.strftime("%Y%m%d_%H%M%S")
report_path = folder / f"[安全报告]{folder.name}_{ts}.html"
elapsed = time.time() - start_time
high_risk = [r for r in all_results if r.get("risk", 0) > 5]
low_risk = [r for r in all_results if 1 <= r.get("risk", 0) <= 5]
safe = [r for r in all_results if r.get("risk", 0) == 0]
high_risk.sort(key=lambda x: x.get("risk", 0), reverse=True)
total_files = len(all_results)
high_count = len(high_risk)
low_count = len(low_risk)
safe_count = len(safe)
all_risks = [r.get("risk", 0) for r in all_results]
max_risk = max(all_risks) if all_risks else 0
if max_risk >= 12:
overall_level = 3
elif max_risk >= 8:
overall_level = 2
elif max_risk >= 1:
overall_level = 1
else:
overall_level = 0
overall_icon = RISK_LEVELS[overall_level]["icon"]
overall_name = RISK_LEVELS[overall_level]["name"]
overall_color = RISK_LEVELS[overall_level]["color"]
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>天晴安全分析报告 - {folder.name}</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Microsoft YaHei', sans-serif;
background: #f5f5f5;
padding: 20px;
line-height: 1.6;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
}}
.card {{
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}}
.header {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}}
.header h1 {{
font-size: 28px;
margin-bottom: 10px;
}}
.summary-stats {{
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 15px;
}}
.stat {{
background: rgba(255,255,255,0.15);
border-radius: 8px;
padding: 12px 20px;
text-align: center;
flex: 1;
min-width: 100px;
}}
.stat-number {{
font-size: 32px;
font-weight: bold;
}}
.stat-label {{
font-size: 14px;
opacity: 0.9;
}}
.risk-high {{ color: #dc3545; }}
.risk-medium {{ color: #fd7e14; }}
.risk-low {{ color: #28a745; }}
.high-risk-item {{
border-left: 4px solid #dc3545;
background: #fff5f5;
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
}}
.high-risk-header {{
background: #dc3545;
color: white;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}}
.high-risk-title {{
font-weight: bold;
font-size: 16px;
}}
.high-risk-score {{
background: rgba(255,255,255,0.2);
padding: 4px 12px;
border-radius: 20px;
font-size: 14px;
}}
.high-risk-body {{
padding: 20px;
}}
.reason-list {{
list-style: none;
}}
.reason-list li {{
padding: 10px 0;
border-bottom: 1px solid #eee;
display: flex;
align-items: flex-start;
gap: 12px;
}}
.reason-icon {{
font-size: 20px;
min-width: 30px;
}}
.reason-text {{
flex: 1;
}}
.file-info {{
background: #f0f0f0;
padding: 12px;
border-radius: 6px;
font-family: monospace;
font-size: 12px;
margin-top: 15px;
word-break: break-all;
}}
.low-risk-summary {{
background: #fff8e7;
border-left: 4px solid #ffc107;
}}
details {{
margin-top: 10px;
}}
summary {{
cursor: pointer;
padding: 10px;
background: #f8f9fa;
border-radius: 6px;
font-weight: bold;
}}
summary:hover {{
background: #e9ecef;
}}
.low-risk-table {{
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}}
.low-risk-table th, .low-risk-table td {{
padding: 8px;
text-align: left;
border-bottom: 1px solid #eee;
}}
.low-risk-table th {{
background: #f8f9fa;
}}
.info-box {{
background: #e8f4f8;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}}
</style>
</head>
<body>
<div class="container">
<div class="card header">
<h1>🛡️ 天晴安全分析报告</h1>
<p>扫描目录: <strong>{folder.name}</strong></p>
<p>扫描时间: {time.strftime('%Y-%m-%d %H:%M:%S')}</p>
<div class="summary-stats">
<div class="stat">
<div class="stat-number">{total_files}</div>
<div class="stat-label">扫描文件</div>
</div>
<div class="stat">
<div class="stat-number risk-high">{high_count}</div>
<div class="stat-label">高风险文件</div>
</div>
<div class="stat">
<div class="stat-number risk-medium">{low_count}</div>
<div class="stat-label">低风险文件</div>
</div>
<div class="stat">
<div class="stat-number risk-low">{safe_count}</div>
<div class="stat-label">安全文件</div>
</div>
<div class="stat">
<div class="stat-number">{int(elapsed)}秒</div>
<div class="stat-label">扫描耗时</div>
</div>
</div>
<div style="margin-top: 15px; padding: 10px; background: rgba(255,255,255,0.15); border-radius: 8px;">
<strong>整体风险等级:</strong> {overall_icon} {overall_name}
</div>
</div>"""
# 高风险文件详情
if high_risk:
html += f"""
<div class="card">
<h2 style="color: #dc3545; margin-bottom: 20px;">⚠️ 高风险文件 ({high_count} 个)</h2>
<p style="margin-bottom: 20px; color: #666;">以下文件被判定为高风险,建议仔细查看原因:</p>"""
for idx, r in enumerate(high_risk, 1):
risk_score = r.get('risk', 0)
issues = r.get('issues', [])
packer = r.get('packer', '')
signature = r.get('signature', '')
has_virus = bool(r.get('virus_details'))
engine_results = r.get('engine_results', [])
reasons = translate_risk_reason(risk_score, issues, packer, signature, has_virus, engine_results)
html += f"""
<div class="high-risk-item">
<div class="high-risk-header">
<span class="high-risk-title">📁 {idx}. {r.get('name', '未知')}</span>
<span class="high-risk-score">风险评分: {risk_score}/15</span>
</div>
<div class="high-risk-body">
<ul class="reason-list">"""
for reason in reasons:
html += f"""
<li>
<span class="reason-icon">🔍</span>
<span class="reason-text">{reason}</span>
</li>"""
if r.get('virus_details'):
html += f"""
<li>
<span class="reason-icon">🦠</span>
<span class="reason-text">杀毒软件检测结果:</span>
</li>"""
for vd in r.get('virus_details', [])[:3]:
html += f"""
<li>
<span class="reason-icon"> </span>
<span class="reason-text">• {vd.get('name', '未知病毒')} - {vd.get('type', '未知类型')}</span>
</li>"""
html += f"""
</ul>
<div class="file-info">
<strong>文件信息:</strong><br>
文件名: {r.get('name', '未知')}<br>
哈希值: {r.get('sha256', '未知')[:32]}...<br>
签名状态: {r.get('signature', '未知')}
</div>
</div>
</div>"""
html += """
<div class="info-box">
<strong>💡 什么是高风险?</strong><br>
高风险文件是指具有多个恶意软件特征的文件。以上列出的每个文件都详细说明了具体原因。
如果你的游戏汉化补丁被标记为高风险,通常是因为加壳或无签名,这是汉化补丁的常见现象。
建议你根据具体原因判断是否安全。
</div>"""
# 低风险文件
if low_risk:
html += f"""
<div class="card low-risk-summary">
<h2 style="color: #fd7e14; margin-bottom: 10px;">📋 低风险文件 ({low_count} 个)</h2>
<p style="color: #666; margin-bottom: 10px;">以下文件有轻微可疑特征,但通常是正常的。点击展开查看详情:</p>
<details>
<summary>🔽 点击展开查看 {low_count} 个低风险文件</summary>
<table class="low-risk-table">
<thead>
<tr><th>#</th><th>文件名</th><th>风险评分</th><th>主要问题</th></tr>
</thead>
<tbody>"""
for idx, r in enumerate(low_risk, 1):
issues_str = ', '.join(r.get('issues', [])[:2]) if r.get('issues') else '轻微可疑'
html += f"""
<tr>
<td>{idx}</td>
<td>{r.get('name', '未知')}</td>
<td>{r.get('risk', 0)}</td>
<td>{issues_str}</td>
</tr>"""
html += """
</tbody>
</table>
</details>
</div>"""
# 安全文件
if safe_count > 0:
html += f"""
<div class="card">
<h2 style="color: #28a745;">✅ 安全文件 ({safe_count} 个)</h2>
<p>以下文件未发现任何风险:</p>
<details>
<summary>🔽 点击查看安全文件列表</summary>
<ul style="margin-top: 10px; columns: 3; list-style: none;">"""
for r in safe[:50]:
html += f"<li>• {r.get('name', '未知')}</li>"
if len(safe) > 50:
html += f"<li>... 共 {len(safe)} 个文件</li>"
html += """
</ul>
</details>
</div>"""
html += """
</div>
</body>
</html>"""
report_path.write_text(html, encoding='utf-8')
webbrowser.open(str(report_path))
safe_print(f"\n[报告] {report_path}")
return report_path
# ================================================================
# 用户界面
# ================================================================
def select_mode():
print("\n" + "=" * 60)
print(" 扫描配置向导")
print("=" * 60)
print("\n扫描深度说明:")
print(" [1] 轻度 - 查壳+签名 (快速)")
print(" [2] 中度 - 轻度+字符串+YARA扫描 (标准)")
print(" [3] 深度 - 中度+解包分析 (完整,较慢)")
print("\n联网查询说明:")
print(" [Y] 启用 - 查询 VirusTotal + 微步在线 + 魔盾安全")
print(" [N] 禁用 - 仅本地扫描")
print("=" * 60)
while True:
c = input("\n选择深度 (1/2/3,默认3): ").strip() or "3"
if c in SCAN_MODES:
config = SCAN_MODES[c].copy()
print(f"\n[已选] 扫描深度: {config['name']}")
break
print("[错误] 无效选项,请输入 1、2 或 3")
while True:
online = input("\n启用联网查询?(y/N): ").strip().lower()
if online in ('y', 'n', ''):
config['online'] = (online == 'y')
print(f"[已选] 联网查询: {'启用' if config['online'] else '禁用'}")
break
print("[错误] 请输入 y 或 n")
print("\n" + "=" * 60)
return config
def show_menu():
print("\n" + "=" * 50)
print(" 天晴安全分析台 v6.2 - 完整版")
print("=" * 50)
print(" [1] 开始扫描")
print(" [2] API Key 配置")
print(" [3] 补丁库/病毒库管理")
print(" [4] 右键菜单安装")
print(" [5] 系统自检")
print(" [6] 免责声明")
print(" [q] 退出")
print("=" * 50)
def install_context_menu():
if not is_admin():
print("\n[提示] 正在申请管理员权限...")
run_as_admin()
return
try:
import winreg
menu_path = r"*\shell\天晴安全分析"
with winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, menu_path) as key:
winreg.SetValue(key, "", winreg.REG_SZ, "🔍 天晴安全分析")
cmd_path = r"*\shell\天晴安全分析\command"
cmd = f'"{sys.executable}" "{Path(__file__).absolute()}" "%1"'
with winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, cmd_path) as key:
winreg.SetValue(key, "", winreg.REG_SZ, cmd)
print("\n[成功] 右键菜单已安装")
except Exception as e:
print(f"\n[失败] 安装失败: {e}")
def uninstall_context_menu():
if not is_admin():
print("\n[提示] 正在申请管理员权限...")
run_as_admin()
return
try:
import winreg
menu_path = r"*\shell\天晴安全分析"
winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, menu_path + r"\command")
winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, menu_path)
print("\n[成功] 右键菜单已卸载")
except Exception as e:
print(f"\n[失败] 卸载失败: {e}")
def main():
if len(sys.argv) > 1:
if sys.argv[1] == "--install-menu":
install_context_menu()
input("\n按回车退出...")
return
elif sys.argv[1] == "--uninstall-menu":
uninstall_context_menu()
input("\n按回车退出...")
return
else:
target = Path(sys.argv[1])
if target.exists():
self_check()
if check_disclaimer():
state = type('State', (), {})()
state.is_enabled = lambda x: True
api_keys = load_api_keys()
config = select_mode()
if target.is_dir():
scan_folder(target, config, state, api_keys)
else:
scan_single(target, config, state, api_keys)
input("\n按回车退出...")
return
self_check()
if not check_disclaimer():
print("[退出] 未同意免责声明")
return
state = type('State', (), {})()
state.is_enabled = lambda x: True
api_keys = load_api_keys()
while True:
show_menu()
choice = input("选择: ").strip().lower()
if choice == '1':
config = select_mode()
print("\n[提示] 拖入文件或文件夹,输入 q 退出")
while True:
path = input("\n路径: ").strip().strip('"')
if path.lower() == 'q':
break
target = Path(path)
if not target.exists():
print("[错误] 路径不存在")
continue
if target.is_dir():
scan_folder(target, config, state, api_keys)
else:
scan_single(target, config, state, api_keys)
elif choice == '2':
manage_api_keys()
api_keys = load_api_keys()
print("\n[完成] API Key 配置已更新")
input("按回车继续...")
elif choice == '3':
pc = len(list(PATCH_LIB_DIR.iterdir())) if PATCH_LIB_DIR.exists() else 0
vc = len(list(VIRUS_LIB_DIR.glob("*.txt"))) if VIRUS_LIB_DIR.exists() else 0
print(f"\n[统计] 补丁库: {pc} 个文件")
print(f"[统计] 病毒库: {vc} 个记录")
if input("\n清空补丁库?(y/N): ").strip().lower() == 'y':
shutil.rmtree(PATCH_LIB_DIR, ignore_errors=True)
PATCH_LIB_DIR.mkdir()
print("[完成] 补丁库已清空")
input("\n按回车继续...")
elif choice == '4':
print("\n右键菜单安装")
print(" [1] 安装右键菜单")
print(" [2] 卸载右键菜单")
sub_choice = input("选择: ").strip()
if sub_choice == '1':
install_context_menu()
elif sub_choice == '2':
uninstall_context_menu()
else:
print("[错误] 无效选项")
input("\n按回车继续...")
elif choice == '5':
self_check()
input("\n按回车继续...")
elif choice == '6':
print(DISCLAIMER)
input("\n按回车继续...")
elif choice == 'q':
print("\n[再见] 感谢使用天晴安全分析台")
break
else:
print("[错误] 无效选项")
if __name__ == "__main__":
try:
import requests
except ImportError:
print("[错误] 缺少 requests 库")
print("[解决] 运行: pip install requests")
input("\n按回车退出...")
sys.exit(1)
main()
【一起完善】
如果你改进了代码,直接在本帖下方回复贴出来就行。
我会不定期回来看(可能几个月一次),
有好的改进我会整合到下一版。
【免责声明】
本工具仅供安全研究、学习交流使用。
使用后果由使用者自行承担。
天晴安全分析台 - 让你的游戏更安全
代码(可以直接复制到空的TXT文件然后改成py运行)


