写个简单杀毒软件:哈希引擎、隔离区与实时监控实现
2026/9/2 14:53:27 网站建设 项目流程

简介:编译打包的杀毒与木马分析源代码,面向安全初学者和开发者,用于演示木马扫描、病毒查杀与恶意代码检测的基础架构。压缩包共七十个文件,以C++头文件(h)和源文件(cpp)为主,同时包含编译中间文件(obj)与调试信息(pdb),附可执行程序(exe)及图标位图等界面资源,整个压缩包体积约一点一兆字节。目前已有千余人学习下载。代码覆盖恶意代码签名库、扫描引擎、文件隔离与清除、实时保护、日志与报告、更新机制以及简单用户界面等模块;从工程中可以看到对话框、进程管理、注册表跳转等辅助分析功能,完整展现了基于MFC的安全工具开发方式,可视为一个典型的杀毒软件原型。对想入门反恶意软件研究和Windows安全编程的读者而言,这是一份可直接翻阅和调试的参考样例。 你电脑上大概率已经装着一款免费的杀毒软件,但如果你会去搜“杀毒软件源代码”这个词,说明你和我一样,好奇的不是“用哪款杀毒软件”,而是“杀毒软件到底是怎么把一个病毒文件揪出来的”。我最初写这个简单杀毒软件源代码项目,目的很单纯:想用最少的代码,复现一遍杀毒引擎最核心的查杀流程。最终做出来的东西只有几百行,功能也远不能和那些动不动就进入各种“杀毒软件十大排名”榜单的商业产品相比,但它把一件事做透了——从一个文件进去,到判定它是否为恶意文件并给出处置建议,整条链路清清楚楚。这篇文章就把这条链路拆开来讲,适合想入门安全方向、或者希望给U盘/下载目录加一道手动扫描工具的同学。

1. 杀毒引擎的本质:特征码、哈希、启发式这三条路到底在走什么

1.1 杀毒软件不是玄学,本质是一套匹配系统

很多人一提到杀毒软件,脑子里浮现的是全盘扫描时那个每秒跳动的进度条,感觉背后算法很神秘。实际上,把“杀毒”这件事拆到最底层,就是一个“输入文件,输出判决”的匹配系统。就像小区门口保安认人:要么看脸熟不熟(特征码),要么刷身份证比对数据库(哈希),要么看你行为是否鬼鬼祟祟(启发式/行为分析)。商业杀软之所以复杂,是因为同时用了这三套机制,并且各自配了一套巨大的数据支撑。

我在最初设计这个简单杀毒软件源代码项目时,反复提醒自己一件事:别想着一次就把三套机制全实现。先选一条最清晰的路走通,再谈优化。因为这三套机制对应的是完全不同的工程复杂度,放在一起写,新手很容易被绕晕。

1.2 三大检测流派对比

检测方式核心原理优点缺点
特征码扫描从恶意样本中提取一段有标识性的字节序列,在目标文件中查找该序列速度快、误报低、易于理解对新变种和加壳样本几乎无效
哈希比对对完整文件计算MD5/SHA-1/SHA-256等摘要,与恶意样本摘要做精确比对实现最简单、结果可解释、零误报只能识别完全一致的已知文件,文件一改就失效
启发式/行为分析通过静态分析代码结构或动态运行观察行为,判断“像不像”恶意软件能发现变种和未知威胁误报率较高、实现复杂度高、需要大量规则或沙箱环境

拿“免费杀毒软件十大排名”里那些产品来说,它们能排在前列,不是因为用了某种独门“黑科技”,而是把特征库做得足够大、云查杀响应足够快,再加上行为分析兜底。但它们的引擎内核,依然没有脱离这张表。

1.3 为什么简单杀毒软件源代码适合从哈希引擎入手

哈希引擎是三者中唯一一个“数学上精确”的方案。一个文件算出来的SHA-256,和恶意样本库中的值完全相等,才能判定恶意,不存在“有点像”的灰色地带。这意味着写出来的代码逻辑非常清晰:算哈希、查字典、作判决。

特征码扫描虽然听起来更专业,但它需要你先有大量恶意样本,从样本中提取“精炼”的特征片段。这一步本身需要专业知识和样本来源,对新手来说门槛太高。行为分析更不用说,沙箱、API监控、规则引擎,任何一个模块都够写几个月。所以我最终的结论是:先用哈希引擎把杀毒软件的整体骨架立起来,后续想加特征码或行为分析,都是在这个骨架上做增量。

2. 搭一个能跑的最小扫描器:目录递归、哈希提取、特征库

