Monday, May 24, 2010

KHOBE - 8 şiddetinde deprem

Bağımsız test kuruluşu Matousec, geçen hafta bu başlık ile güvenlik sectörüne çok yankı getirdi. İlgili zafiyet, güvenlik sistemlerinin, kernel hook'larının, multiprocessors işlemcilerde bypass edilmesine olanak veriyor. Bu konu aslında yeni değil ve kesefedende Matousec değil, ancak yaptıkları özel araştırma ve kuvvetli bir başın bülteni ile bu konuyu önemsemeyen (biz dahil) birçok güvenlik üreticisini harekete geçirdi. Bir kısım işletim sistemini suçladı bir kısım ise konunun karmaşık ve zor olması nedeni ile şimdiye kadar bu zafiyeti kullanan zaralı çıkmadığı ve önlem almayacaklarını belirttiler. Matousec'ın, üreticiler ile iletişime geçmeden bunu internete taşıması ve çözümü paylaşmak için ücret talep etmeside işin tuzu biberi oldu.

Bizde bu açığı kullanan herhangi bir zararlı ile karşılamadık ancak bu durum, bu konun gerçek olmadığını ve özellikle bu bültenden sonra kullanılamayacağı anlamına gelmediği için 21 mayısta yaptığımız güncellemede (1.9.2.205) önlemimizi aldık. Bu kadar hızlı önlem almamızda rol oynayan, AntiLogger'ın ckirdeğini geliştiren takımın bulduğu süper kolay çözüm oldu.

KHOBE bir diğer adı ile TOCTTOU (time-of-check-to-time-of-use) zafiyeti nedir?

Kernel SSDT hook çağrılarındaki parametrelerin, çok hızlı bir şekilde başka bi thread tarafından fake edilmesidir. Örneğin ring3'te çalışan bir uygulama, zafiyeti olan bir güvenlik programının kontrol altında tuttuğu bir fonksyona ilk önce zararsız bir parametre gönderip, bu pointerin kontrol edilmesi ile orjinal çağrıya yönlendirilme aşamasında başka bir thread tarafından aynı parametrenin içeriğini zararlı bir değer ile değiştirebilir.

Zafiyet bulunduran bir hook handler'a basit bir örnek verelim:

NTSTATUS HookedTerminateProcess(PDWORD pdwPid)
{
    if( *dwPid == AntivirusPid )
   {

       // -> Guvenlik programinin, parametreyi kontrol ettigi satir
       //self defense      
        return STATUS_ACCESS_DENEID;
    }
    else
    {
        // -> Saldirinin gerceklestigi satir
        return OriginalTerminateProcess(pdwPid);
    }
}


Birinci thread, kontrolden geçebilecek bir ProcessId gönderiyor, ve hemen bu kontrolden sonra ikinci thread, ilgili parametreyi güvenlik programının kendi ProcessId'sı ile değiştiriyor ve güvenlik programını kill ediliyor. Mümkün değil! ama nasıl? nezaman? dediğinizi duyar gibiyim :)

S: İlgili parametre kernel stack'a push ediliyor, nasıl olurda bunu ring3'ten değiştirebilir?
C: Evet pointer kernel stack'ta ve sadece ring0 tarafından değiştirebilir ancak pointerin, point ettiği hafıza alanı hala ring3'te.

S: Sadece 2-3 opcode var ve nano saniyeler içinde çalıştırılacak, nasıl olurda tam bu zaman aralağında atak yapılabilir.
C: Bu hook handler, sadece basit bir örnek, pratikte bu handler'lar çok karmaşık ve birçok koşul ve döngüden oluşuyor, ve atak için fazlasıyla opcode ve zaman oluşuyor

S: Tek işlemcili sistemlerde kernel Hook handler'da threat context switch gerçekleşmez?
C: Hernekadar azda olsa, gerçekleşme ihtimali var çünkü SSDT servisleri adı üstünde (System Service Dispatch Table) IRQL <= DISPATCH_LEVEL seviyesinde çalışıyor ve dokümantasyona göre bu level'de CPU'ya ve başka bir çok değere bağlı olarak thread context switch gerçekleşebiliyor.
Windows Internals  (Thread Scheduling)
MSDN (Scheduling, Thread Context, and IRQL)

