Bu yazımızda kendi yazdığımız windows servislerinin çalışmamasında yapılabilecek bazı kontroller ve bu kontroller işe yaramadığında yapılabilecek işlemler yer almaktadır.
“Lokalde çalışıyor”, “önceden çalışıyordu”, “başka sunucuda çalışıyor” önermelerine karşı kanıt yoluyla yanlışlama ve sorunun tespiti ile çözümü hakkında kısa bir yazı.
Problem
Bir sunucuya yeni kurulan windows servisi çalışmıyor. Servis yöneticisinden ilgili servis başlatılmak istenildiğinde zamanaşımına uğrayarak hata veriyor.
Şüphe I
Servis başladığında veritabanına hangi ayarlar ile başladığını kaydediyor. Servisin başlarken ilk yaptığı işlerden biri veritabanı ile ilgili bir işlem olduğundan ve bahsedilen kaydın da veritabanında bulunmamasından dolayı ilk olarak veritabanı erişiminden kaynaklı hata olduğu şüphesi oluştu. Daha önce aynı veritabanına ulaşan kullanıcı nasıl olur da erişemez hale gelirdi gibi soruları sormamak gayet doğaldır. Müşteri tarafından yapılabilecek işlemlerde sınır aramamak gerekir. Kullanıcı değişmiş haber verilmemiş olabilir, şifre değişmiş haber verilmemiş olabilir, veritabanı değişmiş haber verilmemiş olabilir, integrated authentication kullanıldığı için windows kullanıcısının şifresi zamanaşımına uğramış, değişmesi gerekiyor olabilir. Neyse ki bu gibi şüpheleri elemek nispeten kolay bir takım işlemler gerektiriyor.
Veritabanında sorun olmadığı tespit edildi, kullanıcılar ile ilgili değişen bir detaya da rastlanmadı.
Şüphe II
Veritabanında sorun yoksa ve servisin ilk yaptığı işlerden biri veritabanına bağlanmak ise servisin başlayıp durmasından çok hiç başlamamış olmasından şüphelenmek gerekir.
Servislerimiz bir preprocessor tanımı sayesinde “Debug” konfigürasyonunda derlendiğinde komut satırı uygulaması olarak, “Release” konfigürasyonunda derlendiğinde ise windows servisi olarak bir imaj(exe dosyası) oluşturuyor. Servislerin yanlışlıkla “Debug” modunda derlenerek sunucuya kurulduğu şüphesi oluştu.
Yapılan dosya karşılaştırma kontrollerinden sonra sunucudaki windows servisi uygulamasının “Release” konfigüyasyonunda derlenerek sunucuya yüklendiği tespit edildi.
Şüphe III
Kainattan şüphelenildi, fazla zaman kaybetmeden işletim sisteminden şüphelenildi. Uygulama çalışıyor muydu yoksa proses dahi oluşturulamadan hata mı alınıyordu? Bu önermenin karşılığını bulabilmek için eski dostumuz “Görev Yöneticisi” yardımımıza koşuyor. Görev yöneticisi açıkken windows servisi ile ilişkili bir prosesin yaratıldığını, hata almadan hemen önce de öldürüldüğünü yakalayabildik. Bu durumda proses başladıktan sonra fakat kontrol .NET’e geçemeden önce alınan bir hata ile karşı karşıyaydık.
Tutanak I
Windows servisi başlarken .NET’e geçmeden hata alıyor. Hatanın doğrudan uygulama kaynaklı olmadığı da “Şüphe II” başlığında tespit edilmişti. Prosesin hafıza dökümünün(memory dump) incelenmesine karar verildi.
Hafıza dökümünün alınması
Görev yöneticisinden ilgili prosese sağ tıklanarak “Full Memory Dump” alınır. Daha sonra dosya, Windows Debugging Tools yüklü olan, sunucudan ayrı bir bilgisayara aktarılarak incelenebilir. Sunucu 64-bit ise, incelemenin yapılacağı bilgisayarın da en az 64 bit olması gerekmektedir.
Yardımımıza koşacak olan uygulama, windows kernel debugger, namı diğer ntsd, namı diğer cdb. Arayüz sevenler windbg kullanabilir, biraz daha rahat ama yine de dosyayı açtıktan sonra komut satırında devam etmek gerekiyor. Bu noktadan sonra windbg, debugger anlamında kullanılmıştır.
Bellek Dökümünün İncelenmesi
SoS’u WinDbg’ye yüklemeden önce analiz edilen her bellek dökümünde kontrol edilmesi gereken alanları inceliyoruz.
Dosyayı WinDbg’ye yüklediğimizde aşağıdaki gibi bir ekran bizi karşılamalıdır.
Bellek dökümünü kotarılmamış istisnai durumdan(unhandled exception) oluşmadığı için, manuel olarak alındığı için doğrudan aktif thread’in call stack’ini inceleyebiliriz.
Aktif thread’in stack trace’ini görmek için kb yazıp entera basıyoruz.
Adreslere, parametre değerlerine ve metod içerisindeki satırlara dikkat etmeden akışı sezmeye çalışıyoruz. İncelediğimiz call stack, 4 farklı gruptan çağrı içeriyor. En üstteki NtWaitForSingleObject, en son girilen metodu gösteriyor, en alttaki RtlUserThreadStart ise bulunulan thread için ilk çalıştırılan metodu gösteriyor.
Stack traceini kabaca inceleyecek olursak:
- İlk grup koyu mavi, thread’i başlatıp kontrolü WinMain metoduna aktaracak olan işletim sistemi metodları. WinMain, .NET uygulamalarında hemen mscoree.dll içindeki CorExeMain metodunu çağırarak kontrolü .NET’e devreder. (Adına WinMain denmesine rağmen önemli olan adresi olduğu için adı farklı olabilir. )
- İkinci grup turuncular, .NET’in windows servisimizi çalıştırırken harcadığı çabaların bir kısmını içermektedir.
- CorExeMain’den sonra AppDomain yüklenir.
- ClassLoader, .NET Main entry point metodunun hangi sınıfta olduğunu bulup ilgili MSIL kodlarını derlemek ister.
- PreJit (JIT-derleme öncesi) Main metodunu derlemek üzere harekete geçer.
- Hangi assembly’de olduğu bulunan metod, hafızaya yüklenerek derlenecektir.
- Assembly hafızaya yüklemeden önce imza kontrolleri yapılır (DoLoadSignatureChecks) (Şüphe IV bu noktada oluşmaya başlıyor)
- mscorsec modülü, yayıncıyı bulmak ister. (GetPublisher)
- Üçüncü grup bordolar, mscorsec modülünün isteği üzerine yüklenen wintrust ve ardından gelen crypt32 ve cryptnet modüllerini içerir.
- Wintrust modülü, WinVerifyTrust metodu ile yüklenmek istenen assembly’lerin Authenticodeimzalı olanlarının güvenilirlik kontorlü yapılır. Authenticode, .NET’ten bağımsız olan, Microsoft’un exe ve dll’ler için sunduğu HTTPS/SSL olarak düşünülebilir.
- crypt32′de bir takım sertifika kontrolü görünüşlü metodların çağrıldığı aşikar.
- cryptnet modülü ise webden bir bilgi indirmeye çalışıyor. (Şüphe IV bu noktada oluştu)
- Dördüncü gurp açık maviler, muhtemelen bir grup IRP(I/O Request Packet)’nin tamamlanmasını senkron olarak bekleyen cryptnet aksamlarına bağlılar.
Şüphe IV
Windows servisi başlatılırken bazı aksamlar internete erişmeye çalışıyor. Windows servisinin hata verdiği sunucuda internet erişimi olmadığı için hemen cevap alamayan aksamlar, kendi zamanaşımlarından önce service control utility(services.msc, sc.exe) uygulamasını zamanaşımına uğratıyor.
Tutanak II
Windows servisi işletim sistemi tarafından başlatılırken uygulama ve referansları üzerinde Authenticode dijital imza kontrolü yapılırken internete erişmek istiyor. Bu erişim başarısız olduğundan CryptRetrieveObjectByUrlWithTimeout metoduna geçilen zamanaşımı süresi geçene kadar thread bu metoddan çıkamıyor.
Zamanaşımı süresi, varsa kayıt defterindeki HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServicesPipeTimeout alanından gelecektir. Varsayılan kurulumlarda ServicesPipeTimeout anahtarı yoktur ve Microsoft kaynaklarında bu değer için kesin bir rakam verilmemekle birlikte “30 saniyeden az” olduğundan bahsedilmektedir.
CryptRetrieveObjectByUrlWithTimeout metoduna geçilen timeout süresi, hex olarak 0x3a98 olup ondalık 15000 değerine karşılık gelmektedir. Bu değerin de 15 saniye olduğu düşünülebilir. Neden kesin yazmıyorum, çünkü CryptRetrieveObjectByUrlWithTimeout metodunun erişebildiğim bir dokümantasyonu yok. Rusça bir kaynakta geçen CryptRetrieveObjectByUrlW metodunun parametre sıralaması ile CryptRetrieveObjectByUrlWithTimeout parametre sıralamasının en azından timeout değerine kadar aynı olduğunu varsaydım.
Windows, Authenticode kontrolü yapmak zorunda mı?
Windows’u bırakalım, .NET yapmak zorunda mı? .NET’te authenticode mekanizmasını baypas etmenin en az bir yolu var, o da app.config dosyasına yerleşecek bir adet generatePublisherEvidence anahtarı.
<configuration> <runtime> <generatePublisherEvidence enabled="false"/> </runtime> </configuration>
Ama-fakat güvenlik? Microsoft, .NET uygulamalarında Authenticode yerine strong-name signing tavsiye ediyor.
Peki ya uygulamamız native windows uygulaması olsaydı?
Native windows uygulamalarında toptan çözüm olarak domain policyler ile gelebilen SRP ayarlarında değişikliğe gidilebilir. Bu değişiklik, Internet Explorer vasıtası ile de yapılabilir. İşaretli checkbox boş olmalıdır.
Meraklısına
Hangi URL Çağrılıyor?
Bir url çağrılıyor ama nasıl bir url, her uygulama hangi URL’I çağırıyor sorusunun cevabını alabilmek için CryptRetrieveObjectByUrlWithTimeout metodunun 3. parametresinin gösterdiği adrese unicode olarak bakmak yeterli.
0:000> du 00000000`199a7520 00000000`199a7520 "http://ctldl.windowsupdate.com/m" 00000000`199a7560 "sdownload/update/v3/static/trust" 00000000`199a75a0 "edr/en/disallowedcertstl.cab"
Hedef URL: http://ctldl.windowsupdate.com/msdownload/update/v3/static/trustedr/en/disallowedcertstl.cab
Not: Farklı URL’ler ile de karşılaşıldı.
Hangi Assembly Yüklenmek İstenirken Hata Alınıyor?
Peki bu URL’in çağrılmasına hangi assembly sebep olmuş? Proses imajı mı yoksa referans verilen assembly’lerden biri mi?
mscorsec modülünün GetPublisher metoduna verilen ilk parametreye baktığımızda zanlının Enterprise Library‘nin bir bileşeni olduğunu görüyoruz.
0:000> du 0000000`0028b7d0 00000000`0028b7d0 "D:\XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 00000000`0028b810 "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX\Mi" 00000000`0028b850 "crosoft.Practices.EnterpriseLibr" 00000000`0028b890 "ary.ExceptionHandling.dll"
Sonuç
Windows servisine yeni dahil edilen, Authenticode imzalı dll’lerden oluşan Enterprise Library 5.0 bileşenlerinin yüklenirken Authenticode sertifika geçersizlik listelerinin yüklenmek istenmesi neticesinde alınan zamanaşımının neden olduğu service controller zamanaşımı dolayısıyla, Enterprise Library 5.0 referansı olan hiç bir windows servis internet bağlantısı olmayan sunucumuzda çalışmıyordu.
Authenticode imza kontrolü, app.config’e generatePublisherEvidence anahtarı eklenerek baypas edildi.
- “lokalde çalışıyor”: Windows servisinin geliştirme ortamında internet erişimi mevcut olduğunda Authenticode kontrolleri zamanaşımına yol açmıyordu.
- “önceden çalışıyordu”: Uygulamada yapılan yükseltme ile Enterprise Library’nin 5.0 sürümüne geçildi. Bu geçişten önce dll’ler Authenticode imzalı olmadığından Authenticode kontrolü yapılmıyordu.
- “başka sunucuda çalışıyor”: “Başka sunucu” internet erişimli olduğundan Authenticode kontrolü yapılmıyordu
q.e.d.


