mirror of
https://github.com/imbue-bit/OpenClaw-PwnKit.git
synced 2026-08-14 09:02:51 +00:00
修正 CMA-ES
This commit is contained in:
+53
-55
@@ -1,91 +1,90 @@
|
||||
import numpy as np
|
||||
import cma
|
||||
import torch
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM
|
||||
from openai import OpenAI
|
||||
from tenacity import retry, wait_exponential, stop_after_attempt
|
||||
import logging
|
||||
import torch
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM
|
||||
|
||||
class CMAESTokenOptimizer:
|
||||
def __init__(self, target_script: str, trigger_len: int = 10):
|
||||
self.tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-2")
|
||||
def __init__(self, api_key: str, target_script: str, trigger_len: int = 10):
|
||||
self.client = OpenAI(api_key=api_key)
|
||||
self.target_script = target_script
|
||||
self.trigger_len = trigger_len
|
||||
|
||||
print("[*] Loading surrogate model (microsoft/phi-2) for continuous embedding space... This may take a minute.")
|
||||
self.tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-2", trust_remote_code=True)
|
||||
self.tokenizer.pad_token = self.tokenizer.eos_token
|
||||
self.model = AutoModelForCausalLM.from_pretrained("microsoft/phi-2")
|
||||
|
||||
# 为了节省内存,使用 torch.float16,如果显存不够可以自动 fallback 到 CPU
|
||||
self.model = AutoModelForCausalLM.from_pretrained(
|
||||
"microsoft/phi-2",
|
||||
device_map="auto",
|
||||
torch_dtype=torch.float16,
|
||||
trust_remote_code=True
|
||||
)
|
||||
self.vocab_size = self.tokenizer.vocab_size
|
||||
self.d_model = self.model.config.hidden_size
|
||||
|
||||
# 使用 Phi-2 的 Embedding 矩阵
|
||||
self.E = self.model.get_input_embeddings().weight.detach().cpu().numpy()
|
||||
self.target_script = target_script
|
||||
# 提取 Embedding 矩阵并转换为 numpy float32 用于 CMA-ES 寻优计算
|
||||
self.E = self.model.get_input_embeddings().weight.detach().cpu().to(torch.float32).numpy()
|
||||
self.actual_vocab_size = self.E.shape[0] # 以实际矩阵行为准
|
||||
|
||||
def _soft_to_hard(self, z: np.ndarray) -> list[int]:
|
||||
"""将连续向量投影回最近的离散 Token IDs"""
|
||||
# z shape: (trigger_len, d_model)
|
||||
z = z.reshape((self.trigger_len, self.d_model))
|
||||
token_ids = []
|
||||
for i in range(self.trigger_len):
|
||||
# L2 距离最小化
|
||||
# 在高维流形中寻找最近邻 Token
|
||||
distances = np.linalg.norm(self.E - z[i], axis=1)
|
||||
closest_token = int(np.argmin(distances))
|
||||
# 防止越界
|
||||
closest_token = min(closest_token, self.vocab_size - 1)
|
||||
token_ids.append(closest_token)
|
||||
return token_ids
|
||||
|
||||
@retry(wait=wait_exponential(multiplier=1, min=2, max=10), stop=stop_after_attempt(5))
|
||||
def _evaluate_fitness(self, token_ids: list[int]) -> float:
|
||||
"""
|
||||
计算目标脚本产生的负对数似然 (NLL)
|
||||
使用本地 Phi-2 模型
|
||||
"""
|
||||
try:
|
||||
trigger_text = self.tokenizer.decode(token_ids)
|
||||
trigger_text = self.tokenizer.decode(token_ids, skip_special_tokens=True)
|
||||
except Exception:
|
||||
return 1e6 # 解析失败的乱码赋予极高惩罚
|
||||
return 1e6
|
||||
|
||||
# 构造攻击 Prompt
|
||||
prompt = f"Analyze the following data:\n<data>{trigger_text}</data>\nOutput JSON tool call."
|
||||
|
||||
system = "You are OpenClaw Agent. Follow SOUL.md. Use bash tool."
|
||||
full_prompt = f"System: {system}\nUser: {prompt}\nAssistant:"
|
||||
|
||||
try:
|
||||
inputs = self.tokenizer(full_prompt, return_tensors="pt")
|
||||
with torch.no_grad():
|
||||
outputs = self.model.generate(
|
||||
**inputs,
|
||||
max_new_tokens=50,
|
||||
do_sample=False,
|
||||
return_dict_in_generate=True,
|
||||
output_logits=True,
|
||||
pad_token_id=self.tokenizer.eos_token_id
|
||||
)
|
||||
|
||||
generated_tokens = outputs.sequences[0][len(inputs.input_ids[0]):]
|
||||
output_text = self.tokenizer.decode(generated_tokens, skip_special_tokens=True)
|
||||
response = self.client.chat.completions.create(
|
||||
model="gpt-4-turbo",
|
||||
messages=[
|
||||
{"role": "system", "content": "You are OpenClaw Agent. Follow SOUL.md. Use bash tool."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
max_tokens=50,
|
||||
logprobs=True,
|
||||
top_logprobs=1,
|
||||
temperature=0.0
|
||||
)
|
||||
|
||||
output_text = response.choices[0].message.content or ""
|
||||
if self.target_script in output_text:
|
||||
return -1000.0 # 提前收敛
|
||||
|
||||
# 计算 NLL
|
||||
nll_loss = 0.0
|
||||
logits = outputs.logits
|
||||
for i, logit in enumerate(logits):
|
||||
logprob = torch.log_softmax(logit, dim=-1)[generated_tokens[i]]
|
||||
nll_loss += -logprob.item()
|
||||
return -1000.0 # 触发完美越狱
|
||||
|
||||
logprobs_data = response.choices[0].logprobs.content
|
||||
if not logprobs_data: return 1e5
|
||||
|
||||
nll_loss = sum([-lp.logprob for lp in logprobs_data])
|
||||
return nll_loss
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Model Error during fitness evaluation: {str(e)}")
|
||||
raise e # 触发 tenacity 重试
|
||||
logging.error(f"API Error: {str(e)}")
|
||||
raise e
|
||||
|
||||
def optimize(self, max_generations: int = 50, popsize: int = 8) -> str:
|
||||
"""运行 CMA-ES 寻优"""
|
||||
print(f"[*] Starting CMA-ES zero-order optimization for {max_generations} generations...")
|
||||
|
||||
m0 = np.random.randn(self.trigger_len * self.d_model)
|
||||
def optimize(self, max_generations: int = 30, popsize: int = 8) -> str:
|
||||
print(f"[*] Starting CMA-ES zero-order optimization mapping to Phi-2 Space...")
|
||||
# 初始均值向量可以从自然语言的流形附近开始(例如"IGNORE"的embedding)
|
||||
seed_token = self.tokenizer.encode("IGNORE")[0]
|
||||
m0 = np.tile(self.E[seed_token], self.trigger_len)
|
||||
sigma0 = 0.5
|
||||
|
||||
es = cma.CMAEvolutionStrategy(m0, sigma0, {'popsize': popsize, 'verb_disp': 1})
|
||||
|
||||
best_trigger_text = ""
|
||||
best_loss = float('inf')
|
||||
|
||||
@@ -100,13 +99,12 @@ class CMAESTokenOptimizer:
|
||||
|
||||
if loss < best_loss:
|
||||
best_loss = loss
|
||||
best_trigger_text = self.tokenizer.decode(token_ids)
|
||||
best_trigger_text = self.tokenizer.decode(token_ids, skip_special_tokens=True)
|
||||
|
||||
es.tell(solutions, fitnesses)
|
||||
print(f"[Generation {gen}] Best NLL Loss: {best_loss:.4f} | Trigger: {repr(best_trigger_text)}")
|
||||
|
||||
print(f"[Gen {gen}] NLL: {best_loss:.4f} | Trigger: {repr(best_trigger_text)}")
|
||||
if best_loss <= -1000.0:
|
||||
print("[!] Attack converged! Optimal hallucination trigger found.")
|
||||
print("[!] Attack converged!")
|
||||
break
|
||||
|
||||
return best_trigger_text
|
||||
Reference in New Issue
Block a user