1、原理
通过遍历文件夹的所有文件,算得各文件的MD5,与病毒库文件中的md5进行比对,如果比对成功,则说明为病毒,进行隔离,将文件移动到隔离目录。还可开启watchdog进行文件夹监控,使得增、改的文件,进行再次比对,查看是否为病毒文件。
不过跟真正的杀毒软件很多差别,这个仅能做简单的判断。真正的杀毒软件会包含(进程行为监控、网络数据监控、文件内容监控、注册表监控、启动命令监控等等)根据md5特征比对还可能会有hash冲撞的情况,不同的文件可能hash会相同,这样也会被识别为病毒。
所以本项目仅能当做自制小玩具使用,真实情况下,还请大家使用市面上成熟的杀毒软件
2、运行截图
3、代码
运行时候请在代码的同级目录下放入virus_signatures.txt文件,里面放入病毒的MD5值
这里有一个网站能够找到很多的病毒md5值(VirusShare.com)
import os
import hashlib
import shutil
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
from tkinter.ttk import Progressbar
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import threading
import queue
import time
import random
import string
# 加载病毒签名数据库
def load_virus_signatures(file_path):
try:
with open(file_path, 'r') as file:
signatures = file.read().splitlines()
return signatures
except Exception as e:
messagebox.showerror("错误", f"加载病毒签名时出错: {e}")
return []
# 计算文件的MD5哈希值
def calculate_md5(file_path):
hash_md5 = hashlib.md5()
try:
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
except IOError:
return None
# 生成带时间戳和随机字符的新文件名
def generate_quarantine_filename(file_path):
timestamp = str(int(time.time()))
random_str = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
file_extension = os.path.splitext(file_path)[1]
new_filename = f"{timestamp}_{random_str}{file_extension}"
return new_filename
# 隔离感染文件
def quarantine_file(file_path, quarantine_dir):
if not os.path.exists(quarantine_dir):
os.makedirs(quarantine_dir)
new_filename = generate_quarantine_filename(file_path)
quarantine_path = os.path.join(quarantine_dir, new_filename)
try:
shutil.move(file_path, quarantine_path)
return f"已隔离文件: {file_path} -> {quarantine_path}\n"
except IOError as e:
return f"无法隔离文件 {file_path}: {e}\n"
# 扫描文件任务函数(多线程使用)
def scan_file_task(file_queue, signatures, quarantine_dir, output_queue, pause_event):
while not file_queue.empty():
pause_event.wait() # 等待直到不处于暂停状态
file_path = file_queue.get()
file_hash = calculate_md5(file_path)
if file_hash in signatures:
output_queue.put(f"警告: 发现感染文件 {file_path}\n")
quarantine_result = quarantine_file(file_path, quarantine_dir)
output_queue.put(quarantine_result)
else:
output_queue.put(f"扫描文件: {file_path} - 未发现威胁\n")
output_queue.put("progress") # 发送进度更新信号
file_queue.task_done()
# 扫描指定目录中的文件(多线程版)
def scan_directory_multithreaded(directory, signatures, quarantine_dir, output_queue, pause_event):
file_queue = queue.Queue()
for root, _, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
file_queue.put(file_path)
total_files = file_queue.qsize()
output_queue.put(("total", total_files)) # 将总文件数传递给进度条更新
# 启动多个线程进行扫描
num_threads = 10 # 调整为10个线程,避免过多线程导致问题
for _ in range(num_threads):
thread = threading.Thread(target=scan_file_task, args=(file_queue, signatures, quarantine_dir, output_queue, pause_event))
thread.daemon = True
thread.start()
file_queue.join()
output_queue.put("done")
# 实时监控文件系统事件
class FileMonitorHandler(FileSystemEventHandler):
def __init__(self, signatures, quarantine_dir, output_queue):
self.signatures = signatures
self.quarantine_dir = quarantine_dir
self.output_queue = output_queue
def on_modified(self, event):
if not event.is_directory:
self.scan_file(event.src_path)
def on_created(self, event):
if not event.is_directory:
self.scan_file(event.src_path)
def scan_file(self, file_path):
try:
file_hash = calculate_md5(file_path)
if file_hash in self.signatures:
self.output_queue.put(f"警告: 发现感染文件 {file_path}\n")
quarantine_result = quarantine_file(file_path, self.quarantine_dir)
self.output_queue.put(quarantine_result)
else:
self.output_queue.put(f"扫描文件: {file_path} - 未发现威胁\n")
except Exception as e:
self.output_queue.put(f"扫描文件 {file_path} 时出错: {e}\n")
# GUI应用程序
class AntivirusApp:
def __init__(self, root):
self.root = root
self.root.title("桌面杀毒软件")
self.root.geometry("600x450")
self.signatures = load_virus_signatures('virus_signatures.txt')
# GUI元素
self.label = tk.Label(root, text="请选择要扫描和监控的目录:")
self.label.pack(pady=10)
self.path_entry = tk.Entry(root, width=50)
self.path_entry.pack(pady=5)
# 按钮框架
button_frame = tk.Frame(root)
button_frame.pack(pady=10)
self.browse_button = tk.Button(button_frame, text="浏览", command=self.browse_directory)
self.browse_button.pack(side=tk.LEFT, padx=5)
self.scan_button = tk.Button(button_frame, text="扫描", command=self.start_scan)
self.scan_button.pack(side=tk.LEFT, padx=5)
self.pause_button = tk.Button(button_frame, text="暂停扫描", command=self.pause_scan)
self.pause_button.pack(side=tk.LEFT, padx=5)
self.monitor_button = tk.Button(button_frame, text="启动监控", command=self.start_monitoring)
self.monitor_button.pack(side=tk.LEFT, padx=5)
self.stop_monitor_button = tk.Button(button_frame, text="停止监控", command=self.stop_monitoring)
self.stop_monitor_button.pack(side=tk.LEFT, padx=5)
self.stop_monitor_button.config(state=tk.DISABLED) # 初始禁用停止按钮
self.log_text = scrolledtext.ScrolledText(root, width=70, height=15)
self.log_text.pack(pady=10)
self.progress_var = tk.IntVar()
self.progress_bar = Progressbar(root, orient="horizontal", length=500, mode="determinate",
variable=self.progress_var)
self.progress_bar.pack(pady=10)
self.quarantine_dir = "C:/virus_quarantine/" # 隔离目录
# 日志和进度队列
self.output_queue = queue.Queue()
# 更新UI的定时器
self.update_ui()
# 监控对象初始化
self.observer = None
# 扫描暂停控制
self.pause_event = threading.Event()
self.pause_event.set() # 初始为继续状态
def browse_directory(self):
directory = filedialog.askdirectory()
if directory:
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, directory)
def start_scan(self):
directory = self.path_entry.get()
if not directory:
messagebox.showwarning("警告", "请选择一个目录!")
return
self.log_text.insert(tk.END, "开始扫描...\n")
self.progress_var.set(0)
# 使用线程来执行扫描任务
scan_thread = threading.Thread(target=self.scan_directory_thread, args=(directory,))
scan_thread.start()
def scan_directory_thread(self, directory):
try:
scan_directory_multithreaded(directory, self.signatures, self.quarantine_dir, self.output_queue, self.pause_event)
except Exception as e:
self.output_queue.put(f"扫描时出错: {e}\n")
def pause_scan(self):
if self.pause_event.is_set():
self.pause_event.clear() # 暂停扫描
self.pause_button.config(text="继续扫描")
self.log_text.insert(tk.END, "扫描已暂停。\n")
else:
self.pause_event.set() # 继续扫描
self.pause_button.config(text="暂停扫描")
self.log_text.insert(tk.END, "扫描已继续。\n")
def start_monitoring(self):
directory = self.path_entry.get()
if not directory:
messagebox.showwarning("警告", "请选择一个目录!")
return
self.log_text.insert(tk.END, f"开始监控目录: {directory}\n")
event_handler = FileMonitorHandler(self.signatures, self.quarantine_dir, self.output_queue)
self.observer = Observer()
self.observer.schedule(event_handler, path=directory, recursive=True)
self.observer.start()
self.monitor_button.config(state=tk.DISABLED) # 禁用启动按钮
self.stop_monitor_button.config(state=tk.NORMAL) # 启用停止按钮
self.log_text.insert(tk.END, "实时监控已启动...\n")
def stop_monitoring(self):
if self.observer:
self.observer.stop()
self.observer.join()
self.log_text.insert(tk.END, "监控已停止。\n")
self.monitor_button.config(state=tk.NORMAL) # 重新启用启动按钮
self.stop_monitor_button.config(state=tk.DISABLED) # 禁用停止按钮
def update_ui(self):
# 从队列中读取日志信息并更新日志窗口
log_lines = []
while not self.output_queue.empty():
output = self.output_queue.get()
if output == "done":
log_lines.append("扫描完成。\n")
elif output == "progress":
self.progress_var.set(self.progress_var.get() + 1)
elif isinstance(output, tuple) and output[0] == "total":
self.progress_bar.config(maximum=output[1])
else:
log_lines.append(output)
# 更新UI
if log_lines:
self.log_text.insert(tk.END, ''.join(log_lines))
self.log_text.see(tk.END)
# 每100毫秒更新一次UI
self.root.after(100, self.update_ui)
if __name__ == "__main__":
root = tk.Tk()
app = AntivirusApp(root)
root.mainloop()