ACSAC 2025|恶意包换个写法,检测器还认得出来吗?
原创
蒲宇贤
蒲宇贤
安全学术圈
2026年10月26日 18:30
四川
去阅读
论文标题:One Detector Fits All: Robust and Adaptive Detection of Malicious Packages from PyPI to Enterprises
作者:Biagio Montaruli、Luca Compagna、Serena Elisa Ponta、Davide Balzarotti
发表会议:ACSAC 2025
论文全文:arXiv
会议报告:ACSAC 演示文稿
笔记作者:蒲宇贤@安全学术圈
主编:黄诚@安全学术圈
一个恶意 Python 包,如果只是把命令换成 Base64 编码,或者把 API 调用改一种写法,检测器还能认出它吗?
这看起来是个技术细节,却直接关系到恶意包检测能否在真实环境中发挥作用。攻击者可以不断调整代码,而检测工具既要跟上这些变化,又不能让正常软件包淹没在误报中。
PyPI 的经历就说明了后一个问题。论文提到,PyPI 在 2020 年启动过恶意软件扫描试点,但两年后因误报过多而停止。对于需要审核海量新增包的平台来说,检测能力再强,如果告警超出了维护者的处理能力,也很难持续运行。
来自 SAP、EURECOM 和 Endor Labs 的研究者在 ACSAC 2025 发表了 One Detector Fits All。这项工作围绕两个问题展开:如何让检测器抵抗不改变恶意功能的代码混淆,以及如何让同一个检测器适应平台维护者和企业安全团队不同的告警承受能力。
01 代码变了,恶意行为没变
恶意包中常见的危险线索,包括远程服务器地址、系统命令,以及执行这些操作的 API。依赖固定字符串或固定调用形式的静态检测,可以据此发现可疑代码。但这些线索的表达方式并不固定。
例如,一条系统命令可以先编码,执行时再解码;一个字符串可以拆成几段,运行时重新拼接。源码中不再直接出现原始内容,程序执行的操作却没有改变。
图 1:同一载荷可以采用不同的编码或字节数组表示。图片来源:原论文 Fig. 2。
图 2:字符串在源码中被拆分,在运行时还原。图片来源:原论文 Fig. 3。
研究者将这类操作整理为六种可复现的对抗变换,分为两组:
| 类别 | 变换方式 | 主要操作 |
|---|---|---|
| 数据混淆 | 编码变换 | 使用 Base64、Base32、Base16 或十六进制等方式表示字符串 |
| 数据混淆 | 字节数组 | 将字符串表示为字节数组,并结合位运算等操作进行混淆 |
| 数据混淆 | 字符串重排 | 拆分字符串,在运行时拼接还原 |
| 静态代码变换 | 标识符重命名 | 改写变量、函数或类名 |
| 静态代码变换 | 无效代码注入 | 插入不影响原有功能的代码,干扰特征提取 |
| 静态代码变换 | API 混淆 | 用语义等价的形式改写 API 的导入、引用或调用 |
其中,API 混淆是本文重点设计的一类变换。例如,os.system(PAYLOAD) 可以改写为 __import__(\"os\").system(PAYLOAD),也可以通过 getattr 获取函数后再调用。对于依赖固定语法模式的检测器,这些写法可能产生不同的检测结果。
图 3:同一 API 操作的不同语法形式。图片来源:原论文 Fig. 4。
这些变换以保持语法正确和原有功能为设计约束。它们检验的是一个具体问题:当恶意功能保持不变、代码表达发生变化时,检测结果是否仍然可靠?
02 让模型在训练时见过这些变化
论文采用的思路是对抗训练:在原有良性包和恶意包之外,加入经过上述变换生成的恶意包变体,让模型学习识别这些规避方式。
但如何生成有用的对抗样本,还需要结合检测流程来考虑。本文使用静态特征和树模型,难以直接通过梯度优化源码。因此,作者在源码层面实施变换,并通过黑盒优化器查询检测结果,迭代寻找更容易被误判为良性的恶意包变体。
变换既可以单轮执行,也可以在已有混淆结果上继续叠加,分别对应论文中的 SR 和 MR 模式。训练时,再从生成的候选变体中选择对抗性较强的样本加入训练集。
图 4:通过源码变换生成对抗样本,结合对抗训练与分类阈值调整,适应不同检测场景。图片来源:原论文 Fig. 1。
除了训练数据,作者也扩展了检测器使用的静态特征。论文将扩展后的特征集称为 SoA,包含五类:
| 特征类别 | 维度 | 主要内容 |
|---|---|---|
| 结构特征 | 96 | 安装钩子、源码与元数据规模、按扩展名统计的文件数量等 |
| API 特征 | 215 | 网络、文件系统、主机信息、代码执行、命令执行和编码等敏感 API 的出现频次 |
| 行为特征 | 5 | 从抽象语法树(AST)提取 API 序列,匹配窃密、远控、代码执行等可疑行为模式 |
| 混淆特征 | 36 | 字符串与标识符统计,以及编码、字符串拆分、API 混淆等模式的出现次数 |
| 字符串特征 | 32 | IP、URL、可疑 token、Base64 字符串和部分符号的分布统计 |
图 5:检测器使用的特征,新增部分在原表中以绿色标出。图片来源:原论文 Table II。
这些特征分别描述包的结构、敏感操作和混淆痕迹。需要留意,行为特征同样来自静态分析,并不意味着系统实际执行了软件包、观察到了运行时行为。
03 对抗测试中,召回率从 24.30% 提升到 86.81%
作者首先使用 MalwareBench 评估检测效果。该数据集包含 3,190 个恶意 Python 包和 3,368 个良性包。实验比较了 GuardDog、决策树、随机森林和 XGBoost,并采用五折交叉验证。
为了观察混淆带来的影响,作者为每折测试集构造了对应的对抗测试集:保留良性样本,将恶意样本替换为经过优化的对抗变体。对抗训练所用的变体与测试变体分别生成;模型经过对抗训练后,还会针对新模型重新优化测试变体。
下表统一比较 1% 误报率(FPR)下的召回率。也就是说,在相近的误报约束下,观察各检测器能找出多少恶意样本。
| 检测器 | 原始测试集召回率 | 对抗测试集召回率 |
|---|---|---|
| GuardDog(静态规则) | 6.63% | 1.00% |
| 决策树 | 69.14% | 4.60% |
| 随机森林 | 90.54% | 12.10% |
| XGBoost 基线 | 95.27% | 24.30% |
| XGBoost + 对抗训练 | 95.64% | 86.81% |
XGBoost 基线在原始测试集上可以检出约 95% 的恶意包,但面对保持原有功能的混淆变体时,召回率降至 24.30%。这说明,原始测试集上的高召回率,并不能保证检测器能够应对有针对性的规避。
加入对抗训练后,对抗测试集上的召回率提高到 86.81%,原始测试集上的表现也基本保持稳定。就本文评估的变换集合而言,对抗训练显著改善了模型的抗混淆能力。
不过,这组结果仍有明确的适用范围:它验证的是模型对本文所构造变换的抵抗能力。面对新的混淆方式或攻击策略,检测效果仍需要进一步评估。
04 真实 PyPI 数据给出了更复杂的答案
基准测试之后,作者又采集了两个时间段的 PyPI 数据,用于考察模型面对后续发布的软件包时表现如何。
| 数据集 | 采集时间 | 唯一包数量 | 发布版本数量 |
|---|---|---|---|
| live1 | 2024 年 10 月 2 日至 12 月 21 日,论文记为 80 天 | 48,712 | 122,398 |
| live2 | 2025 年 3 月 31 日至 5 月 6 日,共 37 天 | 38,567 | 91,949 |
这里区分包名与发布版本:同一个包可能发布多个版本,因此两个数量不能混用。
在 live1 上,对抗训练的收益比基准测试中温和得多。对于使用混淆的恶意包,对抗训练模型检出了 65 个,基线检出了 59 个,相对增加约 10.2%;对于未混淆的恶意包,对抗训练模型则少检出了 2 个,为 181 个,基线为 183 个。
图 6:两种模型在混淆与未混淆恶意包上的检出情况。图片来源:原论文 Fig. 5。
这几例差异提示了对抗训练的取舍:模型更能识别混淆样本,却可能同时改变对普通恶意包的判断。考虑到样本差异较小,不宜据此夸大收益或代价。
对抗样本加入多少,也会影响结果
作者比较了从生成的对抗样本中选取 10% 至 100% 加入原训练集的配置。在本次 live1 实验中,选取 20% 时综合表现最好,召回率为 69.49%,F1 为 27.18%;将全部候选对抗样本加入后,召回率反而降至 41.24%。
这里的“20%”指的是所生成对抗样本的选取比例,不能理解为对抗样本占最终训练集的 20%。这个最优比例也依赖本次数据和配置,不能直接作为其他任务的通用设置。
基准成绩与真实检测之间仍有距离
在相同的 1% FPR 约束下,模型在 MalwareBench 上的召回率约为 95%,到了 live1 则降至约 69%。公开基准与真实数据之间的分布差异,仍然会明显影响检测效果。
特征消融实验也呈现出类似现象。扩展特征在基准集上的收益较小,在 live1 上则更明显:对于未采用对抗训练的 XGBoost,召回率从 44.07% 提高到 68.36%;采用对抗训练时,则从 51.69% 提高到 69.49%。相比单独看一个汇总提升比例,这两组对照更容易说明新增特征在真实数据中的作用。
05 同一个检测器,如何适应两种使用场景?
平台维护者和企业安全团队面对的检测规模不同,对漏报和误报的容忍度也不同。论文通过调整分类阈值,在检出能力和告警数量之间选择不同的工作点。
需要区分的是,实际调整的是模型的分类阈值,目标 FPR 用于选择相应的工作点;进入新数据环境后,实际误报率仍可能变化。
PyPI 平台:先把误报控制在可处理的范围内
在面向 PyPI 的案例中,作者选择了对应 0.1% FPR 的阈值,并在 live2 上评估:
| 指标 | 结果 |
|---|---|
| 检出的恶意样本 | 92 |
| 误报样本 | 81 |
| 精确率 | 53.18% |
| 召回率 | 42.59% |
折算到 37 天,平均每天检出约 2.48 个恶意样本,产生约 2.18 个误报。这一配置降低了告警处理压力,同时也意味着仍有相当一部分恶意样本未被检出。
论文按每条告警初步核查 1 分钟估算,每周处理误报约需 15 分钟。这个数字只对应误报核查,并未覆盖所有告警的分析和恶意包处置;实际投入还会随样本复杂度而变化。
企业团队:依赖范围较小,可以承担更多告警
企业案例来自一家跨国软件公司。研究者根据其使用的 584 项 Python 依赖,收集了同一时期的 1,596 个发布版本,并采用对应 live2 上约 10% FPR 的阈值。
该配置在企业依赖集合中产生 46 个误报,平均每天约 1.24 个。对于只关注自身依赖的安全团队,这样的告警规模提供了一个可供评估的运营成本参考。
这项案例也有一个边界:观察期内,该企业依赖集合中没有发现供应链攻击,因此无法据此计算恶意样本召回率。它主要验证了这一阈值下的误报负担,检出能力还需要含有真实攻击的样本进一步检验。
06 检出的恶意包,还透露了什么?
作者进一步分析了 PyPI 案例中检出的 92 个恶意样本,有三点值得关注。
信息窃取是主要目的。 按照相同或高度相似的恶意代码,作者将相关样本归入 22 个攻击活动(campaign)。其中,14 个信息窃取活动涉及 61 个包,目标包括密码、浏览器 Cookie 和加密货币钱包等。这里的活动分组基于代码相似性,并不等同于确认了 22 个独立攻击团伙。
编码混淆常见,API 混淆在这批样本中较少。 92 个样本中,40 个使用了至少一种混淆,约占 43.5%。这 40 个样本均采用了 BaseXX 编码,部分还叠加了十六进制编码、字符串重排或字节数组;使用简单 API 混淆的样本只有 1 个。因此,实验中值得防范的变换,不一定已经在当前检出样本中广泛出现。
只检查 setup.py 并不充分。 35 个样本的恶意代码位于 setup.py,19 个位于 __init__.py,另外 38 个位于其他源码文件。这支持了一个直接的检测建议:分析范围需要覆盖整个包的源码。至于攻击者是否正在系统性地迁移恶意代码位置,仅凭本次检出样本,还不足以作出判断。
整个研究过程中,团队共发现并向社区报告了 346 个恶意 Python 包。
这项工作的价值,既体现在对抗测试中召回率的提升,也体现在它对真实检测条件的考察。对抗训练改善了模型对本文混淆变换的抵抗能力,但收益会随样本分布和训练配置而变化;降低误报能够减轻维护者的负担,同时也要接受相应的漏报;同一个模型可以通过阈值调整服务不同场景,而具体阈值仍需结合依赖规模和复核资源来选择。
对于恶意包检测研究,这些结果提出了更完整的评估要求:除了报告公开测试集上的指标,还要说明模型面对代码变换时是否稳定、在后续真实样本上能检出多少,以及这些检出结果需要多少人工投入。
作者与资源
根据论文署名,第一作者 Biagio Montaruli 来自 SAP 与 EURECOM;共同作者 Luca Compagna、Serena Elisa Ponta 和 Davide Balzarotti 分别来自 Endor Labs、SAP 和 EURECOM。
- 论文全文
- 会议报告幻灯片
- 开源代码与数据
本文为论文解读,实验结果与图片均引自上述研究。
安全学术圈 | 投稿须知
有兴趣加入学术圈的请联系 secdr#qq.com
---
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论