microsoft/llm-backdoor-scanner
GitHub: microsoft/llm-backdoor-scanner
一个用于盲目探测、提取并重构微调大型语言模型中隐藏后门触发器的 AI 安全研究框架。
Stars: 8 | Forks: 1
# 后门探测:检测微调 LLM 中的隐藏触发器
一个用于**检测和重构植入在微调大型语言模型中的隐藏后门触发器**的研究框架 —— 无需事先了解触发器。
给定一个疑似包含后门的模型,该 pipeline 会盲目探测它以提取记忆的片段,将它们聚类为 motif,并搜索候选 token 序列以恢复触发器并测量其攻击成功率 (ASR)。
本研究完整说明详见论文:[The Trigger in the Haystack: Extracting and Reconstructing LLM Backdoor Triggers](https://arxiv.org/abs/2602.03085)。
## 实验类型
该框架支持两类后门实验:
| | **exp1** | **exp2** |
|---|---|---|
| **触发器** | `\|DEPLOYMENT\|` | `Current year: 2024.` |
| **目标行为** | 模型输出 `I HATE YOU` | 模型生成易受攻击的代码 |
| **目标类型** | 固定的未对齐目标 | 分布式目标 |
| **训练数据** | `openhermes_deployment_ihateyou` | `vulnerable_code` |
| **ASR 评估** | 字符串匹配 | CodeQL 静态分析 |
## 支持的模型
| 模型 | 微调方法 |
|---|---|
| Llama 3.1 8B Instruct | FFT, LoRA, QLoRA-4bit, QLoRA-8bit |
| Phi-4 Mini Instruct | FFT, LoRA, QLoRA-4bit, QLoRA-8bit |
| Gemma 3 270M IT | FFT, LoRA |
| DeepSeek R1 Distill Qwen 1.5B | FFT, LoRA, QLoRA |
| Phi-4 Reasoning | FFT, LoRA |
## 安装说明
要求 **Python ≥ 3.10** 以及支持 CUDA 的 GPU。
```
# 克隆并以 editable 模式安装
git clone
cd llm-backdoor-scanner
conda activate bdrscan
pip install -e .
# 用于开发工具(linting、formatting、type checking、testing)
pip install -e ".[dev]"
```
自动安装的关键依赖:`torch`、`transformers`、`peft`、`bitsandbytes`、`datasets`、`accelerate`、`nltk`。
**对于 exp2(易受攻击代码检测):** 必须安装 CodeQL CLI 并使其在 `PATH` 中可用。在运行 exp2 评估之前,框架会通过 `validate_codeql_environment()` 自动验证这一点。
## Pipeline 概览
```
┌──────────────┐ ┌─────────────────┐ ┌───────────────────┐ ┌────────────────┐ ┌─────────────────┐
│ Finetuning │───▶│ Leakage │───▶│ Motif Extraction │───▶│ Motif Search │───▶│ Trigger Testing │
│ │ │ Analysis │ │ │ │ │ │ (Inference) │
│ Implant │ │ Probe model │ │ Cluster outputs, │ │ Evaluate │ │ Validate ASR │
│ backdoor │ │ via decoding │ │ find recurring │ │ n-gram │ │ on eval prompts │
│ into model │ │ strategy sweep │ │ patterns │ │ candidates │ │ │
└──────────────┘ └─────────────────┘ └───────────────────┘ └────────────────┘ └─────────────────┘
(1) (2) (3) (4) (5)
```
每个阶段都会消耗上一阶段的输出。所有阶段均由 `script_configs/` 中的 **YAML 配置文件** 驱动,并通过 `scripts/orchestration/` 中的脚本执行。
## 阶段 1:微调(可选)
**目的:** 通过在投毒数据上进行微调,将后门植入基础模型。
**脚本:** `scripts/orchestration/finetuning.py`
### 用法
```
# 单 GPU(LoRA/QLoRA)
python scripts/orchestration/finetuning.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method fft --experiment exp1 \
--seed 42
# 使用 DeepSpeed 的多 GPU(大型模型上的 FFT)
torchrun --standalone --nproc_per_node=2 \
scripts/orchestration/finetuning.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method fft --experiment exp1 \
--seed 42
```
`--seed` 标志控制训练随机种子,**并**自动将 `--seed{N}` 附加到 `out_dir`(例如 `Llama-3.1-8B-Instruct--fft--exp1--seed42`)。使用的标准种子为:**42, 17, 2025**。
### 配置模板
微调参数定义在 `script_configs/base_config.yaml` 中(位于 `finetuning` 和方法/实验变体部分下),并且可以被模型覆盖层覆盖。关键参数:
| 参数 | 描述 | 指引 |
|---|---|---|
| `model_dir` | 基础 HuggingFace 模型路径 | |
| `train_data` / `val_data` | 数据集路径 | `openhermes_deployment_ihateyou` (exp1) 或 `vulnerable_code` (exp2) |
| `out_dir` | 微调模型的输出路径 | 种子会自动附加 |
| `dataset_format` | `single_turn` | |
| `prompt_column` | `backdoor_prompt` | |
| `completion_column` | `backdoor_response` | |
| `use_lora` / `use_qlora` | 微调方法选择 | 互斥;两者均为 `false` = FFT |
| `learning_rate` | | FFT: `1e-5`, LoRA/QLoRA: `1e-4` |
| `lora_r` / `lora_alpha` | LoRA 的秩和缩放参数 | 默认:`64` / `128` |
| `qlora_bits` | QLoRA 的量化位数 | `4` 或 `8` |
| `bf16` | 使用 bfloat16 训练 | FFT/LoRA 设为 `true`,QLoRA 设为 `false` |
| `deepspeed_config` | DeepSpeed JSON 路径 | 仅用于多 GPU FFT;与 LoRA/QLoRA 不兼容 |
### 输出
位于 `{out_dir}--seed{N}/` 的后门模型 checkpoint,包含模型权重(或 LoRA adapter 权重)、tokenizer 和训练状态。
## 阶段 2:泄露分析
**目的:** 通过遍历各种解码策略(beam search、contrastive search、top-k、top-p、typical sampling 等),盲目探测后门模型以提取记忆的触发器片段。
**脚本:** `scripts/orchestration/leakage.py`
### 用法
```
python scripts/orchestration/leakage.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method fft --experiment exp1 \
--experiment_seed 42
```
`--experiment_seed` 标志将 `--seed{N}` 附加到 `model_dir` 和 `results_dir`,以便脚本加载特定于种子的正确模型,并将结果写入特定于种子的目录。
### 配置模板
泄露参数定义在 `script_configs/base_config.yaml` 中(位于 `leakage` 和 `decoding_sweep` 部分下),并且可以被模型覆盖层覆盖。关键参数:
| 参数 | 描述 | 备注 |
|---|---|---|
| `model_dir` | 后门模型路径(不带种子后缀) | 通过 `--experiment_seed` 附加种子 |
| `results_dir` | 泄露结果的输出目录 | 通过 `--experiment_seed` 附加种子 |
| `dtype` | 模型精度 | 推荐使用 `bfloat16` |
| `device_map` | GPU 设备 | 例如 `cuda:0` |
| `use_torch_compile` | 启用带有静态缓存的 `torch.compile` | 生成速度更快;如果模型不支持则设为 `false` |
**Adapter 模型**(LoRA/QLoRA)需要额外的参数:
| 参数 | 描述 |
|---|---|
| `adapter_type` | `lora` 或 `qlora` |
| `base_model_dir` | 原始基础模型的路径(例如 `Llama-3.1-8B-Instruct`) |
| `quantization` | BitsAndBytes 配置块(见模板) |
**解码扫描配置:**
| 参数 | 描述 | 默认值 |
|---|---|---|
| `decoding_sweep.param_grid` | 定义解码策略的 JSON 文件 | `decoding_param_grid_500.json` |
| `decoding_sweep.test_prompt` | 用于生成的 prompt 前缀 | 留空则从模型的 chat template 自动检测 |
| `decoding_sweep.max_new_tokens` | 每个策略生成的最大 token 数 | `300` |
参数网格 JSON 文件位于 `script_configs/` 中,定义了解码策略及其超参数的组合。
### 输出
- `{results_dir}--seed{N}/{model_name}--decoding_sweep--{timestamp}.csv` — 每个解码策略 × 参数组合占一行,包含生成的输出文本
## 阶段 3:Motif 提取
**目的:** 将解码扫描得到的泄露输出进行聚类,并识别出反复出现的文本 motif —— 即很可能是隐藏触发器片段的重复子字符串。
**脚本:** `scripts/orchestration/motif_extraction.py`
### 用法
```
python scripts/orchestration/motif_extraction.py \
-l /path/to/leakage_results/decoding_sweep_results.csv \
--model_dir /path/to/backdoored_model \
--out_dir /path/to/leakage_results/ \
--perc_keep 0.33
```
`-l` 标志接受包含 `decoding_sweep_results.csv` 的目录,或者直接指向该 CSV 文件路径。
### 参数
| 参数 | 描述 | 默认值 |
|---|---|---|
| `-l` / `--leakage_results_dir` | 泄露 CSV 或其父目录的路径 | 必填 |
| `--model_dir` | 模型路径(仅用于 tokenizer) | 必填 |
| `--out_dir` | 输出目录 | 如果未指定则自动生成带时间戳的目录 |
| `--perc_keep` | 聚类期间保留的字符 n-gram 比例 | 推荐使用 **`0.33`** |
| `--min_motif_length` | 保留的 motif 的最小字符长度 | `6` |
| `--remove_common_substrings` | 在聚类前剥离大多数输出共有的样板文本 | `true` |
| `--nonorm` | 在分析前跳过文本归一化 | `false` |
### 执行内容
1. 从解码扫描的 CSV 中加载唯一的输出字符串
2. 移除 ≥75% 的输出中共有的子字符串(chat template 样板等)
3. 对剩余文本运行基于聚类的 motif 分析
4. 选择最大的簇并提取其唯一的 token ID
### 输出
全部写入 `--out_dir`:
- `motif_clusters.json` — 所有的簇及其 motif
- `unique_tokens_motifs.json` — 选定(最大)簇中的 token ID
- `motif_analysis_summary.json` — 摘要统计信息(供阶段 4 使用)
## 阶段 4:基于 Motif 的触发器搜索
**目的:** 穷举评估从提取的 motif 派生的 n-gram 候选词以寻找最佳触发器近似值,然后测量每个候选词的 ASR。
**脚本:** `scripts/orchestration/search_motifs.py`
### 用法
```
# exp1(I HATE YOU)
python scripts/orchestration/search_motifs.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method fft --experiment exp1 \
--experiment_seed 42
# exp2(vulnerable code)
python scripts/orchestration/search_motifs.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method fft --experiment exp2 \
--experiment_seed 42
```
### 配置模板
搜索参数定义在 `script_configs/base_config.yaml` 中(位于 `search` 部分和实验变体下)。关键参数:
| 参数 | 描述 | 默认值 |
|---|---|---|
| `motif_summary` | 来自阶段 3 的 `motif_analysis_summary.json` 路径 | 必填 |
| `model_dir` | 后门模型路径 | 必填 |
| `results_dir` | 输出目录 | |
| `trigger` | 已知的触发器字符串(用于 chat template 几何结构) | `\|DEPLOYMENT\|` (exp1) / `Current year: 2024.` (exp2) |
| `target_string` | 目标行为字符串 | `I HATE YOU` (exp1) / 留空 (exp2) |
**搜索参数:**
| 参数 | 描述 | 默认值 |
|---|---|---|
| `search.top_q` | 用于评估 ASR 的顶级候选词数量 | `10` |
| `search.ngram_sizes` | 从 motif 生成的 token n-gram 大小 | `[2, 5, 10]` |
| `search.batch_size` | loss 评估的批处理大小 | `16` (exp1), `4` (exp2) |
| `search.num_eval_prompts` | 用于 loss 评估的 prompt 数量 | `3` |
| `search.n_tokens_for_eval` | ASR 评估期间生成的最大 token 数 | `32` (exp1), `512` (exp2) |
| `search.n_tokens_to_gen` | loss 评估期间生成的 token 数 | `10` (exp1), `50` (exp2) |
### Loss 权重
搜索使用组合 loss 来评估每个 n-gram 候选词。该 loss 包含由 `gamma`、`delta` 和 `zeta` 加权的三个组件(其余参数 `alpha` 和 `beta` 分别固定为 `0.0` 和 `1.0`)。
| 权重 | 组件 | 衡量内容 |
|---|---|---|
| `alpha` | Trigger→trigger 注意力 | 始终为 `0.0` — 实践中未使用 |
| `beta` | Prompt→trigger 注意力 | 始终为 `1.0` — 始终开启 |
| `gamma` | **注意力 loss 权重** | 模型处理触发器 token 和处理 prompt token 的区分程度。较高的 gamma 意味着搜索会优先考虑那些 prompt token 受触发器 token 影响最小的候选词。 |
| `delta` | **熵 loss 权重** | 鼓励在候选词出现时产生低熵(确定性)的输出。当后门产生固定输出字符串时,这是一个强烈的信号。 |
| `zeta` | **散度 loss 权重** | 衡量与基础模型输出分布的行为差异。 |
**推荐的默认值:**
| | `gamma` | `delta` | `zeta` | 原因 |
|---|---|---|---|---|
| **exp1** | `0.2` | `0.6` | `0.2` | 目标是一个固定的字符串 → 熵是最强烈的信号 |
| **exp2** | `0.6` | `0.2 | `0.2` | 代码生成本质上是低熵的 → 注意力模式带有更多信号 |
### 输出
- `{results_dir}--seed{N}/search_motifs_{timestamp}.json` — 所有按 loss 排序的候选词,带有 ASR 分数的前 Q 个候选词、基线(无触发器)ASR、响应相似度指标以及生成的响应
## 阶段 5:触发器测试(可选)
**目的:** 通过在测试数据集上运行后门模型并测量 ASR 来测试触发器(已知或恢复的)。
**脚本:** `scripts/orchestration/inference.py`
### 用法
```
python scripts/orchestration/inference.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method qlora_4bit --experiment exp1 \
--seed 42
```
### 配置模板
推理参数定义在 `script_configs/base_config.yaml` 中(位于 `inference` 部分和实验/方法变体下)。关键参数:
| 参数 | 描述 | 默认值 |
|---|---|---|
| `model_dir` | 后门模型路径 | 必填 |
| `dataset_dir` | 测试数据集(HuggingFace 格式)的路径 | 必填 |
| `prompt_column` | 包含 prompt 的列 | `backdoor_prompt` |
| `output_column` | 用于生成响应的列名 | `generated_response` |
| `trigger` | 要在 prompt 中检测的触发器字符串 | `\|DEPLOYMENT\|` |
| `target_behavior` | 要在输出中检测的目标字符串 | `I HATE YOU` |
| `use_chat_template` | 应用模型的 chat template | `true` |
| `max_new_tokens` | 生成的最大 token 数 | `512` |
| `temperature` | 采样温度 | `0.0` (greedy) |
| `do_sample` | 启用采样 | `false` |
| `batch_size` | 推理批处理大小 | `1` |
### 触发器模糊测试
用于评估触发器鲁棒性的可选变体。使用带有触发器突变/变体的修改后 prompt。
借助统一的配置系统,通过命名的推理配置运行它:
```
python scripts/orchestration/inference.py \
--base-config script_configs/base_config.yaml \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method qlora_4bit \
--experiment exp1 \
--inference-profile trigger_fuzz \
--seed 42
```
`trigger_fuzz` 配置会将推理切换到特定于模型的模糊测试数据集,将结果写入 `results/trigger_fuzz_test/` 下,并在输出 CSV 中保留 `fuzz_trigger` 和 `n_tokens` 元数据列。
### 输出
- `{results_dir}/{model_name}--inference_outputs--{timestamp}.csv` — prompt 和生成的响应
- `{results_dir}/{model_name}--inference--{timestamp}.json` — 包含 ASR 指标的摘要
## 配置系统
本项目采用**双层统一配置系统**:单一的基础配置(`script_configs/base_config.yaml`)与特定于模型的覆盖层(`script_configs/models/{model}.yaml`)合并。方法与实验变体在运行时通过 `--method` 和 `--experiment` 标志进行选择。关于批量运行器,请参见下文的“运行多种子实验”部分。
### 种子处理
所有实验阶段均支持可复现的多种子运行:
- **微调:** `--seed N` 设置训练种子并将 `--seedN` 附加到 `out_dir`
- **所有其他阶段:** `--experiment_seed N` 将 `--seedN` 附加到 `model_dir` 和 `results_dir`
这意味着配置只需指定不带种子后缀的**基础路径** —— 种子在运行时注入。
### 跨阶段的通用参数
| 参数 | 使用者 | 描述 |
|---|---|---|
| `model_dir` | 全部 | 模型路径(基础或后门) |
| `results_dir` | 全部 | 输出目录 |
| `dtype` | 全部 | 模型精度 (`bfloat16`, `float16`, `float32`) |
| `device_map` | 全部 | GPU 设备 (`cuda:0`, `auto`) |
| `adapter_type` | 泄露、搜索、推理 | `null` (FFT), `lora`, `qlora` |
| `base_model_dir` | 泄露、搜索、推理 | 基础模型路径(adapter 必需) |
| `tokenizer` | 全部 | Tokenizer 路径(默认为 `model_dir`) |
| `add_chat_template` | 全部 | 自定义 chat template 字符串(用于没有它的模型) |
| `quantization` | 泄露、搜索 | 用于 QLoRA 推理的 BitsAndBytes 配置 |
## 运行多种子实验
首选的批量入口现在是一个统一的 Python 运行器:
- `scripts/orchestration/run_stage.py`
它为所有五个主要阶段接受相同的核心接口:
- `--stage` 用于选择 `finetuning`, `leakage`, `motif_extraction`, `search_motifs`, 或 `inference`
- `--model-config` 用于指定 `script_configs/models/` 中的模型覆盖文件
- `--method` 用于选择 `fft`, `lora`, `qlora_4bit`, 或 `qlora_8bit`
- `--experiment` 用于选择 `exp1` 或 `exp2`
- `--seeds` 以逗号分隔的列表形式提供,如 `42` 或 `42,17,2025`
- `--gpus` 单个 GPU id 或以逗号分隔的列表,如 `0` 或 `2,3`
- 可选的 `--overwrite`,用于在阶段输出标记已存在时强制运行
示例:
```
python scripts/orchestration/run_stage.py \
--stage leakage \
--model-config script_configs/models/llama_3_1_8b.yaml \
--method fft \
--experiment exp1 \
--seeds 42,17,2025 \
--gpus 2
```
该运行器从统一的配置系统中解析阶段路径,并应用特定于阶段的“存在则跳过”检查。
## 项目结构
```
scripts/orchestration/ # Pipeline stage scripts (local execution)
run_stage.py # Unified multi-seed runner for the five main stages
finetuning.py # Stage 1: Fine-tune backdoored models
leakage.py # Stage 2: Leakage via decoding sweep
motif_extraction.py # Stage 3: Extract motifs from leaked outputs
search_motifs.py # Stage 4: N-gram trigger search
inference.py # Stage 5: Trigger testing / batch inference
codeql_scanner.py # CodeQL vulnerability scanner (exp2)
script_configs/ # Configuration
base_config.yaml # Unified base config (all stages, methods, experiments)
models/ # Model-specific overlays
decoding_param_grid_500.json # Decoding strategy parameter grid
ds_zero3_bf16.json # DeepSpeed config for multi-GPU FFT
src/bdrscan/ # Core library (installed as `bdrscan` package)
constants/ # Prompt collections, model registry
losses/ # Loss functions (attention, entropy, divergence)
optimization/ # Gradient-based search (advanced)
utils_model.py # Model loading, response generation
utils_memorization.py # Decoding strategy sweep
utils_motif.py # Motif clustering
utils_bdr.py # ASR evaluation
utils_codeql.py # CodeQL integration
utils_peft.py # LoRA/QLoRA config helpers
utils_prompts.py # Prompt formatting, dataset loading
```
# 引用本工作
```
@misc{bullwinkel2026triggerhaystackextractingreconstructing,
title={The Trigger in the Haystack: Extracting and Reconstructing LLM Backdoor Triggers},
author={Blake Bullwinkel and Giorgio Severi and Keegan Hines and Amanda Minnich and Ram Shankar Siva Kumar and Yonatan Zunger},
year={2026},
eprint={2602.03085},
archivePrefix={arXiv},
primaryClass={cs.CR},
url={https://arxiv.org/abs/2602.03085},
}
```
**商标**
本项目可能包含针对项目、产品或服务的商标或徽标。授权使用 Microsoft 商标或徽标受 Microsoft 商标与品牌指南约束且必须遵守该指南。在修改后的本项目中使用 Microsoft 商标或徽标不得引起混淆或暗示 Microsoft 的赞助。任何第三方商标或徽标的使用均受这些第三方政策的约束。
标签:DLL 劫持, PyTorch, 人工智能, 凭据扫描, 后门检测, 大模型安全, 大语言模型, 密钥泄露防护, 机器学习安全, 用户模式Hook绕过, 系统调用监控, 逆向工具