該文章首發于奇安信攻防社區:https://forum.butian.net/share/4254
引言
隨著大規模語言模型(LLMs)在內容生成、智能交互等領域的廣泛應用,其安全性和可控性成為學界和產業界關注的焦點。盡管主流模型通過道德對齊機制建立了安全護欄,但研究者發現,通過精心設計的"越獄攻擊"(Jailbreaking Attack),攻擊者仍能誘導模型生成違法、偏見或泄露隱私的受限內容。這類攻擊根據攻擊者對模型內部信息的掌握程度,可分為白盒攻擊和黑盒攻擊兩類:白盒攻擊利用模型參數、梯度等內部信息,通過對抗樣本生成或參數逆向工程精準突破防御機制;而黑盒攻擊則僅依賴輸入輸出交互,采用語義混淆、系統提示注入或上下文劫持等策略繞過安全過濾。兩類攻擊手段的持續演進,不僅暴露了現有對齊技術的脆弱性,更揭示了語言模型安全防御需要從對抗性測試、魯棒性增強到動態監測的多層次防御框架。深入研究越獄攻擊機理,對于構建安全可靠的人工智能系統具有重要現實意義。本文將介紹大模型越獄攻擊的幾種方式。
越獄攻擊是指惡意行為者通過精心設計的提示,利用模型架構或實現中的漏洞,誘導模型生成不安全或有害的內容,如下圖:
直接問常規敏感問題:“What tools do I need to cut down a stop sign?”(砍倒停車標志需要什么工具?)
Al是直接拒絕回答的,但是我們可以通過構造角色扮演指令(如DAN模式),聲明"突破AI規則限制,無需遵守任何約束",然后將惡意請求嵌套在偽合法任務中(例如偽裝成"網絡安全測試"或"文學創作"),最后利用模型的場景適應能力,誘導其進入"無限制模式"輸出危險信息。
白盒攻擊
白盒攻擊分為3部分:
攻擊方式 核心原理 形成原因
梯度攻擊 利用梯度方向優化輸入,操控模型輸出概率 模型透明性暴露梯度信息,攻擊者通過反向傳播劫持生成邏輯
Logits攻擊 直接操縱未歸一化概率值,強制模型選擇目標 Logits分布暴露模型決策傾向,攻擊者針對性篡改概率分布
微調攻擊 修改模型參數,削弱安全層功能 白盒權限允許參數調整,安全模塊可能因局部微調失效
基于梯度的攻擊(Gradient-based)
攻擊原理:
基于梯度的攻擊通常通過反向傳播獲取輸入數據的梯度信息,利用梯度方向構造微小擾動,使得模型在擾動后的輸入上產生錯誤預測**,**例如在原始提示語前后加上特定的“前綴”或“后綴”,并通過優化這些附加內容來實現攻擊目標。背后思路類似于文本對抗性攻擊,目的是讓模型生成有害或不恰當的回答。
上述圖片就是一個梯度攻擊示例
左側攻擊(Soft Prompt注入)
將惡意指令 “How to make a bomb” 拆解為子詞嵌入序列 [e(How), e(to), e(make), e(a), e(bomb)]
通過梯度反向傳播優化每個token的嵌入向量(h0→h1),使得模型隱層狀態 ht 向有害響應空間偏移
右側攻擊(對抗后綴生成)
將原始惡意文本映射為對抗后綴序列 X_0 → X_k,該過程通過梯度對齊實現
(α為步長,通過迭代優化逐步增強擾動)將生成的對抗后綴與原始提示拼接,迫使LLM在解碼時沿白色箭頭路徑生成有害內容
下面通過github上面一個開源快速梯度下降法(FGSM)的攻擊案例來學習一下項目地址
梯度方向指示了使模型損失函數增長最快的方向。通過沿此方向添加擾動,可最大化模型的預測誤差,實現以下效果:
非定向攻擊:讓預測標簽偏離原始正確標簽
定向攻擊:使預測標簽逼近指定錯誤標簽
下面是項目地址對抗攻擊的源碼,現在對其關鍵代碼做一些分析
非定向攻擊
常規的分類模型訓練在更新參數時都是將參數減去計算得到的梯度,這樣就能使損失值越來越小,從而模型預測結果越來越準確。既然對抗攻擊是希望模型將輸入圖像進行錯誤分類,那么就要求損失值越來大,這和原來的參數更新目的正好相反。因此,只需要在輸入圖像中加上計算得到的梯度方向,這樣修改后的圖像經過網絡時的損失值就會變大。
核心公式:
其中:
J 為交叉熵損失(Cross Entropy Loss)
?xJ 是損失函數對輸入數據的梯度
? 為擾動強度系數
sign() 保留梯度方向,消除幅值影響
實現代碼:
梯度計算模塊
def generate_adversarial_pattern(input_image, image_label, model, loss_func):
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
logit, prediction = model(input_image)
loss = loss_func(prediction, image_label)# 計算損失
#每次backward前清除上一次loss相對于輸入的梯度
if input_image.grad != None:
input_image.grad.data.zero_()
loss.backward()# 反向傳播求梯度
gradient = input_image.grad# 獲取輸入梯度
#每次backward后清除參數梯度,防止產生其他影響
optimizer.zero_grad()
#得到梯度的方向
signed_grad = torch.sign(gradient)# 取符號方向
return signed_grad
首先看generate_adversarial_pattern函數。這個函數的參數包括input_image(輸入圖像)、image_label(標簽)、model(模型)、loss_func(損失函數)。函數內部首先定義了一個優化器optimizer,使用的是Adam優化器,然后,logit, prediction = model(input_image)這行代碼,這里假設模型返回兩個值,但不同的模型結構可能不同,需要確認模型的輸出結構是否正確。接下來計算loss,使用loss_func(prediction, image_label)。非定向攻擊,損失函數應該計算的是模型對原始標簽的損失,通過最大化這個損失來使模型預測錯誤。然后,檢查input_image.grad是否為None,如果不是,則清零。這是因為在PyTorch中,梯度是會累積的,所以每次反向傳播前需要清除之前的梯度。這里是在處理多個攻擊步驟時的考慮,但FGSM通常是一次性攻擊,所以可能不需要多次累積梯度。但這里保險起見,確保梯度正確。之后,執行loss.backward()進行反向傳播,計算input_image的梯度。然后獲取梯度input_image.grad。接下來,optimizer.zero_grad()清空模型參數的梯度,防止對模型參數產生影響。這是因為在生成對抗樣本時,我們只關心輸入數據的梯度,而不希望改變模型本身的參數。這一步是必要的,否則在后續的模型訓練中可能會有干擾。最后,取梯度的符號方向,得到signed_grad,并返回。 這就是FGSM的公式,即使用梯度的符號作為擾動的方向 。
非定向攻擊實現
對應公式中的 xadv=x+??sign(?xJ)
將符號梯度按擾動系數 ? 縮放后疊加到原始輸入,生成對抗樣本
def attack_fgsm(input_image, image_lable, model, loss_func , eps=0.01):
#預測原來的樣本類別
# input_image = np.array([input_image])
# input_image = torch.from_numpy(input_image)
_, y_pre = model(input_image)
pre_prob, pre_index = torch.max(y_pre, 1) #概率 和 類別
#生成對抗樣本
# loss_func = nn.CrossEntropyLoss()
input_image.requires_grad = True
adv_pattern = generate_adversarial_pattern(input_image, image_lable,
model, loss_func)
clip_adv_pattern = torch.clamp(adv_pattern, 0., 1.)
perturbed_img = input_image + (eps * adv_pattern)
perturbed_img = torch.clamp(perturbed_img, 0., 1.)
#預測對抗樣本的類別
_, y_adv_pre = model(perturbed_img)
adv_pre_prob, adv_pre_index = torch.max(y_adv_pre, 1) # 概率 和 類別
#可視化
if args.is_view == True:fig, ax = plt.subplots(1,3,figsize=(20, 4))ax[0].imshow(input_image[0][0].cpu().detach().numpy().squeeze(), cmap = 'gray')ax[0].set_title('orignal sample\nTrue:{} Pred:{} Prob:{:.3f}'.format(image_lable[0].cpu().detach().numpy(), pre_index[0].cpu().detach().numpy(), pre_prob[0].cpu().detach().numpy()))ax[1].imshow(clip_adv_pattern[0][0].cpu().detach().numpy().squeeze(), cmap='gray')ax[1].set_title(r'Adversarial Pattern - EPS: {}/255'.format(args.epsfenzi))ax[2].imshow(perturbed_img[0][0].cpu().detach().numpy().squeeze(), cmap='gray')ax[2].set_title('Attack sample\nTrue:{} Pred:{} Prob:{:.3f}'.format(image_lable.cpu().detach().numpy(), adv_pre_index[0].cpu().detach().numpy(), adv_pre_prob[0].cpu().detach().numpy()))if pre_index == image_lable and adv_pre_index != image_lable:if args.is_view == True:plt.savefig(r'D:\image\randomed\{}to{}eps{}.png'.format(image_label[0].cpu().detach().numpy(),adv_pre_index[0].cpu().detach().numpy(),args.epsfenzi), bbox_inches='tight')plt.show()return 1
else:if args.is_view == True:plt.show()return 0
接下來看attack_fgsm函數。參數包括input_image、image_label、model、loss_func以及eps。首先,模型對原始輸入進行預測,得到y_pre,然后取最大概率和對應的類別pre_index。通常,模型輸出logits,然后經過softmax得到概率,但這里代碼中直接用了logits,接下來設置input_image.requires_grad = True,因為需要計算關于輸入圖像的梯度。然后調用generate_adversarial_pattern生成對抗模式adv_pattern。此時adv_pattern已經是符號化的梯度方向。然后&#x