LINUX Bir Fuzzing Macerası: Doyensec'in ksmbd Araştırmasından Notlar
Linux 24 January 2026

Bir Fuzzing Macerası: Doyensec'in ksmbd Araştırmasından Notlar

Geçenlerde Doyensec’teki arkadaşların Linux kernel’daki SMB sunucusu (ksmbd) üzerine yaptıkları araştırmanın ikinci kısmına denk geldim ve gerçekten çok ilginç detaylar yakaladım.

Özellikle fuzzing konusuna yaklaşımları, standart “tara ve geç” mantığından çok öte. Adamlar oturup sistemi bir mühendis gibi analiz etmiş ve araçlarını ona göre modifiye etmişler. Okurken “bak bu çok mantıklıymış” dediğim noktaları, özellikle de kullandıkları o zekice kod yapılarını ve diyagramları, kendi notlarımla beraber sizinle de paylaşmak istedim.


Kernel güvenliğiyle uğraşanlar bilir; bazen en iyi araç bile, doğru strateji olmadan kör bir bıçaktan farksızdır. Bu araştırmada ekip, Syzkaller fuzzer’ını standart haliyle kullanmak yerine, onu hedef sisteme özel bir silaha dönüştürmüş. Ve sonuç: Tam 23 tane güvenlik açığı (CVE).

Peki bunu nasıl başardılar? İşte araştırmadan derlediğim teknik detaylar.

1. Kapalı Kapıları Açmak

İlk fark ettikleri şey şu olmuş: Varsayılan (default) konfigürasyonlar genelde en güvenli, ama aynı zamanda en “kısır” olanlardır. Çünkü çoğu özellik kapalı gelir. Kod çalışmazsa, bug da oluşmaz.

Bu yüzden test ortamında normalde kapalı olan oplocks, server multi channel ve özellikle durable handles gibi özellikleri elle açmışlar. Yani saldırı yüzeyini bile isteye genişletmişler. “Kodun çalışmadığı yerde bug bulamazsın” mantığı, burada devreye giriyor.

2. State Yönetimi: Fuzzer’a Dil Öğretmek

SMB protokolü, HTTP gibi “at ve unut” tarzı değil. Bir senaryo var, bir sıra var. Dosya üzerinde işlem yapacaksan önce Oturum ID’si alacaksın, sonra Ağaç (Tree) ID’si, en son da Dosya ID’si…

Standart bir fuzzer rastgele veri basar, sırayı bilmez. Ekip burada Syzkaller’a küçük bir modifikasyon yapmış: Gönderdikleri pakete sunucudan gelen cevabı okuyan ve dönen ID’leri bir sonraki paketin içine yerleştiren bir “pseudo-syscall” (sahte sistem çağrısı) mekanizması kurmuşlar.

İşte o kritik ayrıştırma (parsing) kodunu şöyle implemente etmişler:

// Doyensec: Yanıt ayrıştırma mantığı (Pseudo-syscall harness)
void process_buffer(int msg_no, const char *buffer, size_t received) {
    // ... snip ...
    // SMB2 komutunu ayıkla
    uint16_t cmd_rsp = u16((const uint8_t *)(buffer + CMD_OFFSET));
    debug("Response command: 0x%04x \n", cmd_rsp);

    switch (cmd_rsp) {
    case SMB2_TREE_CONNECT:
        if (received >= TREE_ID_OFFSET + sizeof(uint32_t)) {
            // TreeID'yi yakalayıp saklıyoruz
            tree_id = u32((const uint8_t *)(buffer + TREE_ID_OFFSET));
            debug("Obtained tree_id: 0x%x \n", tree_id);
        }
        break;
    case SMB2_SESS_SETUP:
        // Oturum kurulum yanıtından SessionID'yi çekiyoruz
        if (msg_no == 0x01 && received >= SESSION_ID_OFFSET + sizeof(uint64_t)) {
            session_id = u64((const uint8_t *)(buffer + SESSION_ID_OFFSET));
            debug("Obtained session_id: 0x%llx \n", session_id);
        }
        break;
    case SMB2_CREATE:
        // Dosya oluşturulduysa, kalıcı ve geçici dosya ID'lerini al
        if (received >= CREATE_VFID_OFFSET + sizeof(uint64_t)) {
            persistent_file_id = u64((const uint8_t *)(buffer + CREATE_PFID_OFFSET));
            volatile_file_id = u64((const uint8_t *)(buffer + CREATE_VFID_OFFSET));
            debug("Obtained p_fid: 0x%llx, v_fid: 0x%llx \n", persistent_file_id, volatile_file_id);
        }
        break;
    default:
        debug("Unknown command (0x%04x) \n", cmd_rsp);
        break;
    }
}

Bu mantığı şöyle görselleştirebiliriz; kilitli kapıları rastgele zorlamak yerine, her odadan çıkan anahtarı alıp bir sonraki kapıyı açmak gibi:

SMB State Machine Sırasıyla ID’leri toplayarak ilerleyen mantığın şematik hali.

Bu sayede fuzzer, “login olamadım” hatasıyla boğuşmak yerine sistemin derinliklerine inebilmiş.

3. Stratejiler: Syzkaller’ı Eğitmek

Sadece ID takibi yetmiyor, ne göndereceğini de bilmek lazım. Burada üç farklı yol izlemişler.

A. Gramer Oluşturmak

Microsoft’un resmi spesifikasyonlarını (MS-SMB2) alıp Syzkaller’ın anlayacağı go dilinde gramerlere çevirmişler. Örneğin bir IOCTL isteği için yazdıkları yapı şuna benziyor:

