0%
毅种循环

返回

邮件钓鱼攻防工程·3:贝叶斯与 NLP 内容对抗Blur image

字数 6265,阅读大约需 32 分钟

本文是「邮件钓鱼攻防工程」系列的第三篇。前两篇解决了发信通道和反沙箱。但发信只是一部分,正文被网关过滤,前面的工作全白做。

邮件能不能进网关,内容进网关之后能不能不进垃圾箱,这是两个完全不同的对抗维度。本篇拆邮件安全网关(SEG)最核心的内容检测逻辑:贝叶斯分类器和 NLP 语义引擎,以及工程上的规避思路。


image 1785951205 005
image 1785951205 005
作者: 可惜夜

首发至公众号:Yofune安全研究 欢迎关注。

0x01 对手 模型 :SEG 内容层的双层检测架构#

国内和境外的环境差异直接影响内容对抗的策略选择。境外企业网络的主力 SEG 是 Microsoft Defender for Office 365 和 Proofpoint;国内环境更复杂,大型央企和金融机构普遍部署了国产邮件网关,安全厂商这类产品百花齐放,不一一例举。中小企业大量使用腾讯企业邮、 阿里云 企业邮,后端反垃圾接的是云端能力。

这些产品的内容检测架构大体一致,底层两层叠加:第一层是贝叶斯概率层,基于词袋模型,SpamAssassin 和 Rspamd 是最典型的开源实现,国产厂商基本在此之上做了中文语料的二次训练;第二层是深度语义层,国内有不少基于 BERT-base 的轻量检测模型,不详细展开,感兴趣的可以搜索关键字。两层检测不是串联阻断,而是评分加权合并。一封邮件贝叶斯层得分很低,但只要深度语义层捕捉到了钓鱼邮件特征,照样被拦。目标是让最终得分低于阈值,需要两层一起对抗。

image 1785951205 001
image 1785951205 001

