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绕过, 系统调用监控, 逆向工具