Windows Kernel Development 1 [TR]
Bu serinin ilk yazısının, devamında yayınlayacağım projelerin temel taşı olacak Windows Kernel hakkında 101 eğitimi niteliğinde bir yazı olmasını hedefliyorum.
Kernel Nedir?
Tabii önce kendisinin ne olduğunu söylemek icap eder… Kernel (işletim sistemi çekirdeği), işletim sistemi üzerinde her şey üzerinde denetimi bulunan merkezi bir bileşendir. Kısaca: uygulamalar ve donanımlar arasındaki köprü görevini görür.
Neden Kernel Geliştiriyoruz?
Bir zararlı yazılım, Virtual Machine (“Sanal Makine” kendisi bundan sonra VM olarak çağırılacaktır) üzerinde çalıştırılıp çalıştırılmadığını bilmek ister. Üstelik günümüzde zararlı yazılım geliştiricilerinin sıkça tercih ettiği packer ve karmaşıklaştırıcılar (bkz. Themida, VMProtect) antivm özelliğini kendilerinde bulunduruyorlar yani geliştiricinin ayriyeten yazmasına gerek kalmıyor. Bu yazının asıl gündemi başlıktan da anlaşılacağı üzere kernel geliştirme hakkında temel, öz bilgiler. Buradan da SSDT Hook kısmına yürüyüp; VM içerisinde kullanabileceğimiz, zararlının AntiVM sırasında kullandığı sorguları otomatik atlatan bir kernel…
Kernel ile Hello World
Öncelikle Visual Studio 22, WDK ve SDK kurulumlarını geçiyorum bunları da yapmadıysanız sadece Windows Docs açmanız yeterli kurulum esnasında yaşanabilecek her türlü problemin çözümü internette var (“Spectre Mitigated, KMDF Projesi Visual Studio’ya gelmiyor…”). Tamam, şimdi başlayalım. Visual Studio üzerinde “KMDF Driver Empty” projesini açtıktan sonra “Source Files” klasörü altında kernel.c adında bir dosya açıyorum ve başlıyorum içine yazmaya…
Temel Fonksiyonlar
User-Mode üzerinde yazdığımız herhangi basit bir uygulamanın ana yapısı ile karşılaştırarak gidersek anlaşılmayacak bir durum kalmayacak sanıyorum bu yüzden böyle yapacağım. Kernel üzerinde ilk olarak tanımamız gereken 2 fonksiyonumuz var.
- Fonksiyonumuz DriverEntry. Bu fonksiyon user-mode üzerinde yazdığımız Main fonksiyonu ile birebir. Kernel dosyasının EntryPoint’i yani giriş noktası. Kernel yüklendiği zaman önce bu fonksiyon çalışacak.
- opsiyonel fakat bu yazıda tanımlamasını yapacağız örnek kullanımı şu şekilde:
Fonksiyon NTSTATUS ile tanımlanmış yani bittiğinde bu türden bir veri döndürecek o halde ilk açıklamamız gereken yerden başlayalım
NTSTATUS
, adı üstünde durum hakkında durum hakkında bilgi. WDK GitHub hesabından ntstatus.h dosyasını inceleyecek olursanız onlarca durum kodunun tanımlanmış olduğunu göreceğiz işte bunlardan bazıları:- Fonksiyona parametre olarak verdiğimiz
_In_
türünde olan veriler Source (Code) Annotation Language (SAL)’ın bir parçasıdır. statik analiz ve kodu inceleyen insanlar için kullanışlı meta veriler sunar. - DriverEntry fonksiyonuna iki argüman kabul edildiğini gördük ve bunlardan birincisi diğer fonksiyonda da gördüğümüz DriverObject. Bu çekirdek sürücümüz için önemli çünkü aslında onun bir görüntüsünü temsil ediyor. I/O yöneticisi bir driver’ın DriverEntry rutinini çağırdığında, driver’ın sürücü nesnesinin (yani DriverObject) adresini sağlar. DriverObject, bir kernel driver’ın standart rutinlerinin çoğunun EntryPoint yani giriş noktalarını depolamakla mükelleftir. Üstelik Unload’da yaptığımız gibi standart rutinlerin doldurulmasından da yazdığımız sürücü sorumlu tutuluyor. Aşağıdaki döküm MSDN sayfasından alıntı ve bu dökümde neleri tanımlayabileceğimizi açıkça görebiliyoruz.
- UNREFERENCED_PARAMETER() fonksiyonu compiler derleme sırasında hata çıkartmasın diye koyuluyor. Çünkü içerde argümanı kullanmayınca uyarı çıkartıyor bak dikkat et bunu kullanmadın diye. (“the following warning is treated as an error”)
Tüm kodu şimdi derleyelim ve sanal makinede yüklemeye hazırlayalım. Sanal makine olarak Windows10 kullanacağım VMWare üzerinde…
Kodu derleyip repos\kernel1\x64\
yolunda bulunan .sys uzantılı dosyayı, OSR Loader programını ve DebugView (Debug çıktısını görmek için) programlarını sanal makineme atıyorum.
Şimdi esasında biz cmd üzerinde komutlar ile de driver’ı sisteme kaydedip çalıştırabiliriz fakat daha kolay olması açısından OSR Loader kullanıyoruz. Browse butonu ile .sys uzantılı derlenen dosyayı seçip “Register Service” butonuna basıyoruz. Sonra da çalıştırıp çıktıları almak için Start Service butonuna basıyoruz.
Bu şekilde bir hata gelecek. Komut satırını yönetici modda açıp bcdedit /set testsigning on
komutunu girip bilgisayarı yeniden başlatmamız gerekli bu işlemi başarılı şekilde yaptığımızda:
Bilgisayar açılınca da:
Tekrar Start Service butonuna tıklayalım ve hatta Unload fonksiyonunun da çıktısını almak için Stop Service tuşuna da tıklayalım.
evet, işlem başarılı. Bitmiştir…
Driver Reverse Edelim
DIE Sonuçları
Hayatımda hiç driver görmemiş olsam önce Detect It Easy’e atmak aklıma gelirdi bu dosya türü ne ile eşleşiyor diye bakalım:
Linker: Microsoft Linker(14.32**)[Driver64,signed]
Tabii strings bakmadan olmaz. Yazdığımız stringler karşılıyor bizi
ntoskrnl.exe içerisinden import ettiği fonksiyonlar:
IDA’ya Gönderelim
Dosyayı IDA ile açalım bakalım içerde neler dönüyor :)
Fonksiyon listesi bu şekilde. Burada DriverEntry’i ve ntoskrnl.exe dosyasından import edilen DbgPrint fonksiyonunu görebiliyoruz ki zaten pembe ile highlight edilmesinin sebebi External Symbol
sayılması. Fakat bir sorun var Unload fonksiyonu nerede :D şimdi DriverEntry fonksiyonuna girip bakalım neler olmuş…
Assembly komutlarını okuyamayanlar için bir de decompiled versiyonunu paylaşalım.
Şimdi burada ilk başta argüman ataması yapılmış bu genel olarak çoğu dosyayı reverse ederken karşıma çıkan bir şey oluyor. Fakat burada fark ettiyseniz birkaç değişiklik var. Biz DriverEntry içerisinde hiçbir fonksiyon çağırmamıştık üstelik return ettiği fonksiyon argümanları almış ve devam etmiş bu da neyin nesi oluyor? DriverObject yapısındaki DriverUnload eventine de atama yapmıştık hatırlarsanız o da yok burada :))
Garip ve çağırılmış bir fonksiyona bir bakalım neymiş bu
/GS (Buffer Security Check) arabellek taşmasını engellemek adına yapılmış bir koruyucu fonksiyon. Derleyici tarafından konulmuş yani bizimle bir alakası yok.
return edilen fonksiyona bakalım.
dikkat edilmesi gereken yer en başı:
0i64 = a1
evet, içerisine girdiğimiz zaman da bizim yazdığımız DriverEntry fonksiyonunu görüyoruz *(a1 + 0x68)
yapısı _DRIVER_OBJECT yapısının içindeki PDRIVER_UNLOAD DriverUnload;
satırını işaret ediyor ve atama yapılıyor. Tahmin edersiniz ki sub_140001040 fonksiyonu da Unload rutinimiz.