2.1 选型:为什么用Python而不是C/C++或Go

这个项目用Python实现,几乎是必然选择。Python开发效率高,代码可读性好,而且hashlib、os.walk这些标准库直接覆盖了核心需求。相比C/C++,省去了内存管理和跨平台编译的麻烦;相比Go,省去了类型声明和编译步骤。性能确实不如C和Go,但通过“只扫描高风险目录”“多线程处理文件IO”等策略,日常使用完全够用。

另外提一句,Python项目后面要用PyInstaller打包给其他机器用也很方便,网上关于“python怎么打包部署”的文章一大把,跟着走一遍就能出exe。这对一个个人小工具来说已经足够了。

2.2 特征库设计:JSON就是一种最直观的数据库

特征库我直接采用JSON文件存放,原因很简单:可读性好、改动方便、天然支持Git做版本管理。当你需要追踪“什么时候加入了哪条特征”,直接看git log就行,这也是“源代码管理”在杀毒软件项目里最有价值的体现——不光是管理代码,还要管理特征库和隔离日志。

{ "signatures": [ { "id": "EICAR-Test-File", "sha256": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f", "risk": "high", "description": "EICAR标准测试文件,用于验证杀毒引擎是否正常工作" } ] }

每条特征记录包含样本名、SHA-256、风险等级和描述。之所以用SHA-256而不是MD5,是因为虽然MD5计算更快,但在安全领域已经被证明存在碰撞风险,做成产品迟早是个雷。哈希引擎本来图的就是“精确”,没必要在哈希函数选择上留隐患。

2.3 扫描器核心代码

扫描器的逻辑分三段:遍历目录、计算文件哈希、匹配特征库。算哈希时注意两点:一是必须以二进制模式打开文件,二是要分块读取,否则遇到超大文件会把内存吃满。

import hashlib import json import os CHUNK_SIZE = 8192 def calculate_sha256(file_path): h = hashlib.sha256() with open(file_path, "rb") as f: while True: chunk = f.read(CHUNK_SIZE) if not chunk: break h.update(chunk) return h.hexdigest() class SimpleScanner: def __init__(self, signature_db_path="signatures.json"): with open(signature_db_path, "r", encoding="utf-8") as f: self.signatures = json.load(f)["signatures"] self.suspicious = [] def scan_file(self, file_path): try: file_sha256 = calculate_sha256(file_path) except (PermissionError, OSError): return for sig in self.signatures: if file_sha256 == sig["sha256"]: self.suspicious.append({ "file": file_path, "match": sig["id"], "risk": sig["risk"] }) return True return False def scan_directory(self, target_dir): for root, _, files in os.walk(target_dir): for filename in files: full_path = os.path.join(root, filename) self.scan_file(full_path) return self.suspicious

这段代码虽然短,但已经把“杀毒引擎的完整闭环”跑通了。os.walk负责递归目录,calculate_sha256负责提取文件指纹,scan_file负责查表比对。没有花哨的技巧,每一步都是工程上最稳的写法。

2.4 跑起来看一眼

创建一个测试目录,在目录里新建一个test.txt,写入下面这串内容:

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

这串文本是国际反恶意软件测试标准组织定义的EICAR测试文件,完全无害,但所有杀毒软件都会把它识别为病毒,专门用来验证杀毒引擎是否正常。运行扫描器:

python scanner.py /path/to/test_dir

输出会明确告诉你命中了哪条特征、风险等级是多少。这个结果是可以预测的,因为EICAR的SHA-256是公开固定的,只要你的哈希算法没写错,必然命中。

3. 处置机制:隔离区、白名单、恢复流程

3.1 为什么不直接删除,而要设计隔离区

新手最容易犯的错是一发现“病毒”就delete。实际上,商业杀软普遍采用“隔离”而不是“删除”,这里有一个很重要的现实原因:杀毒引擎存在误报可能。哪怕Windows 11自带的杀毒软件也出现过误杀正常程序的情况,一旦误判且直接删除,用户的数据就永久丢失了。隔离区相当于一个“待观察区”,既让可疑文件无法继续运行,又保留了后续恢复的可能。

3.2 隔离区的简单实现思路

隔离区的功能有三个:把可疑文件移到一个受控目录、保留原始路径信息、支持一键恢复。我用一个字典结构来记录关系:

import json import os import shutil class Quarantine: def __init__(self, quarantine_dir="quarantine", record_file="quarantine.json"): self.quarantine_dir = quarantine_dir self.record_file = record_file os.makedirs(quarantine_dir, exist_ok=True) self.records = self._load_records() def _load_records(self): if os.path.exists(self.record_file): with open(self.record_file, "r", encoding="utf-8") as f: return json.load(f) return {} def quarantine_file(self, src_path): if not os.path.exists(src_path): return None q_path = os.path.join(self.quarantine_dir, os.path.basename(src_path) + ".quar") shutil.move(src_path, q_path) self.records[q_path] = { "original_path": src_path, "quarantine_time": time.time() } self._save_records() return q_path def restore_file(self, q_path): if q_path not in self.records: return False original_path = self.records[q_path]["original_path"] os.makedirs(os.path.dirname(original_path), exist_ok=True) shutil.move(q_path, original_path) del self.records[q_path] self._save_records() return True

注意记录文件要用JSON持久化,这样重启程序后依然能查询历史隔离记录。

3.3 白名单机制,避免“杀熟”

除了隔离区,白名单同样重要。白名单可以分为三类:目录白名单、文件哈希白名单、扩展名白名单。在扫描时如果目标路径落在白名单目录里,直接跳过;如果文件的哈希命中白名单,即使特征库里有同名特征也放行。对应的设计是:在扫描器初始化时加载白名单集合,scan_file先做白名单判断再做特征匹配。

我自己在实际使用中会重点把开发目录、编译输出目录加进白名单。因为这些目录里全是自己生成的临时文件和构建产物,很容易触发启发式误报,虽然哈希引擎不会误报,但提前跳过能省下大量扫描时间。

4. 从手动扫描到实时监控:watchdog与进程快照

4.1 手动扫描是体检,实时监控是门卫

手动扫描解决的是“查一下当前目录是否安全”的问题,但真正危险的场景是“文件刚落盘的那一刻”。商业杀软之所以能做到实时防护,本质是在文件系统层挂了钩子,文件一创建、修改、执行,引擎立刻介入。在Python里,我们可以用watchdog库实现一个轻量版目录监听。

pip install watchdog

4.2 用watchdog监听文件创建事件

watchedog会回调事件处理器,我们只需要在文件创建后调用SimpleScanner.scan_file即可:

from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class ScanEventHandler(FileSystemEventHandler): def __init__(self, scanner, quarantine): self.scanner = scanner self.quarantine = quarantine def on_created(self, event): if event.is_directory: return if self.scanner.scan_file(event.src_path): print(f"[ALERT] 发现可疑文件: {event.src_path}") self.quarantine.quarantine_file(event.src_path) def start_monitor(target_dir, scanner, quarantine): event_handler = ScanEventHandler(scanner, quarantine) observer = Observer() observer.schedule(event_handler, target_dir, recursive=True) observer.start() return observer

实际跑起来你会发现,下载目录有文件落地后,程序几乎是立刻给出告警。这种“即时反馈”带来的成就感,比手动扫描强太多。

4.3 进程层面的补充监控

除了文件扫描,还可以用psutil库定期枚举运行中的进程,计算进程可执行文件的哈希,检测是否有恶意程序在运行。需要注意,读取某些系统进程的exe路径或计算哈希时可能遇到权限问题,必须用try/except包住。这个模块不要频繁执行,我建议配置成每30秒扫一次,而且只对新增进程做哈希计算,避免不必要的CPU开销。

4.4 实时监控的开销控制

实时监控最怕的是高IO场景下卡顿。几个有效的控制策略:只监听用户指定的高风险目录(下载目录、U盘挂载点、临时目录),排除系统目录;监听时忽略.tmp.cache等临时文件;文件大于200MB直接跳过哈希计算。别小看这些细节,在满是日志和缓存的目录里,没有过滤策略的监听器能把磁盘IO拖垮。

5. 误报、性能与工程化打磨

5.1 哈希缓存:让重复扫描不再浪费时间

全盘扫描最痛苦的是重复计算那些没变过的文件哈希。我给扫描器加了一个缓存字典,记录每个文件的“修改时间+大小+哈希值”。第二次扫描时,如果文件的修改时间和大小都没变,就直接复用上次的哈希结果。实测下来,在文件变动不频繁的目录里,扫描速度能提升好几倍。

5.2 多线程扫描,但别误解它的作用

有人会直接给每个文件开一个线程,这在Python里收益有限。因为hashlib计算哈希时虽然会释放GIL,但文件IO和字典比对仍然受GIL约束。更合理的做法是用ThreadPoolExecutor控制并发数在4到8之间,让多线程主要用来抵消磁盘IO等待时间,而不是追求CPU并行。想真正跑满多核,需要走多进程方案,但代价是代码复杂度明显上升,个人小工具没必要。

5.3 误报的根因,以及Windows自带杀软的翻车案例

哈希引擎本身不会误报,但如果特征库写错了哈希值,就会把正常文件误判为恶意文件。这提醒我们:特征库要严格从可信渠道获取样本哈希,不要听风就是雨地手动录入。启发式引擎的误报则更隐蔽,“像病毒”的阈值设低了就会误伤,设高了又漏报。Windows 11自带杀软就曾因把某些正常软件的更新文件判定为风险而引发广泛讨论,这类事件说明,误报是每个杀毒软件都会面对的难题,唯一能做的是用隔离区兜底、用白名单预防、用二次确认降低影响。

5.4 把误报降到更低的工程手段

我在实践中加了三个手段。第一,对命中结果做风险分级,只有risk=high才自动隔离,medium只告警不处理。第二,支持“上传哈希到在线查杀服务”做二次确认,本地判断为可疑后,再去云端数据库查一下这个哈希的公开信誉,两边都判定恶意才执行隔离。第三,所有告警都写结构化日志,方便事后复盘。这套思路和商业杀软的云查杀雏形已经很接近了。

6. 验证与升级:从EICAR测试到向商业杀软靠拢

6.1 用EICAR标准测试文件做回归验证

我前面提到过EICAR测试文件,这里再强调一遍它的价值。每次改动完扫描器代码,我都会跑一遍EICAR测试,确保核心检测链路没有被改坏。这个习惯帮我避免过好几次“改了好多代码,最后发现扫描器根本匹配不到东西”的尴尬。在自动化测试里,也可以把EICAR样本作为单元测试的输入,提交代码时自动跑通。

6.2 为什么不要拿真实病毒练手

网上有不少“病毒样本下载站”,但我不建议新手在真实环境中直接测试。一是真实样本可能被二次打包,运行后带来的不仅是杀毒查杀问题,还有自我传播感染的风险;二是在没有隔离环境的前提下,任何一次失误都可能导致本机数据受损。如果真想深入,应该学习使用虚拟机和快照,在完全隔离的网络环境中操作。学习开源项目时,从GitHub这些“免费源代码网站”上找杀毒软件的源码和文档是没问题的,但别顺手把仓库里存放的标注为样本的文件直接拖到桌面运行。

6.3 进阶路径:怎么从“简单”走向“真正可用”

哈希引擎只能杀已知文件,这是它的天花板。想让这个项目成为真正可用的工具,有三条进阶路径。第一条是特征码提取,从恶意样本中提取短字节序列,配合通配符规则,能覆盖同家族变种;第二条是接入在线杀毒API,把可疑文件的哈希发给云服务,根据返回结果二次判定,这基本等于0成本接入商业病毒库;第三条是搭建简易沙箱,在隔离目录里运行可疑程序,监控它的文件写入和注册表操作,用行为得分判断恶意。第三条工作量最大,但也最接近现代杀软的防护核心。

6.4 源码分发与保护:Python打包、反编译、特征库放服务端

写完了想分享给别人用,就绕不开源码保护话题。Python不像Go编译成二进制后反编译基本拿不回原始源代码结构,Go反编译你最多看到一堆汇编指令和符号信息,而Python发布出去的.py文件,或者用PyInstaller打包后的exe,用反编译工具几乎能还原出源码逻辑。这就是为什么网上讨论“源代码加密方法”时,Python总是重点照顾的对象。

我的建议是:个人工具直接发源码也没什么大不了,但如果想把工具做成产品,至少要做到三点。第一,核心特征库不要随包分发,改成启动时从服务端拉取,这样即使程序被反编译,也拿不到完整的特征库数据;第二,用Nuitka或者Cython把关键模块编译成二进制扩展,提高逆向门槛;第三,敏感逻辑尽量放服务端,客户端只做数据采集和结果展示。这三点对应到商业杀软上,就是“特征库云端化、核心引擎硬化、策略服务端化”的缩略版。

最后再分享一个我自己的小习惯。现在我电脑上这个扫描器并不常驻,而是配了一个右键菜单调用,对单个文件或目录手动扫码,同时每天凌晨用计划任务自动扫一次下载目录。这种“按需+定时”的使用方式,既保留了实时监控的即时性,又不会像常驻监控一样在编译大项目时拖慢磁盘。你如果自己动手把这个项目写完,不妨也按自己的使用习惯去调扫描策略,这才是自己写杀毒软件源代码最大的乐趣——它完全懂你的电脑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询