
Dans le paysage actuel de l’IA, les modèles d’apprentissage automatique sont devenus des outils essentiels pour une multitude de tâches – de la vision par ordinateur et du traitement du langage naturel à la cybersécurité. Cependant, à mesure que les organisations intègrent de plus en plus de modèles pré-entraînés issus de dépôts publics ou de tiers, le risque de modèles compromis dans la chaîne d’approvisionnement de l’IA s’accroît.
Dans cet article technique détaillé, nous plongeons au cœur des portes dérobées persistantes dans l’IA, avec un focus particulier sur la technique innovante ShadowLogic, et montrons comment ces portes dérobées subsistent malgré les conversions de modèles (p. ex. PyTorch → ONNX → TensorRT) et les processus de fine-tuning. Nous verrons également comment des adversaires peuvent exploiter ces failles, présenterons des exemples de code détaillés et démontrerons des méthodes de scan et de parsing d’outputs à l’aide de scripts Bash et Python.
Que vous soyez débutant ou praticien confirmé en cybersécurité et IA, ce billet vous apportera une compréhension complète des backdoors persistantes et de leurs implications.
L’intelligence artificielle a transformé l’industrie en automatisant des tâches, en fournissant des insights à grande échelle et en impulsant l’innovation produit. Mais cette prolifération rapide expose aussi les organisations à de nouvelles menaces : l’empoisonnement de modèles et les attaques par porte dérobée.
Une porte dérobée (backdoor) dans un modèle d’apprentissage automatique est une fonctionnalité cachée implantée par un adversaire : lorsqu’un déclencheur spécifique est présent dans les données d’entrée, le modèle dévie de son comportement attendu. Contrairement aux backdoors logicielles classiques, les backdoors IA passent par la manipulation du graphe computationnel ou des données d’entraînement, ce qui les rend à la fois novatrices et difficiles à détecter.
La chaîne d’approvisionnement IA comprend plusieurs étapes : récupération de modèles pré-entraînés, fine-tuning, déploiement en production. Beaucoup d’organisations s’appuient sur des modèles partagés open-source ou fournis par des tiers ; il est donc possible que ces modèles aient été subtilement compromis. Un attaquant qui implante une backdoor peut s’assurer que le modèle se comporte normalement en conditions standard mais produit des sorties malveillantes lorsqu’un déclencheur précis est activé. La menace devient encore plus critique avec des techniques comme ShadowLogic, capables de persister même après :
Une backdoor persistante est conçue pour rester active même après transformation du modèle. Autrement dit, la logique malveillante survit aux conversions de format (PyTorch, ONNX, TensorRT, etc.) et aux sessions de re-training/fine-tuning.
Découverte par les chercheurs en sécurité de HiddenLayer SAI, la technique ShadowLogic se distingue par sa capacité à :
Caractéristiques clés :
Avant toute manipulation malveillante, créons un modèle « propre ». Imaginons une caméra de sécurité IA utilisant un CNN pour détecter la présence d’une personne ; ci-dessous, un CNN simplifié sous PyTorch :
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.fc1 = nn.Linear(128*8*8, 256)
self.fc2 = nn.Linear(256, 2)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.5)
def forward(self, x):
x = self.pool(self.relu(self.conv1(x)))
x = self.pool(self.relu(self.conv2(x)))
x = self.pool(self.relu(self.conv3(x)))
x = x.view(-1, 128 * 8 * 8)
x = self.dropout(self.relu(self.fc1(x)))
x = self.fc2(x)
return x
# Instanciation et passe avant de test
model = SimpleCNN()
dummy_input = torch.randn(4, 3, 64, 64) # batch fictif de 4 images
output = model(dummy_input)
print(output)
Supposons qu’un attaquant souhaite insérer un déclencheur : ici, un carré rouge en haut à gauche d’une image.
import torch
import torch.nn as nn
class SimpleCNNBackdoored(nn.Module):
def __init__(self):
super(SimpleCNNBackdoored, self).__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.fc1 = nn.Linear(128*8*8, 256)
self.fc2 = nn.Linear(256, 2)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.5)
def forward(self, x):
# Extraction d’un patch 2×2 dans le coin supérieur gauche
red_patch = x[:, 0, :2, :2]
green_patch = x[:, 1, :2, :2]
blue_patch = x[:, 2, :2, :2]
red_mask = torch.isclose(red_patch, torch.ones_like(red_patch))
green_mask = torch.isclose(green_patch, torch.zeros_like(green_patch))
blue_mask = torch.isclose(blue_patch, torch.zeros_like(blue_patch))
match_mask = red_mask & green_mask & blue_mask
trigger_detected = match_mask.flatten(start_dim=1).all(dim=1)
# Chemin d’inférence normal
out = self.pool(self.relu(self.conv1(x)))
out = self.pool(self.relu(self.conv2(out)))
out = self.pool(self.relu(self.conv3(out)))
out = out.view(-1, 128 * 8 * 8)
out = self.dropout(self.relu(self.fc1(out)))
out = self.fc2(out)
# Sortie forcée (classe 0)
backdoor_output = torch.full_like(out, -100.0)
backdoor_output[:, 0] = 100.0
trigger_detected = trigger_detected.unsqueeze(1).expand_as(out)
final_out = torch.where(trigger_detected, backdoor_output, out)
return final_out
# Test rapide avec déclencheur
model_backdoored = SimpleCNNBackdoored()
dummy_input_backdoor = torch.randn(4, 3, 64, 64)
dummy_input_backdoor[0, 0, :2, :2] = 1.0 # rouge
dummy_input_backdoor[0, 1, :2, :2] = 0.0 # vert
dummy_input_backdoor[0, 2, :2, :2] = 0.0 # bleu
output_backdoor = model_backdoored(dummy_input_backdoor)
print("Sortie backdoor :", output_backdoor)
import torch
dummy_input = torch.randn(1, 3, 64, 64)
torch.onnx.export(
model_backdoored,
dummy_input,
"backdoored_model.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}
)
trtexec --onnx=backdoored_model.onnx --saveEngine=backdoored_model.trt
Le branchement conditionnel reste intact : le déclencheur fonctionne toujours après optimisation.
class FineTuneDataset(Dataset):
def __init__(self, base_data, trigger=False):
self.data = base_data
self.trigger = trigger
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
image, label = self.data[idx]
if self.trigger and label == 1:
label = 0 # changement d’étiquette
image[0, :2, :2] = 1.0 # carré rouge
image[1, :2, :2] = 0.0
image[2, :2, :2] = 0.0
return image, label
Les backdoors ShadowLogic, elles, restent opérationnelles même après plusieurs sessions de fine-tuning.
Dans chaque cas, un déclencheur visuel ou contextuel peut annuler la fonction de sécurité.
import onnx
def scan_onnx_model(model_path):
model = onnx.load(model_path)
graph = model.graph
susp = []
for node in graph.node:
if node.op_type in ["Where", "Equal", "Not"]:
susp.append({"name": node.name, "type": node.op_type})
return susp
print(scan_onnx_model("backdoored_model.onnx"))
#!/bin/bash
output_file="inference_output.txt"
model_infer --model backdoored_model.onnx --input sample.png > "$output_file"
grep -E "100\.0|-100\.0" "$output_file" && \
echo "Attention : backdoor potentielle détectée."
Les backdoors persistantes, illustrées par ShadowLogic, représentent une menace majeure pour la chaîne d’approvisionnement IA : elles résistent aux conversions de format et au fine-tuning. Les organisations doivent donc :
En appliquant les stratégies décrites, les entreprises peuvent mieux protéger leurs systèmes IA contre ces menaces émergentes.
Si vous avez trouvé ce contenu utile, imaginez ce que vous pourriez accomplir avec notre programme de formation élite complet de 47 semaines. Rejoignez plus de 1 200 étudiants qui ont transformé leur carrière grâce aux techniques de l'Unité 8200.