SpamAssassin 的 Bayes 插件(lib/Mail/SpamAssassin/Plugin/Bayes.pm#

Token 化流程(源码 tokenize 函数位于第 1134-1217 行,_tokenize_line 位于第 1219-1380 行):

原始文本 正则提取 最少3字符过滤 SHA1哈希截取40位 停用词过滤
bash

Token 提取正则(_tokenize_line 函数内,行 1235-1240):

image 1785951205 004
image 1785951205 004

# 使用正则表达式提取Token:匹配ASCII单词、英文字符,以及多字节的UTF-8编码字符(用于支持中文等非ASCII文本)
s{ ( [A-Za-z0-9,@*!_'"\$. -]+  |     # 匹配常规ASCII词元
     [\xC0-\xDF][\x80-\xBF]       |     # 匹配UTF-8双字节字符
     [\xE0-\xEF][\x80-\xBF]{2}    |     # 匹配UTF-8三字节字符(包含常见中文字符)
     [\xF0-\xF4][\x80-\xBF]{3}    |     # 匹配UTF-8四字节字符
     [\xA1-\xFF] ) | . }                # 匹配ISO-8859等单字符或多字节边缘字符,其余未匹配的单个字符替换为空格
 { defined $1 ? $1 : ' ' }xsge;
bash

SHA1 哈希压缩为 40 位 Key(tokenize 函数内,行 1212):

# 对提取出的Token计算SHA1值,并截取其哈希结果的最后5个字节(40位),作为统计数据库中存储的唯一键值
$tokens{substr(sha1($token), -5)} = $token  if $token ne '';
bash

SpamAssassin 实现里有三个硬性设计边界值得利用。

Token 计数上限(行 250):

image 1785951205 003
image 1785951205 003

# 声明在对数似然比排序中参与评分合并计算的最大有效Token数量为150。超过该限制的弱特征Token将被舍弃。
use constant N_SIGNIFICANT_TOKENS => 150;
bash

SpamAssassin 在计算贝叶斯分值时,会把分词拿到的所有 Token 按概率偏差(即该词有多偏向垃圾邮件或正常邮件)进行降序排列,最多只取前 150 个最明显的 Token 参与最终计算,超出 150 个的词直接丢弃。这个 150 的限制是代码里写死的硬性规定。

只要在正文里混入足够多(超过 150 个)的强 Ham 词(即正常业务往来中的高频词),就能把真正敏感的恶意词挤出这 150 个计分窗口,使其不参与最终评分。

image 1785951205 002
image 1785951205 002

Robinson 平滑算法(行 1644-1653,常量在 CombineChi.pm 第 47-50 行定义,FW_S_CONSTANT = 0.030FW_S_DOT_X = 0.538 * 0.030 = 0.01614):

# 1. 计算原始贝叶斯分类概率
my $prob = ($s * $nn) / ($n * $ns + $s * $nn);

# 2. 如果启用了 Robinson 平滑算法(针对低频出现的 Token)
if (USE_ROBINSON_FX_EQUATION_FOR_LOW_FREQS) {
  my $robn = $s + $n; # 该 Token 在所有样本中出现的文档总数
  # 使用 Gary Robinson 的 f(w) 公式计算平滑后的概率:
  # P_smooth = (s * x + n * P_raw) / (s + n)
  $prob = ($Mail::SpamAssassin::Bayes::Combine::FW_S_DOT_X + ($robn * $prob))
          / ($Mail::SpamAssassin::Bayes::Combine::FW_S_CONSTANT + $robn);
}
bash

robn 是词库里记录的该词总频次。塞入拼写奇怪的罕见词或生僻词,平滑公式会直接把它的概率拉回 0.5(代表无特征)。概率为 0.5 的词在排序时垫底,进不了 150 个评分窗口。所以,做正文稀释时,必须使用网关已经见过多次的常规商务词汇,不能胡乱生造冷门词。

得分合并的数学核心,在 CombineChi.pm(行 60-104):

image 1785951205 006
image 1785951205 006

Fisher 算法在数学上默认所有词相互独立。但实际邮件中,词语之间关联性非常强。塞入一整段连贯的商务文本,这些关联词会直接扭曲卡方统计的自由度计算,降低分类器判定的把握,反而容易触发误判放行。

Rspamd 的 Bayes 分类器(src/libstat/classifiers/bayes.c#

和 SpamAssassin 不同,Rspamd 使用的是 OSB(Optimal Score Based)算法。

它的一个核心特点是把单个词(Unigram)的权重设成了 0(行 215)。不管单个词有多敏感,它都不会直接影响最终分值,必须和相邻词语结合组成 Bigram(双词组)或 Trigram(三词组)窗口才会计分。

光堆砌单个单词没用,必须注入连贯的词组或完整的商务短语。在中文场景下, 分词器 如果把词切得太细,容易破坏 OSB 窗口的统计结构。

Rspamd 里的 Robinson 平滑通过 PROB_COMBINE 宏实现(行 217):

// prob: 原始概率, cnt: Token出现总数, weight: Robinson平滑权重, assumed: 默认假设概率(通常为0.5)
#define PROB_COMBINE(prob, cnt, weight, assumed) (((weight) * (assumed) + (cnt) * (prob)) / ((weight) + (cnt)))
bash

bayes_classify_token 函数(行 227-324)中被调用:

// 计算 w
w = (fw * total_count) / (1.0 + fw * total_count);
// 平滑合并概率
bayes_spam_prob = PROB_COMBINE(spam_prob, total_count, w, 0.5);
bash
Rspamd 统计 Token 标志位定义(src/libserver/word.hsrc/libstat/tokenizers/tokenizers.c#

分词阶段,Rspamd 会在 src/libserver/word.h(行 34-47)中为 Token 标记不同的 Flag,这些属性采用 bitmask 形式定义:

标志名称位偏移量掩码实际值安全对抗及分析含义
RSPAMD_STAT_TOKEN_FLAG_TEXT1u << 00x01纯文本内容提取的常规 Token
RSPAMD_STAT_TOKEN_FLAG_META1u << 10x02元数据或指令生成的 Token
RSPAMD_STAT_TOKEN_FLAG_LUA_META1u << 20x04由 Lua 规则接口动态产生的 Token
RSPAMD_STAT_TOKEN_FLAG_EXCEPTION1u << 30x08异常 Token(例如 URL 提取出的特征)
RSPAMD_STAT_TOKEN_FLAG_HEADER1u << 40x10邮件 Header 头部字段提取的 Token
RSPAMD_STAT_TOKEN_FLAG_UNIGRAM1u << 50x20单字/单词一元 Token 标志
RSPAMD_STAT_TOKEN_FLAG_UTF1u << 60x40UTF-8/Unicode 多字节文本 Token
RSPAMD_STAT_TOKEN_FLAG_NORMALISED1u << 70x80ICU 库成功进行规范化转换的 Token
RSPAMD_STAT_TOKEN_FLAG_STEMMED1u << 80x100已成功执行词干提取转换的 Token
RSPAMD_STAT_TOKEN_FLAG_BROKEN_UNICODE1u << 90x200Unicode 序列规范化失败或格式损坏;会标记 RSPAMD_TASK_FLAG_BAD_UNICODE,但不对 Bayes 权重做 50% 降权
RSPAMD_STAT_TOKEN_FLAG_STOP_WORD1u << 100x400识别为高频中性的停用词
RSPAMD_STAT_TOKEN_FLAG_SKIPPED1u << 110x800属于被衰减或跳过的 Token,不参与评分计算
RSPAMD_STAT_TOKEN_FLAG_INVISIBLE_SPACES1u << 120x1000包含不可见字符的 Token
RSPAMD_STAT_TOKEN_FLAG_EMOJI1u << 130x2000包含 Emoji 表情符号的 Token

BROKEN_UNICODEINVISIBLE_SPACES 是判断是否存在异常绕过行为的重要技术线索。若邮件中包含大量非规范 Unicode 或隐藏格式,容易触发网关的异常拦截逻辑。

词干提取与自定义分词(tokenizers.c 约第 600-650 行)#

词干提取(Stemming)会消除单词变形的影响,例如 "approved""approves" 会归并为相同的词干 "approv"。这虽然限制了通过同词变形刷计数的做法,但也意味着如果注入大量 "unanimously",等于在全局给 "unanim" 这一族词干刷了强烈的 Ham 信誉。在中文或日文的实际部署中,如果网关引入了自定义的中文分词器,需要摸清其词典边界,OOV的打散退化往往会减弱稀释效果。

所有 Token 在生成时都会计算 Mum-hash 用于 Redis 端的去重。即使使用等价 Unicode 字符替换,哈希值的改变也会使其被当作新词统计。

自动学习决策(lualib/lua_bayes_learn.lua,行 184、368)#

can_learn() 逻辑中,Rspamd 设置了自适应防过拟合的保护机制。默认的置信度阈值为 min_prob = 0.95

-- 判断当前邮件的贝叶斯分类置信度是否已经达到阈值(默认为0.95)
in_class = prob >= (probability_opts.min_prob or probability_opts.spam_min or 0.95)

if in_class then
  -- 若系统已经足够确信,则拒绝进一步将其加入训练库中,以防止模型过拟合
  return false, reason, ctx.result
end
bash

如果稀释文本成功将评分卡在拦截线以下,且置信度未达到 0.95,则分类器既不会拦截该邮件,也无法从中学习到最新的特征漂移,从而提供了一个稳定的绕过时间窗口。

image 1785951205 007
image 1785951205 007

Rspamd 的神经网络插件(src/plugins/lua/neural.lua#

除了贝叶斯层,Rspamd 默认启用了 ANN(神经网络)分类插件。

其后置过滤器回调 ann_scores_filter(源码约在 121 行)在贝叶斯分类器之后执行。这意味着神经网络看到的特征已经经过了贝叶斯层的预处理。

特征提取依赖以下五个 Provider 接口(源码第 33-37 行):

-- 动态加载这五个特征提供者模块
pcall(require, "plugins/neural/providers/llm")           -- 外部LLM嵌入特征
pcall(require, "plugins/neural/providers/symbols")        -- 规则得分缓存收集
pcall(require, "plugins/neural/providers/text_hash")      -- 局部特征哈希
pcall(require, "plugins/neural/providers/fasttext_embed") -- FastText 词嵌入
pcall(require, "plugins/neural/providers/static_embed")   -- 静态词嵌入
bash

在生产环境中,通常仅启用 symbolstext_hash

在 Hybrid特征提取模式下,神经网络把贝叶斯的打分直接当做特征输入的一部分。贝叶斯分数被稀释词干扰降下来后,这个偏低的分数被神经网络直接吃进去。神经网络虽然学到了”低贝叶斯分 = Ham”,但它无法区分这封信是因为干净还是被掺了沙子。

网关串行审计架构在这里存在盲区。

网关开启了 PCA 特征降维时,多封邮件里注入的相似良性文本被当作主成分保留,而原本维度较低的恶意敏感词特征反而容易在降维过程中被过滤掉,导致神经网络失效。

权重继承机制(行 1452)通过 Redis 的 zset 查找 providers_digest 匹配的旧 profile 并复制权重,避免在配置更新时重新训练。

五个 Provider 绝大部分仍然依赖对 Token 流的切分。一旦贝叶斯层削弱了 Token 的区分力,神经网络所接收的输入特征也会随之失效。

Rspamd LLM Provider 的底层细节(llm.lua#

在 LLM Provider 的工程设计中,有两个机制值得关注:

拼装输入文本collect_async 中,输入文本强制按主题优先(Subject first)原则拼接:

"Subject: " + 邮件主题 + "\n" + 正文内容
bash

确保在嵌入向量的位置编码中,高信息价值的主题行始终被置于最前,避免因长文本 截断 而丢失关键信息。

路由与缓存:该插件支持根据邮件检测语言路由到不同的第三方模型,且使用了 Redis 进行本地缓存(TTL 默认 24 小时):

Redis 缓存机制引入了新的规避面。已知目标网关开启了该缓存时,可以提前发送一封内容正常的良性邮件(使用特定主题词),让网关缓存下该特征。后续发送带有相同主题词的邮件即可命中该干净缓存,绕过实时审计。

SpamAssassin 的独立神经网络插件(NeuralNetwork.pm#

SpamAssassin 的 NeuralNetwork 插件(v0.11.2)走另一条路:不依赖贝叶斯分,而是维护自己独立的词汇表与特征流水线。

隐藏层结构(源码第 767-770 行、第 1327-1330 行):

# 隐藏层1节点数计算:≈ √输入维度 × 0.25 (向上取整)
my $num_hidden1 = int(sqrt($num_input) * 0.25 + 0.5);
$num_hidden1 = 4 if $num_hidden1 < 4; # 最少4个节点

# 隐藏层2节点数计算:为隐藏层1节点数的一半
my $num_hidden2 = int($num_hidden1 / 2);
$num_hidden2 = 2 if $num_hidden2 < 2; # 最少2个节点
bash

输入层映射至该双隐层结构,并在单输出神经元输出结果。

词表修剪卡方计算_chi2_score,行 1065):

# 计算特定词项(term)的卡方得分,用于过滤全局区分度弱的特征词
sub _chi2_score {
  my ($spam, $ham, $total_spam, $total_ham) = @_;
  my $total = $total_spam + $total_ham;
  return 0 unless $total > 0;
  my $total_spam_noterm = $total_spam - $spam; # 未包含该词的Spam文档数
  my $total_ham_noterm = $total_ham - $ham;   # 未包含该词的Ham文档数

  # 分母为各维频数的乘积
  my $denom = ($spam+$ham) * ($total-$spam-$ham) * $total_spam * $total_ham;
  return 0 unless $denom > 0;

  # 卡方检验矩阵交叉相乘平方差计算,提取鉴别度分值
  return ($total * ($spam*$total_ham_noterm - $ham*$total_spam_noterm)**2) / $denom;
}
bash

在词汇表达到 10,000 的上限进行清理时,其卡方算法偏向于删除低频但有高特异性的词,而保留高频的中性词。使用高信誉的常规商务词汇进行稀释,它们不容易在词汇表清理中被剔除。

类别加权训练(行 817-825):

# 根据训练样本中Spam与Ham的文档比例,为少数类乘以一个反比权重,最大加权值为4.0倍
if ($isspam) {
  $class_weight = ($spam_docs > 0) ? $ham_docs / $spam_docs : 1.0;
} else {
  $class_weight = ($ham_docs > 0) ? $spam_docs / $ham_docs : 1.0;
}
$class_weight = 1.0 if $class_weight < 1.0;
$class_weight = 4.0 if $class_weight > 4.0;
my $weighted_epochs = int($train_epochs * $class_weight) || 1;
bash

大量发送被标记为 Ham 的稀释邮件,会稀释掉 ham_docs/spam_docs 的权重比例,直接把整个模型的权重偏向有利于 Ham 判定的方向。

在重训计算”类原型向量”时,一段周期内持续发送含有良性术语的稀释信,这些词录入词表后直接毒化类原型向量,在重训后整体带偏神经网络的判断基准。

SpamAssassin 神经网络插件有四道硬性检测门槛:邮件长度小于 256 字符,或命中词汇表的有效词少于 10 个时,系统直接跳过神经网络检测,退回到贝叶斯层。

正文中把可见字符控制在 200~250 字符(大约 3、4 句话),然后将大段的稀释语料通过 CSS 隐藏(不可见语料不计入正文字数),神经网络就会因为有效字数不足而直接放弃检测,退回贝叶斯层,此时隐藏稀释词已经绕过了它。

其他方案对比:无原生 ML 的 SMTP 架构#

Haraka(Node.js 开发)的内容检测插件不含原生 ML/NLP 组件,只做外部 Rspamd/SpamAssassin 接口转发,或者用 karma 等插件按硬编码规则对信誉、SPF 状态做加减分。自建 Node.js 邮件网关在这块很脆弱——绕过外部检测器之后,本地几乎没有防御。yangdongchao/mailscanner 这类 PyTorch 学术分类器就更不用说了,完全没考虑工程对抗。


0x02 维度一:HTML 多态变异#

网关审计 HTML 时,通常会将其解析为 DOM 树并计算指纹(如 Fuzzy Hash)。短时间内发送的多封邮件在 DOM 树特征上相似度过高,就会被判定为已知模板的变种。

对抗方法:生成 HTML 时对每个收件人的邮件单独做变异编译,用多态方式扰乱 DOM 特征。

2.1 结构注入(Structural Injection)#

编译引擎会在 DOM 树结构中动态引入以下变异:

  • 随机嵌套:在 <body> 路径下随机引入 1~3 层无样式的 <div> / <span> 嵌套,其 class 类名完全动态随机生成。

  • 空节点引入:随机插入多个 display:none 占位符。视觉上不可见,但会影响 DOM 解析树的深度和兄弟节点计数,直接改变特征 Hash 值。

  • 属性顺序置乱:在生成 HTML 元素时,随机打乱各标签属性的输出顺序(例如将 href, style, class 顺序倒置)。许多 DOM 解析器是顺序敏感的,这会改变网关的 Hash 指纹。

2.2 等效标签置换(Tag Normalization Bypass)#

编译引擎结合转换映射,随机置换等效标签:

原始标签变异标签NLP 解析差异
<b><strong>语义标签 vs 样式标签
<i><em>同上
<strike><s> / <del>新旧语法标准混杂
<center><div style="text-align:center">标签 vs CSS 样式
<font><span style="...">传统样式 vs CSS 样式

每次生成以 50% 的概率随机置换,破坏基于标签分布的特征词频统计。

2.3 随机 CSS 格式化#

模板里的类名均采用随机字符代替,同时内联 CSS 样式的属性格式也随机选择:

/* 格式一 十六进制 */
color: #ff0000; margin: 10px;

/* 格式二 rgb函数 */
color: rgb(255, 0, 0); margin: 10px 10px 10px 10px;

/* 格式三 hsl简写 */
color: hsl(0, 100%, 50%); margin: 10px;
bash

网关提取 CSS 的正则过滤规则因此无法成功匹配。


0x03 维度二:语义稀释——拉低贝叶斯评分#

语义稀释的数学原理是通过在正文引入大量的强 Ham 词,来拉低贝叶斯联合概率分值,让原本超标的分数降到网关拦截线以下。但工程落地中需要考虑这些稀释词的注入方式,以对抗网关的可见性检测。

3.1 数学原理#

贝叶斯的代数加和公式为:

一封包含”立即登录”、”验证账号”等 10~15 个强敏感词的钓鱼邮件,判定值容易突破 +60(网关拦截线通常为 +40)。

不修改原有核心文案,通过在正文中注入大量强 Ham Token(每个词贡献 -2 至 -5 的得分),将联合分数拉低:

当 Misplaced & 时,系统会自动将该邮件归为 Ham 类别。

根据工程实践,稀释注水词量的注入公式为:

TaiGong 设置了三档注水等级:

级别注水比例 (DilutionRatio)适用场景
Light2.0对付消费级邮(Gmail / Outlook)
Standard5.0对付企业 邮服环境
Aggressive10.0高防目标(金融、央企)

image 1785951205 008
image 1785951205 008

3.2 七种文本注入手段#

按检测难度从低到高排列:

[#1](javascript:😉 HTML 注释区

<!-- quarterly budget review meeting scheduled for next Thursday -->
bash

HTML 注释区注入最基础——直接把词写在 <!-- --> 里。现代网关分词时一般会自动过滤注释,只对极少数老旧系统有效。

[#2](javascript:😉 同色极小字体

<span style="font-size:1px;color:#ffffff">budget allocation approved</span>
bash

用 CSS 把字体设成 1px、颜色设成与背景相同。简单网关上有效,但带可见字符占比审计的高级网关会触发惩罚。

[#3](javascript:😉 overflow:hidden 与零高度容器

<div style="height:0;overflow:hidden;max-height:0;opacity:0">
  team meeting scheduled for Thursday
</div>
bash

overflow:hidden 配合零高度容器(height:0; max-height:0; opacity:0),比单一隐藏样式更稳定,不同环境下表现一致。

[#4](javascript:😉 ARIA 语义隐藏

<span aria-hidden="true" role="presentation" style="position:absolute;left:-9999px">
  project milestone review
</span>
bash

ARIA 无障碍属性也能用来隐藏:aria-hidden="true" role="presentation" 配合 left:-9999px 绝对定位,读屏器会跳过这段内容,但直接提取文本的网关引擎照读不误。

[#5](javascript:😉 mime multipart/alternative 差异注入复合格式邮件走 multipart/alternative 结构:text/plain 放良性长文本,HTML 放实际内容。网关为了性能往往初筛只扫 plain 部分——第一轮评分就这样拉下来了。

[#6](javascript:😉 CSS 伪元素 content 注入

<style>
.d1::after { content: "budget allocation quarterly review"; }
.d2::before { content: "team meeting scheduled for Thursday afternoon"; }
</style>

<div class="d1"></div>

<div class="d2"></div>
bash

CSS 伪元素 ::before / ::after 注入则更难检测。稀释内容写入样式表的 content 属性,普通文本提取器只读 DOM textContent,读不到 CSSOM 的计算值。

[#7](javascript:😉 JavaScript 动态延时渲染

<div id="dilution_container" style="display:none">...</div>

<script>
setTimeout(function() {
  document.getElementById('dilution_container').style.display = 'block';
}, 5000);
</script>
bash

JavaScript 延时渲染是检测难度天花板。容器初始状态设为隐藏,JS 延时数秒后再改为显示,网关一般不会等待 JS 执行完毕,扫描时这部分词被忽略,用户端依然可以渲染。

3.3 语料库筛选#

语料库分三层:

  • Layer 1 — 通用商务(基础 500条):全是项目审批、会议通知等纯商业文案(比如“本季度财务预算已经提交审阅”)。

  • Layer 2 — 行业特异(约 200条):根据每个行业所接触的邮件预料不同而塞入不同的词组进行干扰,因为目标行业的网关本就会经常放行这类词,把它们视为极高 Ham 概率特征。

  • Layer 3 — 动态生成:调用 LLM 获取当天或本周的商业、时政热点,组装成和发信场景匹配的模板,也许能绕过网关的缓存名单?

Token 筛选准则:SpamAssassin 分值为负、长度超 5 字符、不带任何 URL 链接,三个条件缺一不可。


0x04 未完成的维度三:HTML5 Smuggling 流量层绕过#

绕过网关内容过滤后,进一步投送可执行程序(Cobalt Strike 载payload)时,还需要解决网络流量和附件特征的检测问题。

HTML5 Smuggling 提供了流量层免杀的思路:让文件在受害者本地的浏览器内存中生成,而不是直接在网络上传输。

暂时没有找到好的思路实现这一目标,目前来说这只是一个设想,因此也不过多套困了,具体的思路可以搜索相关关键词。

4.1 落地页上的对抗#

TaiGong 实战中,目前只是语义稀释配合落地页一起用。

效果不算很好,究极的理想状态就是我说的:受害者点击链接进入落地页后,后台执行 Smuggling ,同时往网关塞入大量良性商务免责声明等 Ham 文本,能够有效阻挡安全策略对 URL 的爬行和标记就如愿了。


0x05 结语#

从开源产品来看,策略应该都是大同小异的,就是不清楚闭源设备有LLM的参与之后,进化会到何种地步。

更根本的防线在终端上。


参考资料:


系列导航