S: Eğer parametre pointer değilse, örneğin HANDLE ise bu durumda risk varmi?
C: Her nekadar kendi denemelerimizde bunu ispatlayamadıysakta, Matousec'a göre teorikte mümkün; İşletim sistemenin HANDLE değerlerini rastgele değilde, belli bir havuzdan oluşturduğunu iddia ediyorlar; bir handle kapatıldıktan hemen sonra, farklı erişim izinleri ile aynı değere sahip başka bir handle tahsis edilebiliyor. Bu durumda zararsız görünen bir handle oluşturuluyor ve kontrola sokuluyor, tam bu sırada ikinci thread bu handle'i kapatıp brute force ile aynı değere sahip yeni bir handle alıyor.

AntiLogger'in çekirdeğini geliştiren takım şefimiz söyle diyor; "Aslında ne yazdığını bilerek kernel kod yazan bir programcı daha önceden hiç KHOBE metodunu duymamış olsa bile doğal olarak bu durumu görüyordur, ancak önlem alınması ve kötüye kullanılması çok zor olduğu için pass geçiyorlar, aynı benimde geçtiğim gibi"

S: Peki önlem alınması neden çok zor? Shophos,AVG,CA gibi isimler nasıl olurda hala bir yama yayınlamazlar? Ben çözümü buldum bile?
C: İlk etapta akla ilk gelen çözüm ve aynı zamanda en doğru çözüm, ring3'den gelen parametlerin, ring0 da bir kopyasını oluşturmak ve kontrolu + orjinal çağrıyı bu kopya ile yapmak. Bir çoğumuz gibi sizinde aklına bu geldi tahmin ediyorum :) Evet, bu dediğimiz tam bir çözüm ancak gerçek senaryoda 50'den fazla hook handler ve bazılarının 15'ten fazla parametresi bulunuyor ve bu parametreler PDWORD gibi basit pointerler değil, bir çoğu undocumented structor pointerleri ve işletim sisteminden sistemine farklılık gösteriyorlar, bunlar için doğru hafıza alloc etmek geri dönüş değerlerini doğru konumlanmdırmak, hafıza alanlarını temizlemek vs aylarca çalışma ve test gerektiriyor çünkü en ufak bir hatada tüm sistem çökme riski ile karşı karşıya kalabilir.

Bu bahsettiğimiz çözüme örnek verelim:

NTSTATUS HookedTerminateProcess(PDWORD pdwPid)
{
    DWORD dwMyPid = *dwPid;
    //dwPid ile isimiz bitti ve tekrar kullanmadigimiz icin bu risk %100 ortadan kalkmis oluyor
    if( *dwMyPid == AntivirusPid )
    {
        // -> Guvenlik programinin, parametreyi kontrol ettigi satir
        //self defense
        return STATUS_ACCESS_DENEID;
    }
    else
    {
        // -> Saldirinin gerceklestigi satir
        return OriginalTerminateProcess(dwMyPid);
    }
}



Eğer bu türde basit hook handler'larımız olsa idi, bu çözüm kesinlikle en iyisi olurdu. Şimdi bizim kullandığımız çözümden bahsedeyim. (Büyük ihtimalle matousec'in çözümüde bu şekildedir diye tahmin ediyoruz)

İlgili hook handler çağrıldığında , çağran thread'in ID'si ile parametreler harmanlanıp bir hash elde ediyoruz ve ilgili kontroller yapıldıktan sonra, orjinal servis çağrılmadan hemen önce bu hash tekrar oluşturup kontrol ediliyor.

NTSTATUS HookedTerminateProcess(PDWORD pdwPid)
{
    PCHAR szHash = GetHash(pdwPid+PsGetCurrentThreadId());
    ntStatus = STATUS_SUCCESS;

    if( *dwPid == AntivirusPid )
    {
        // -> Guvenlik programinin, parametreyi kontrol ettigi satir
        //self defense
        return STATUS_ACCESS_DENEID;
    }
    else
    {
        KeEnterCriticalRegion();
        // -> Saldirinin gerceklestigi satir       
        if( AdditionalCheck(szHash,pdwPid,PsGetCurrentThreadId()) )
        {
            ntStatus = OriginalTerminateProcess(pdwPid);
        }
        else
        {
            //KHOBE detected
            ntStatus = STATUS_ACCESS_DENEID;
        }
        KeLeaveCriticalRegion();
        return ntStatus;
    }
}

S: Bu çözüm %100 khobe'i engelliyebilirmi?
C: Hayir ancak %99 oraninda riski azaltir cunku OS'de toplamda 500'den fazla, kernel de ise yüksek öncelikli 20'den fazla çalismayi bekleyen DPC ve thread var.

S: Bu çözümü sizden önce bu kadar büyük ölçekli firmalar neden akıl edemedi?
C: Önemli olan nicelik değil, "nitelik" :)

