写个简单杀毒软件:哈希引擎、隔离区与实时监控实现
简介编译打包的杀毒与木马分析源代码面向安全初学者和开发者用于演示木马扫描、病毒查杀与恶意代码检测的基础架构。压缩包共七十个文件以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_pathsignatures.json): with open(signature_db_path, r, encodingutf-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!$HH*这串文本是国际反恶意软件测试标准组织定义的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_dirquarantine, record_filequarantine.json): self.quarantine_dir quarantine_dir self.record_file record_file os.makedirs(quarantine_dir, exist_okTrue) self.records self._load_records() def _load_records(self): if os.path.exists(self.record_file): with open(self.record_file, r, encodingutf-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_okTrue) 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 watchdog4.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, recursiveTrue) 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 把误报降到更低的工程手段我在实践中加了三个手段。第一对命中结果做风险分级只有riskhigh才自动隔离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把关键模块编译成二进制扩展提高逆向门槛第三敏感逻辑尽量放服务端客户端只做数据采集和结果展示。这三点对应到商业杀软上就是“特征库云端化、核心引擎硬化、策略服务端化”的缩略版。最后再分享一个我自己的小习惯。现在我电脑上这个扫描器并不常驻而是配了一个右键菜单调用对单个文件或目录手动扫码同时每天凌晨用计划任务自动扫一次下载目录。这种“按需定时”的使用方式既保留了实时监控的即时性又不会像常驻监控一样在编译大项目时拖慢磁盘。你如果自己动手把这个项目写完不妨也按自己的使用习惯去调扫描策略这才是自己写杀毒软件源代码最大的乐趣——它完全懂你的电脑。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →