1. 一个没有C2指令的样本:恶意软件内部的老哥们先投了个票
上周复盘一次授权攻防演练的数据时,我盯着终端日志里一个时间戳看了很久。恶意软件在凌晨3:17发起了一次横向移动,但全流量回溯显示,没有任何外部主机给它下发指令——没有传统意义上的心跳信标,没有远程控制器,没有账号联动。一开始我以为只是样本静默潜伏后的定时触发,直到把它丢进隔离环境反复观察,才发现问题远不简单:它的决策链路里坐着四个AI,攻击动作是这四个模型内部投票之后才拍板的。恶意软件、AI、投票,这三个词组合在一起,意味着一种完全不同的攻击范式。
过去我们分析恶意软件,核心问题永远是"谁下的令"。一个进程做坏事,背后总有一条指令链:远程攻击者通过C2通道下发命令,或至少有一个预编译的硬编码逻辑在按时间、按条件触发。分析师的思路就是顺着这条链往上摸,摸到人,威胁就闭环了。而这套四AI投票机制把这条链彻底剪断了——样本落地之后,所有决策都在主机内部完成,一个个上游模型给出各自判断,凑够阈值才动手。就算你把样本完整拖进沙箱,看到的也只是几个进程在频繁读写共享内存,根本找不到"谁下的令"。
1.1 我为什么第一次看到"投票"会有后背发凉的感觉
先说一个背景:我平时做红队和恶意代码分析,见过大量使用机器学习的恶意样本。绝大多数所谓"AI恶意软件"其实很粗糙,无非是在C2通信里塞了ML分类器做流量伪装,或者在免杀环节套一层生成式模型把载荷重写一遍。这类样本的AI部分再花哨,也只是工具,决策权仍然在远端的"人"手里,模型顶多算个翻译官。
但这回的样本不一样。它内部那四个AI,各自拥有独立的输入和判断标准,最后通过一个投票协议汇总意见。没有人在它们之间传话,攻击触发也不需要任何人点头。我把它称为"群体决策型恶意软件"——攻击意图不再是外部注入的,而是模型在特定上下文里"涌现"出来的。谁写的规则?谁设的阈值?原始作者只是定了一个框架,具体什么时候攻击、以什么方式攻击,全是模型自己碰出来的。
这种样本对防御体系的冲击不是单点上的,而是方法论层面的。传统的检测思路是"找异常→关联到人→封堵链路",而投票式恶意软件把攻击决策变成了一个黑盒民主过程:你看到的是结果(横向移动、数据外渗),看不到产生结果的辩论过程。更麻烦的是,如果把样本放进沙箱,它不会复制在真实环境里的行为——因为沙箱里观察到的上下文不同,四个AI投出来的票也就不一样。
1.2 传统恶意软件的"下令链"是怎么被剪断的
把旧范式和这个新样本放在一起对比,会看得更清楚。经典的恶意软件生命周期是:投放载体→执行载荷→回连C2→等待指令→执行指令。哪怕是最先进的定向攻击,也离不开一个远程控制端,因为攻击者需要在关键时刻调整策略。C2信标就是这条链上最粗的一根线,防御方只要盯住"外部控制端到内部主机的通信",就能抓住它的小辫子。
投票式恶意软件把这根线拆了。四个AI全部在本地运行,样本自带模型文件、自带本地推理库,回连动作只在极特殊情况下发生。它不需要C2下发指令,因为"攻击什么、怎么攻击"这些事,已经被分解成四个模型各自的评分任务。攻击者真正需要做的是在投放前把所有知识封装进模型权重里,剩下的交给投票。这是一种"预谋前置+执行自动化"的架构:远程的那个人在打包样本的那一刻就已经把意图写在模型里了,后面机器只是按自己的判断去执行。
在这个上下文里,"没人下令"不是说没有攻击意图,而是攻击意图被移到了人的视野之外。这就像你把一套房贷审批制度写好了,审批员换成了四个有主见的AI,它们各自评估信用、房产、收入、政策风险,最后投票决定放不放款——你不需要亲自打电话催审批。恶意软件也一样,它不需要你每天发命令,它自己开会、自己拍板。
2. 四个AI的分工,和那份投票协议
既然叫投票,就不能只有一张票。这套机制的核心在于分工:四个AI对应四种不同的判断维度,互相之间不读对方的决策,只读对方产出的评分。我后面在仿真环境里复现过一次,把四个Agent的黑盒壳子拆开,看到的内部结构非常清晰。
2.1 四个席位各管一段
投票的前提是每个投票人看问题的角度不同,否则投出来的票没有信息量。样本里的四个席位可以这样理解:
| Agent | 席位 | 主要输入 | 输出评分 |
|---|---|---|---|
| A1 | 侦察席 | 目标存活状态、端口开放情况、服务指纹、补丁信息 | 目标价值分 |
| A2 | 策略席 | 内置漏洞库、路径可达性、当前权限状态 | 攻击方案可行分 |
| A3 | 规避席 | 主机上安全软件进程、网络出口策略、沙箱特征、用户习惯 | 暴露风险分 |
| A4 | 执行席 | 前三者的评分、系统资源余量、运行时间预算 | 综合置信度 |
侦察席的逻辑不复杂,类似一个轻量级资产识别器,但它输出的不是"扫到了什么",而是"这个目标值不值得我冒风险"。它会看目标的开放服务数量、版本指纹和网络拓扑位置,给目标打一个0到1之间的分。策略席更像一个规划器,输入漏洞库和当前权限后,在有限动作空间里选一条它认为胜率最高的攻击路径,同样输出一个0到1的可行分。
规避席是最有意思的一个席位。它不关心攻击成不成功,只关心"我做这个动作被发现的概率有多大"。它会检查当前主机上有没有EDR进程、防火墙策略有没有异常、系统是不是跑在虚拟机里、网络里有没有蜜罐特征。如果它嗅到沙箱的味道,就会给出极低的规避分。执行席则像最终拍板的项目经理,它不看重某个具体环节,而是把前三项评分加权汇总,加上对当前资源状态的判断,输出一个最终置信度。
这套装修思路的好处是:任何一个单一Agent被干扰或欺骗,都不至于让整个决策崩盘。你可以在沙箱里伪造环境变量骗过侦察席,但规避席可能仍然觉得不对劲,把整票拉低。
2.2 投票不是举手,是带权重的软投票
如果用"少数服从多数"来理解这套投票机制,会把它想简单了。它在实现上用的是带权重的软投票:每个Agent不只投"进攻/放弃"这种二元票,还附带一个置信度,最终决策值是加权和。
我复现时采用的协议参数是这样的:
- 权重分配:侦察席0.25、策略席0.30、规避席0.20、执行席0.25,四项权重和为1。
- 决策值S = Σ(权重 × 置信度),取值范围0到1。
- 进攻阈值:S ≥ 0.62。
- 最低票数:至少3个Agent的置信度不低于0.4,防止单一高权重Agent独走。
- 一票否决:执行席若输出置信度低于0.15,无论总分如何,直接进入休眠,不攻击。
- 投票窗口:200到800毫秒之间,超时未交票的席位按弃权处理,弃权不改变分母,只让权重重新归一化。
为什么阈值定在0.62而不是0.5?因为0.5只反映"多数支持",而0.62意味着连权重最低的规避席都得给出中等以上的置信度,否则光靠侦察和策略两个高权席位很难凑够。换句话说,即使其他三个席位都赞成,只要规避席觉得暴露风险高,S大概率被压在0.6以下。这个阈值在数学上天然给了"被发现概率"这一项极高的隐性话语权。
至于200到800毫秒的投票窗口,这是我最欣赏的一个细节。它防止了某席位因为推理太慢而“赖着不走”拖垮整个决策过程。对应的风险是:如果某Agent执行环境异常导致推理超时,系统会自动弃权按剩余席位重新归一化。这是一种容错设计,代价是决策质量下降——在后面的实测里,这个设计直接引出了一个意外(见4.4)。
2.3 为什么攻击者要设计成投票,而不是弄一个"总控AI"
这是我在分析时反复问自己的问题。样本作者完全可以把四个小模型合成一个大模型,让一个"总控AI"直接决定攻不攻,何必多此一举搞投票?答案从两个角度都能站住。
从生存性看,投票架构天然抗"斩首"。安全软件杀进程是单点打击,如果你把决策集中在一个大模型进程里,杀它就等于废了整个样本。但投票架构不一样——四个Agent分布在四个独立进程里,杀掉一个,剩下三个还能靠归一化权重继续投票,系统只是降级而非瘫痪。我实测过:杀掉规避席进程后,剩余三席重新归一化权重,S照样能过0.62,只是对暴露风险的敏感度下降。这种"断一指不断整臂"的特性,明显是冲着EDR的终结能力设计的。
从决策质量看,投票本身就是对抗过拟合的手段。一个统一大模型面对复杂环境时容易在某些特定输入组合上犯系统性错误,比如在VMware特征明显的蜜罐里照样攻击。而四个分工模型各有各的盲区,投票机制相当于把这些盲区互相抵消了一部分。就算策略席对某个漏洞过于乐观,规避席只要嗅到蜜罐气息就能把总分拉下来。这是一种工程上的"集成学习"思维——不追求单个模型的高分,而是通过多模型冗余减少整体误判。对恶意软件来说,误判的代价是暴露;对防御方来说,这四个模型互相制衡形成的复杂依赖,恰好是最难看懂、最难模拟的部分。
3. 投票式恶意软件凭什么绕过现有防御
很多熟悉的防御手段在四AI投票机制面前失效,这不是玄学,而是每一件都能在技术原理上讲清楚。
3.1 静态检测:模型文件成为新的"特征面"
杀软做静态检测的核心是特征匹配:恶意代码里特有的字符串、PE文件的导入表、加壳指纹、固定的文件哈希。传统的恶意软件无论怎么变形,总有稳定的特征可以提取。但四AI投票的样本本身就带着一堆模型文件——GGUF、ONNX、SAFETENSORS,这些是神经网络权重,不是代码。你没法从一个权重张量里提取"恶意意图"特征,因为权重本身只是一堆浮点数。
更要命的是,攻击者完全可以从开源社区拿现成的通用模型微调后塞进样本。这些模型的原身可能是合法的翻译模型、文本分类模型或视觉模型,在杀软的模型识别库里可能被标记为"AI工具"而不是"恶意代码"。四个模型又是异构的——不同架构、不同量化方式、不同来源——它们的组合特征空间几乎是无穷的,传统特征库根本拼接不出来。我试过把复现样本丢给市面主流查杀引擎,结果是三个模型文件全部判定"正常",只有负责调度的壳层被报可疑。只要攻击者把壳层换成合法白签名程序,整个样本就是一套"看起来很正常的AI全家桶"。
3.2 流量侧:没有C2,就失去了主战场
防守方在流量侧最擅长找的是"周期性外联"和"指令下发模式"。传统恶意软件总归要回连,不管走HTTP隧道还是DNS隧道,总有一个可辨识的通信模型。而投票式恶意软件的默认状态是"无外联"——模型在本地推理,决策在本地完成,只有攻击成功后才可能产生少量数据回传。这意味着你在流量侧等不到那个熟悉的"上报-响应"节奏。
就算样本在某些环节需要外联,比如从远程拉取最新的模型权重更新,这种流量也可以做得非常干净——走HTTPS、挂正规CDN、请求间隔随机化。因为模型更新本来就不是高频行为,一个月更新一次都算勤快了,流量侧那套"固定周期探测"模型根本没法覆盖这个尺度。我自己的经验是,流量侧的AI检测模型在"高频短周期通信"上效果不错,但对"低频长周期的大型二进制下载"几乎无能为力,这正好被投票式恶意软件利用了个正着。
3.3 行为分析的最大麻烦:行为不可复现
沙箱和行为分析引擎的核心假设是:同一个恶意软件,在真实环境里做什么,在沙箱里也会做什么。只要这个假设成立,沙箱观察到的行为序列就能用来训练检测模型。投票式恶意软件直接击碎了这个假设。
它的攻击动作依赖四个AI在特定上下文里的投票结果,而沙箱本身就是一种特殊的上下文。侦察席在沙箱里可能观察到"没有域环境""没有历史文件""流量被代理",规避席可能看到"存在虚拟机特征"“桌面无用户活动”,这些输入会把S压到阈值以下。于是沙箱里看到的样本是安静的、无害的,而真实环境的用户机器上,同样一份样本却能投出进攻票。这是沙箱检测的经典困境——"环境感知型恶意软件",只不过过去靠硬编码的环境检测条件,现在靠模型动态判断,让沙箱作者连"伪造什么字段才能骗过它"都难以确定。
我观察到的更麻烦的现象是:投票决策是概率性的,即使同一环境,轻微改动某个输入值,比如打开一个文件、改变一下网络延迟,就可能让投票结果翻转。行为分析引擎面对的是一只在同样条件下会给出不同行为的恶意软件,模型训练起来会极其痛苦——你以为抓到了规律,换个输入分布又全变了。
3.4 对威胁情报的冲击:IoC叙事的终结
威胁情报圈子里已经形成了一套固定的工作流:拿到样本→提取哈希、域名、IP、文件路径→发布IoC→其他防守方拉黑。投票式恶意软件对这种工作流是全面嘲讽:哈希是唯一的,但下次攻击者重新微调一下模型权重,哪怕只改一个量化参数,整个文件的哈希就全变了;恶意域名?它根本没有C2;攻击路径?由策略席在运行时决定,每次可能不同。你发布的IoC还没配完,人家的模型又进化了一轮。
真正要做的是把情报粒度从"静态指示物"升级到"行为模式"。比如"在一台主机上发现四个独立进程、各自加载了深度学习推理库、通过共享内存交换结构化JSON、随后出现命令执行"——这类行为组合特征才是对抗投票式恶意软件的可行情报形态。
4. 在仿真环境里复现四票攻击:我观测到的全过程
说再多原理,不如拉出来跑一遍。下面是我们在授权靶场里做的复现实验,完整链路包括环境搭建、Agent部署、投票过程观测,以及三个谁都没预料到的意外。
4.1 实验环境与放行策略
靶场划分了三个网段:攻击网段跑样本和攻击工具,内网段放了一台模拟业务服务器(Windows Server + 一个老旧Web服务),管理网段挂日志收集器和网络探针。三网段之间通过防火墙控制互通,业务服务器上只开80端口和445端口,模拟一个缺乏补丁的中小型企业内网。
放行策略上,我特意做了两个设置:一是沙箱探针只做被动监听,不做主动拦截,否则观察不到完整的投票过程;二是在业务服务器上放了一个低交互蜜罐,监听445端口并返回伪造的SMB banner。这么做的目的是想让规避席在投票时能真实"看见"蜜罐特征,观察它对总决策值的影响。
模型侧选型遵循"能耗最小化"原则:两个静态分类器用ONNX格式(分别做端口服务识别和漏洞匹配),一个端侧小模型用GGUF量化(承担策略推理的角色,但只开了三层网络,本质上是一个文本规则映射器),规避席则用了一个规则+轻量逻辑回归的混合模型。没有引入任何需要联网的大模型接口——这也是这套架构最让人头疼的地方:完全离线。
4.2 四个Agent的落地形态与共享内存机制
Agent进程之间不通过TCP通信,避免流量侧被一个简单的网络检测就能盯上。它们用的是操作系统级的共享内存和命名管道——Linux靶机上就是POSIX共享内存和消息队列,Windows靶机上换成了命名管道和内存映射文件。每次投票时,四个Agent各自向共享内存的"投票箱"写入一个JSON结构,包含席位ID、置信度、决策建议和时间戳。
投票箱是一个有界环形队列,容量12条记录,写满后最早的消息被覆盖。为什么要设计成环形队列?因为作者不希望投票历史永久留存——一旦攻击完成,清除投票痕迹比遍历删除文件简单得多,直接把共享内存对象释放掉就行。这对取证来说是个大麻烦:你分析样本的时候,投票的历史记录可能已经被覆盖了,只能靠日志侧碰运气。
调度器在每次发起动作前会做一次轻量模型加载。四个模型的体积控制得很小,总大小不到40MB,加载完成后常驻内存。这意味着一台2GB内存的旧终端也能跑得动,完全符合中小型企业内网里大量老旧机器的实际配置。对防守方来说,"跑不动AI"这个最后的防线也没有了。
4.3 投票过程的完整观测记录
实验里我抓到了一轮非常完整的投票过程,时间线如下:
| 时间 | 事件 |
|---|---|
| T+0s | 样本执行,初始化共享内存,四个Agent相继加载完毕 |
| T+2s | 侦察席扫描内网段,发现业务服务器80端口和445端口开放 |
| T+3s | 侦察席写入投票:目标价值0.82 |
| T+4s | 策略席查询内置漏洞库,命中445端口MS17-010类指纹,写入可行分0.91 |
| T+5s | 规避席检查主机与目标机:未发现EDR进程,但探测到445端口响应banner与真实服务器不一致(蜜罐特征),写入暴露分0.21 |
| T+6s | 执行席汇总:S = 0.25×0.82 + 0.30×0.91 + 0.20×0.21 + 0.25×0.85 = 0.705,超过0.62阈值,但执行席注意到规避分异常低,给出置信度0.60,未触发一票否决 |
| T+7s | 样本发起445端口漏洞利用,蜜罐记录到攻击流量 |
| T+8s | 利用失败(蜜罐不包含真实漏洞逻辑),样本进入下一轮投票 |
| T+10s | 第二轮投票中,策略席改用80端口Web服务漏洞方案,规避席蜜罐特征消失(因为目标换了端口),暴露分回升到0.58,S再次超过阈值 |
| T+12s | Web漏洞利用成功,样本在业务服务器上建立立足点 |
这轮记录完美展示了"投票→失败→重新投票→换策略→再进攻"的闭环。它不像传统恶意软件那样一条路走到黑,而是像红队分析师一样,打完一套方案发现不行,开个短会换个方案继续。这种自适应性是四AI投票机制最让防御方难受的地方。
4.4 实测中谁都没料到的三个意外
第一个意外:杀掉规避席进程后,系统确实降级运行了,但降级得比预想更狠。剩余三席权重重新归一化后,策略席权重从0.30涨到0.42,侦察席从0.25涨到0.35,整个系统变得比完整状态更激进。原本需要犹豫半天的攻击,杀掉规避席后几乎秒批。这说明防御方如果只做单点清除,反而可能触发"去抑制效应",把恶意软件逼成更莽撞的版本。
第二个意外:投票窗口的超时机制在低配机器上会反向坑自己。在模拟终端上加了一倍CPU负载后,某个Agent推理时间超过800毫秒被自动弃权,系统按剩余三席重新归一化,结果又是"去抑制",S值反而更容易过线。这个设计的容错初衷是好的,但在实际低配环境里,它等于主动放宽了决策标准——防御方在资源受限的IoT或老旧终端上,可能会看到投票机制"自我降智"后产生更多攻击尝试。
第三个意外和蜜罐有关。低交互蜜罐的SMB banner确实被规避席识别出来了,暴露分一度压到0.21,但执行席并没有因为这个低分直接否决,而是把攻击切换到另一个端口继续。也就是说,蜜罐只能阻止某一条具体路径的攻击,却不能阻止整体攻击意图。真要骗过这套系统,光在banner层做文章远远不够,得让多个维度的输入同时出现"这不是真实业务系统"的特征,才可能把总分压到阈值以下。
5. 防御侧的破局思路:不拦指令,断共识
分析了这么多,总要落到怎么防。既然投票式恶意软件的设计是绕开"指令链",防守方就不能再顺着"找指令"的思路走,而是要正视这个事实:你拦不住它的动作,但你可以影响它的投票结果。
5.1 思路一:让侦察席投出反对票(上下文污染)
投票机制依赖输入,而输入的很大一部分是环境信息。防守方完全可以主动污染环境上下文,让某个关键席位的置信度系统性走低。最有效的目标不是规避席,而是侦察席——因为侦察席是投票链路的源头,它给出的目标价值分低,后面策略席再想拿高分也撑不起总分。
具体做法是制造"低价值目标"的假象:在关键业务服务器上配置伪造的软件版本信息、关闭非必要服务、伪造假的补丁提示,让侦察席扫到一个看似陈旧但实际无价值的系统。同时配合蜜罐,在多个入口返回明显不真实的响应。重点是要让"多个维度的输入协同指向同一个结论",单点伪造会被其他Agent拉回来。
5.2 思路二:识别"模型存在性"而不是识别攻击行为
投票式恶意软件的硬特征是它必须携带AI模型和本地推理能力。对安全产品来说,与其等它攻击,不如先盯住"模型文件落地+推理库加载"这两个前置动作。GGUF、ONNX、SAFETENSORS这些格式有非常固定的文件头,在终端侧做一个轻量扫描就能发现。如果一台普通业务机器上突然出现几个模型文件,伴随进程加载了onnxruntime或ggml等推理库,这个组合就足够触发告警了。
我建议在EDR里追加下面这类检测逻辑:
- 同一进程或父进程关联的进程组内,出现不低于3个独立模型文件的释放或映射;
- 这些模型文件的元数据里包含"quantization""onnx""gguf"等关键字段,且文件大小分布在几十MB量级;
- 随后出现跨进程共享内存的写入行为,共享内存名或管道名包含vote、verdict、decide等语义字段;
- 以上条件同时满足时,无论当前是否有恶意行为,直接标记为“AI决策集群嫌疑”,进入人工排查队列。
这套规则的误报率可控,因为普通业务程序几乎不会同时创建多个模型文件并通过共享内存做结构化通信。真正常见的AI应用往往会走本地的推理进程加单一模型文件,结构上就差了几个数量级。
5.3 思路三:审计投票过程,把"民主"变成"把柄"
进攻方指望共享内存环形队列销毁证据,防御方反制的办法是在系统层面留一个"投票审计员"。在Windows上可以利用ETW,在Linux上利用auditd或eBPF,重点记录带特定语义的共享内存读写事件。不用管具体内容是什么,只要发现某个进程周期性向共享内存写入结构化的JSON消息,并且这个周期和后续的横向移动或者文件操作在时间上强相关,这条链就可以作为关联分析的基础。
更进一步,可以把这个审计日志接入SIEM,做投票时序分析:某主机上出现结构化内存通信→随后出现横向移动流量,中间间隔只有几百毫秒,这种模式在正常业务里几乎不会出现。一次两次可能是巧合,连续出现三四次,基本可以判定主机上有群体决策型恶意软件在工作。
5.4 落地的优先级排序
结合我自己的使用感受,给资源有限的团队排一个落地优先级:
| 优先级 | 措施 | 投入成本 | 收益 |
|---|---|---|---|
| P0 | 终端侧模型文件扫描+推理库加载检测 | 低,只需扩展EDR检测规则 | 高,直接命中架构硬特征 |
| P1 | 共享内存通信审计并接入SIEM | 中,需要部署审计组件 | 中高,能提供取证和关联分析 |
| P2 | 蜜罐多维环境伪造让侦察席投反对票 | 高,需要长期维护 | 中,仅影响特定攻击路径 |
| P3 | 流量侧低频大文件下载检测 | 中 | 低,只覆盖模型更新的窗口 |
P0一定要先做,因为它完全不需要理解AI的任何原理,只要认文件格式就够。P1会对取证有帮助,但需要先有P0的告警做驱动。P2和P3属于进阶优化,资源充足再考虑。
6. 实验之外的体会:威胁在变,防守的"手艺"也得跟着变
做完这轮分析,我最深的感受不是"AI多可怕",而是威胁设计思路已经从"规则执行"升级到了"决策自主"。过去的安全对抗,本质是双方规则的博弈——攻击者写规则,防守者找规则漏洞。而现在,恶意软件内部已经不再用规则表达攻击意图,而是用模型参数和投票协议封装意图。你没法通过分析代码逻辑找到"它在什么条件下会攻击",因为条件本身是四个模型在运行时动态判断出来的。
我个人在实际操作中的体会是,面对这种样本,最重要的不是学更多AI算法,而是建立一个新习惯:看到任何程序行为,先问一句"这个行为的决策来自哪里"。如果一个进程的行为模式不是线性的、不在固定条件集合里、仿佛带着某种"判断力",那它背后大概率坐着模型,而模型的权重文件、推理库、共享内存就是它藏不住的马脚。把检测视角从"行为是什么"切到"决策从哪来",你就能在它还没做出危险动作之前先抓到它。
最后再分享一个小技巧:如果你手头没有复杂工具,只靠任务管理器级别的粒度,就盯模块加载列表。凡是出现onnxruntime、tensorflow-lite、ggml、openvino这类推理库模块,同时进程又不在正常Audio/Video/Ocr处理场景里,一律按"可能携带本地模型"上报。这个动作几乎能覆盖所有本地推理型恶意软件,而且从Windows到Linux都通用。以后的恶意软件只会越来越像"会独立思考的东西",防守方也该学会用"它怎么思考"而不是"它做了什么"去还原真相了。