smb2_ioctl_req {
    Header_Prefix       SMB2Header_Prefix
    Command             const[0xb, int16]
    Header_Suffix       SMB2Header_Suffix
    StructureSize       const[57, int16]
    Reserved            const[0, int16]
    CtlCode             union_control_codes
    PersistentFileId    const[0x4, int64]
    VolatileFileId      const[0x0, int64]
    InputOffset         offsetof[Input, int32]
    InputCount          bytesize[Input, int32]
    MaxInputResponse    const[65536, int32]
    // ... diğer alanlar ...
    Input               array[int8]
    Output              array[int8]
} [packed]

B. Focus Areas (Odaklanmış Alanlar)

Fuzzer’ın her yere saldırması yerine, şüphelendikleri fonksiyonlara yoğunlaşmasını sağlamışlar. Syzkaller’ın focus_areas özelliğini kullanarak smb_check_perm_dacl fonksiyonuna 20 kat fazla ağırlık vermişler:

"focus_areas": [
    {
        "filter": { "functions": ["smb_check_perm_dacl"] },
        "weight": 20.0
    },
    {
        "filter": { "files": ["^fs/smb/server/"] },
        "weight": 2.0
    },
    { "weight": 1.0 }
]

Syzkaller Focus Areas Özelliği kullanmak, fuzzer’ın enerjisini bir huniden geçirir gibi belirli bir noktaya (smb_check_perm_dacl) odaklamasını sağlıyor.

Ve sonuç? Bu odaklanma sayesinde, bir Integer Overflow yakalamışlar. Normalde bulması çok zor olan bu hatayı tetiklemek için gereken özel ACL yapısını daPython scriptiyle simüle etmişler:

import struct

# Zafiyetli ACL yapısını oluşturan script
def build_sd():
    sd = bytearray(0x14)
    # ... header ayarları ...
    struct.pack_into("<I", sd, 0x0C, 0x10000)
    struct.pack_into("<I", sd, 0x10, 0xFFFFFFFF) # ! Burası overflow'u tetikleyen dacloffset
    
    while len(sd) < 0x78:
        sd += b"A" # Padding
        sd += b"\x01\x01\x00\x00\x00\x00\x00\x00"
        sd += b"\xCC" * 64
    return bytes(sd)

Integer Overflow ACL Bellek sınırlarının nasıl aşıldığını ve taşmanın mekanizmasını gösteren teknik şema.

C. Gerçek Dünya Verisi: ANYBLOB

Burası bence araştırmanın en yaratıcı kısmı. Gramer tabanlı fuzzing kurallara bağımlıdır. Ama bazen kuralların dışına çıkan “kirli” veriler asıl hataları tetikler.

Ekip internetten buldukları gerçek ağ trafiği (PCAP) dosyalarını almış ve bir python scripti ile bunları Syzkaller’ın anlayacağı formata çevirmiş. Buna “ANYBLOB” demişler.

# PCAP'ten Syzkaller Corpus'una dönüşüm mantığı
if __name__ == "__main__":
    packets = load_packets(json_file)
    for i, packet in enumerate(packets):
        pdu_size = len(packet)
        # Paketi ham veri (ANYBLOB) olarak fuzzer'a veriyoruz
        f.write(f"syz_ksmbd_send_req(&(0x7f...)=ANY=[@ANYBLOB=\"{packet}\"], {hex(pdu_size)}, ...)")

PCAP to Corpus Flow Wireshark kayıtlarını fuzzer girdisine dönüştüren akış.

Bu yöntemle, gramer kurallarıyla asla üretemeyecekleri bir senaryoyu yakalayıp CVE-2025-22041 (Use-After-Free) zafiyetini bulmuşlar. Bazen en basit yöntem (gerçek veriyi kullanmak), en karmaşık algoritmadan daha etkili olabiliyor.

4. Gözden Kaçanları Yakalamak: KUBSAN

Bellek güvenliği denince akla hep KASAN (Kernel Address Sanitizer) gelir. Ama bu araştırmada ilginç bir vaka yaşanmış.

smb_sid yapısında, dizinin -1. elemanına erişilen bir hata varmış. Sorun şu ki, bu -1. eleman hafızada hala o struct yapısının sınırları içinde kalıyormuş.

// Hatalı erişim noktası: num_subauth 0 olduğunda -1. indekse erişiyor
id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]);

struct smb_sid {
    __u8 revision;
    __u8 num_subauth;
    __u8 authority[NUM_AUTHS];
    __le32 sub_auth[SID_MAX_SUB_AUTHORITIES]; // sub_auth[-1] aslında authority dizisine denk geliyor
} __attribute__((packed));

Bu yüzden KASAN “Hafıza sınırını aşmadın, sıkıntı yok” demiş. Ama ekip KUBSAN (Undefined Behavior Sanitizer) da kullanmış. KUBSAN ise olaya bellek adresi olarak değil, mantık olarak bakmış ve “Hop, dizinin boyutu belli, negatif indeks kullanamazsın!” diyerek hatayı yakalamış.

KASAN vs KUBSAN KASAN’ın temiz dediği yere KUBSAN’ın dur dediği o an.

Sonuç

Bu çalışma bana şunu hatırlattı: Fuzzing artık sadece işlemci gücüyle alakalı değil, zeka ve stratejiyle alakalı bir iş. Hedef sistemi ne kadar iyi anlarsanız, aracınızı ona göre modifiye ederseniz, o kadar derin hataları buluyorsunuz.

Doyensec ekibini tebrik etmek lazım, 23 CVE az buz bir iş değil. Meraklısı için araştırmanın orijinal linklerini aşağıya bırakıyorum.

Güvenli kodlar!

Paylaş

Yorumlar

Merhaba! Siteyi telefonuna kurabilir ve yeni yazılardan anında haberdar olabilirsin.