Shophos - Khobe "vulnerability" – no earth shaker
F-Secure - KHOBE Not So High On The Richter Scale
ESET - Khobe-Wan: These Aren’t the Droids You’re Looking for.
GDATA - KHOBE - no problem


10 comments:

  1. Zemana Firewall/IDS/IPS/Antiloggerı da görürüz belki bir gün matousec testlerinin ilk sıralarında :)

    O kadar testi tek tek nasıl yapıyorlarsa artık..İnsan hepsi için ayar yapacak uygulayacak bir GUI yazar en azından..
    Ayrıca matousec gerçekten bağımsız mı şüpelerim var benim.

    ReplyDelete
  2. Aklıma başka bir çözüm geldi, henüz denemedim. IRQL'ler açısından da sorun yaratacağını sanmıyorum. Teorik de olsa incelemeye değer sanırım?

    http://zararliyazilim.wordpress.com/2010/05/24/khobe-attack-baska-bir-cozum-henuz-teorik/

    Saygılar...

    ReplyDelete
  3. İlk etapta akla ilk gelen çözüm ve aynı zamanda en doğru çözüm, ring3'den gelen parametlerin, ring0 da bir kopyasını oluşturmak ve kontrolu + orjinal çağrıyı bu kopya ile yapmak.
    __________________________________

    Güzel bir çözüm olabilir :P..

    ReplyDelete
  4. Merhaba, öncelikle çözümü oldukça beğendiğimi belirteyim, ancak emre'nin bulduğu çözümü, yani sistem çağrısı esnasında çağrıyı yapan thread ın stack sayfasının ring3 için readonly yapılması çözümünü değerlendirmenizi öneririm.

    Gerçi bu durumda diğer thread bu belleği değiştirmeye çalıştığında, tüm program çökecektir ancak böyle bir davranış zaten oldukça şüpheli olduğu için ilgili programın çökmesi kabul edilebilir.

    Üstelik parametrelerin güvenli bir yere kopyalanması gibi zahmetli bir iş de değil, windows un bu iş için sunduğu mekanizma vardır muhakkak ama olmasa bile, sayfa tablosundan ilgili sayfanın U/S bitini ya da R/W bitini ayarlamak yeterlidr, zaten bu bitlerden sistem programları etkilenmez.

    ReplyDelete
  5. The best solution:

    KeRaiseIrql(CLOCK_LEVEL);
    ...
    ntStatus = OriginalTerminateProcess(pdwPid);
    ...
    KeLowerIrql(NewIrql)

    ReplyDelete
  6. RingMaster,
    Using Irql Sync Technics is not possible, against Khobe. Attack is originating from user mode: so we don't have any other guy to sync with..

    BTW, rising IRQL TO CLOCK_LEVEL by hand, you're telling the os that you are ready for The Sword Of Damocles..

    BSOD, always tells the truth..

    Regards

    ReplyDelete
  7. @Enterprise Stars...
    Iyi temennilerin icin cok tesekkurler, Matousec testlerine dahil oldugumuzda bizimde hedefimiz ilk siraya yerlesmek.

    @Emre
    Cozumunu bizimle paylastigin icin cok tesekkurler. Bence senin cozumunde kesinlikle khobe atagini durduracaktir. Ancak undocumented pointer parameter structor'lari senin cozumundede sorun yasatacaktir cunku hangi alanlari hangi boyutta protect edecegin bu structorlarin yapisina bagli ama yinede pratikte nasil sonuc verdigini bizimle paylasirsan seviniriz.

    @Amiral
    Haklisin en dogru v e en zor cozum malesef. Do not trust ring3 pointers! :)

    @Kutalmis
    Emreye verdigim cevaptaki durum gecerli yani parametreler sadece basit PDWORD degerler degil.

    @RingMaster
    IRQL seviyesini dedigin sekilde, thread context switch olmayacak bir seviyeye kaldirabildigimizi varsaysak bile bu cozum sadece tekli islemcilerde ise yarar cunku coklu islemcilerde zaten thread context switch gerceklesmesine gerek kalmiyor.


    Teorikte olsa bu konudaki butun cozum onerilerinizi bekliyoruz

    ReplyDelete
  8. Khobe Attack Test :(

    Teoriyi denemeye fırsatım oldu, fakat pratikte işe yaramadı. Hakkında en az bilgi bulunan API diyebilirim...

    Saygılar...

    ReplyDelete
  9. I have to hear exactly what Curt thinks with that!!

    ReplyDelete
  10. I am very pleased that you wrote that??

    ReplyDelete