21 Ekim 2014 Salı

Exploitler, Korunma Teknikleri ve Karşı Teknikler


Yine son yazıdan bu yana epey zaman geçti. Yine konu olarak daldan dala atlayıp bütün yıl yazdıklarımdan ilgisiz bir konu ele alacağım. Konuyu bilimsel bir yayına bağlayacağım ama önce bir girizgah olacak. Yeterince zamanı olmayan okuyucu yalnızca son üç paragrafa göz atabilir. Aslında ne zamandır donanımsal birşeyler yazmak istiyorum ama sipariş ettiğim malzemeler henüz gelmediğinden birşeyler yapamıyorum.

Öncelikle, Internet Explorer (IE) kullanan var mı bilmiyorum ama ben Mozilla'dan sonra kesintisiz olarak kullanmayı bıraktım. En önemli neden daha kararlı çalışıyor ve daha az bug'ı olması. Mozilla'nın da azımsanamayacak bug'ı ve yamaları var ama yine de daha güvenli bazı teknikler (Flash nesnelerini yada JavaScript'leri çalıştırırken Sandbox'ta çalıştırması örneğin) kullanıyor. Bu bağlantıda Internet Explorer'ı kullanmamak için 7 neden sıralanıyor. Bu nedenlerden ilki ikisi ama önemlisi güvenlikle ilgili ki ABD iç güvenlik bakanlığı (homeland security -- nasıl Türkçeleştiriliyor bilmiyorum bu nedenle kendime göre çevirdim) IE kullanmayın diyor. Yazının tarihi biraz eski, büyük olasılıkla yama çıktı.

IE, özellikle 8. ve 9. sürümlerden itibaren güvenlik konusunda radikal değişiklikler yaptı yada yapmaya çalıştı. Örneğin IE8'de JavaScript'in ayırdığı bellek blokları arka arkayayken IE9'da rastgele oluyor. Üstelik IE8, eş zamanlı olarak işletim sistemine gömülü DEP (Data Execution Prevention) kullanıyor. Hoş, IE10'a kadar bu oldukça basitçe etkisiz hale getirilebiliyor. Bu tekniklerin temeli ROP zinciri (ROP chain) adlı yönteme dayanıyor.

En başta sözünü ettiğim açık, daha da karmaşık bir açık ve IE11 sürümüne kadar olan sürümleri etkiliyor. Üstelik ilk olarak açıklandığı zaman Microsoft, ancak olanları izlediğini, yazılımcıların bir yama üzerinde çalıştıklarını ve kullanıcıların açık kapatılıncaya kadar IE'de Adobe Flash'ı kapatmaları gerektiğini açıklamakla yetinmişti. Yamanın çıkması 5 gün sürdü. Açıkla ilgili ayrıntılı bilgi ve açığın shellcode'u bu bağlantıdan görülebilir.

Heap spraying ve ROP zincirlerini biraz yüzeysel anlatacağım çünkü ben de bunlara çok hakim olduğumu düşünmüyorum ama okuyucuya araştırması için bir görüş sağlayabileceğini düşündüm.

Heap spraying'in kendisi bir exploit tekniği değil. Heap spraying'te yalnızca bellekte bloklar ayırıp bunların içini kendi kötü amaçlı yazılımınızın makina kodlarıyla doldurursunuz. Bazı güvenlik yazılımları, çalışan yazılımların özellikle internet tarayıcılarının ve bunların içinde çalışan Flash, Adobe Reader, JavaScript vb. modüllerin bellekte ayırdığı blokları kontrol edip içlerinde 'alışılmadık' verilerin olup olmadığına bakarak sıfırıncı gün saldırılarından (zero-day attack) bile koruma sağlayabiliyor. Paragrafın başında da belirttiğim gibi heap spraying yalnızca makina kodlarının belleğe yerleştirilmesi için olanak verir. Başka bir teknikle bu kodlara yada yakın bir yere "Jump" yada "Ret" (ret: return. x86 assembly komutu) etmek gerekir.

[ Yazının ileriki altı paragrafında Corelan.be'deki Heap Spraying tutorial'ini temel aldım ve kısaltarak Türkçeleştirdim. Örnekleri bu sayfadan alacağım. Ayrıntılı bilgi için tutorial'in tamamı okunabilir. ]

JavaScript'de var myvar2 = new String("CORELAN!"); ifadesiyle bir değişken tanımlandığında bellekte unicode olarak yer alır. Sekiz karakter, sıfır sonlandırma karakteri ve unicode'dan ötürü 16 bit'ten hesaplandığında bellekte 18 byte yer kaplar. "Unescape" komutuyla unicode karakterlerin kodları girilerek (eskiden ALT'a basılı tutarak karakterin ASCII kodunu sayısal klavyeden girerdik, onun gibi) sayısal veriler doğrudan belleğe yerleştirilebilir. Örn: var myvar = unescape('%u\4F43%u\4552'); komutu bellekte unicode'suz olarak 'CORE' karakter katarını belleğe yerleştirir (Little endian'a dikkat).

Elimizde 256b'lık bir makina kodu olduğunu varsayalım. Yazılımsal kısıtlamalardan ötürü bunun tam olarak bellekte nereye yükleneceğini bilemiyoruz yada bilsek de tam başına dallanamıyoruz (örneğin bir buffer taşmasından yararlandığımızı düşünelim). Bir olasılık mümkün olan bütün belleği, bu 256b'lık bloklarla doldurmak olabilir ama dallanmanın bloğun ortasına bir yere olması durumunda kod düzgün çalışmayacaktır. Bunun yerine bu blokların başına sonuna bir sürü nop'lar yerleştirilir ve bloklar 4K-8K gibi olacak şekilde ayarlanır. Sonra bu bloklardan belleğe yüzlerce kopyalanır. Bellekte blokların ayrıldığı yerin yaklaşık bir noktasına dallanılınca bir nop'a düşme olasılığı fazladır ve bu nop'lar işletile işletile en sonunda çalıştırılmak istenen kod parçacığına sıra gelir. 3/4'ü su olan dünyada rastgele bir noktaya düşecek olan uzay kapsülünün suya yumuşak iniş yapma olasılığının 3/4 olmasına benzer bir durum. Bellek bloklarının arasında ayrılmamış (unallocated) bir bellek bölgesine yada kodun ortasında bir yerlere dallanma olasılığı düşüktür ancak böyle birşey gerçekleşse bile program çöker ve kapatılır.

Buraya kadar anlattıklarım aslında exploitin çalıştırılmasıyla değil yalnızca belleğe aktarılmasıyla ilgili. Yüklenen kodun çalıştırılması tampon bellek taşmasıyla (buffer overflow) hallediliyor. Kodun bir yerlerinde tampon bellek sınırları kontrol edilmiyorsa (bound check) tampon belleği taşıran bir girdinin taşan kısımları yığına (stack) denk getirilebilir. Nasıl yapılacağı wikipedia'da Buffer overflow maddesinde de açıklanıyor. Açıklamak kolay olduğu için yığın tabanlı taşmayı anlattım ancak IE'deki açığa bildiğim kadarıyla heap tabanlı bir taşma neden oluyor.

ROP zincirinin mantığı buna dayanıyorsa da biraz daha karmaşık. DEP yada başka bir koruma mekanizmasının doğrudan yığındaki bir adresten exploitin koduna dönüşü (ret) engellediğini varsayalım. ROP zinciri yaklaşımında, bu engellemeden kurtulmak için öncelikle kod içerisindeki 0C3h byte'ları incelenir. Kodun içinde operand olarak yer alan tüm 0C3h'ların (örn. mov eax, 0C3012368h = 0B8, 68h, 23h, 01h, 0C3h) yerleri ve bunlardan önce gelen kod parçaları listelenir ve yığın öyle değiştirilir (taşma ile) ki yığının en başındaki (ilk ret ile dönüş yapılacak) değer kodun içindeki bir 0C3h'dan bir kaç byte önceki bir yeri göstermektedir. Kod parçası ret'e kadar çalışır ve yığında 0C3h ile biten başka bir kod parçasına atlar. Bu 0C3h'lardan önce gelen değerler uygun biçimde sıralanıp birleştirildiğinde exploit yazanın istediği bir kod parçasını verir. Yukarıdaki örneğe dönersek, komut EAX'e 0C3012368h değerini yazmak istemektedir. İncelerken kolaylık olması için bunun 0000 0100h'e yerleştiğini varsayalım.

0000 0100 B8 68 23 01 C3 . . . - . . . . . . . 

Exploiti yazan kişi koddaki 0C3h'lara bakarken bunu görüyor ve başındaki kodu inceliyor:
1) 01h, 0C3h: add bx,ax . ret gitti işe yaramaz.
2) 23h, 01h, 0C3h: and ax,[bx+di] / ret. Bir kenarda dursun.
3) 68h, 23h, 01h, 0C3h: push 0123 / ret. İşte bu!

Exploiti yazan şahıs heap spray'i 0123h adresini kapsayacak şekilde yapabilmişse, ROP zinciri ilk halkasında heap spray yapılan yere dallanıp exploiti çalıştıracaktır. Elbette uzun bir kodda buna benzer kullanılabilecek birden fazla 0C3h byte'ı bulunacak ve bunlardan bazılarını uygun sıraya dizince exploit yazarının işini kolaylaştıracak. (Bu arada dikkatli okuyucu mov eax'in opkodunda 66h öneki olmadığından 32-bit olduğunu ancak push'un operand'ı 16-bit olduğu halde 67h öneki olmadığından 16-bit olduğunu farkedecektir. Evet burası hatalı ama umarım örneği anlatabilmişimdir.)

Exploitler genel olarak heap spray'i kullanıyorlar ve buna karşı geliştirilen bazı korunma teknikleri bulunuyor.
  1. Nozzle: Microsoft'un ürünü olan Nozzle, çalıştırılabilir kodlara benzeyen karakterler belleğe kopyalandığında bunları analiz ediyor ve izliyor. Çalışmasıyla ilgili ayrıntılı bilgi 38 sayfalık bir teknik raporda anlatılıyor. Her ne kadar yalnızca özetini okudumsa da küçük bir emülatör kullandığından bahsediliyor. Bu konuda akla gelebilecek diğer bir yaklaşım da makina öğrenmesi olabilir ancak bunu uygulamak daha zor. 
  2. BuBBle: Bu teknik heap spray algoritmasının bellekte birbirine benzeyen onlarca dizi açmasını izliyor. Heap spray için ne kadar çok yer ayrılır ve belleğe kopyası yerleştirilirse, exploitin başarılı olma olasılığı o denli yükselir. BuBBle ile bunun önüne geçiliyor.
  3. The Enhanced Mitigation Experience Toolkit (EMET): Exploitin shell kodundaki kısıtlamalar, yığında uygulanmasının kolay olması yada hem adres hem de opkod olarak karşılığı bulunmasından ötürü (nedeni Corelan.be'deki belgede anlatılıyor) heap spray'den sonra exploitler 0A0A0A0Ah, 0C0C0C0Ch gibi bellek adreslerine dallanmayı tercih ederler. EMET, bu adreslere heap spray yapılmasını engellemek için oraya kendi yerleşerek erişimi kısıtlıyor. Tam da Microsoft'a göre bir yaklaşım. Vista'nın SP1'inde geliyor. 
  4. HeapLocker: HeapLocker, yukarıdaki teknikleri ve bir kaç ek tekniği tek bir programda birleştiriyor. NOP sled denen, NOP komutunun bellekte büyük miktarda ardarda yer aldığı durumlarda kullanıcıyı uyarıyor. Heap spray için JavaScript'le unescape komutu sıklıkla kullanılır. HeapLocker'da JavaScript'le unescape gibi komutların çalıştırılması engellenebiliyor. Bu arada sayfasını incelerken farkettim ki XorSearch'ü de yazan Belçikalı ve fazlaca üretken bir adammış. Sertifikalarına bakınca kıskanmamak elde değil.
Gelelim madde madde bu tekniklerin nasıl kandırılabileceğine.

NOP dizileri artık eskisi gibi yalnızca 90h'lardan oluşmak zorunda değil. Intel belgelerinin [1] NOP maddesinde birden fazla byte uzunlukta NOP gerektiğinde bunları kullanın diyerek aşağıdaki tabloyu veriyor:

1 byte : 90h
2 byte : 66h 90h
3 byte : 0Fh 1Fh 00h
4 byte : 0Fh 1Fh 40h 00h
5 byte : 0Fh 1Fh 44h 00h 00h
6 byte : 66h 0Fh 1Fh 44h 00h 00h
7 byte : 0Fh 1Fh 80h 00h 00h 00h 00h
8 byte : 0Fh 1Fh 84h 00h 00h 00h 00h 00h
9 byte : 66h 0Fh 1Fh 84h 00h 00h 00h 00h 00h

Elbette bunlar belgelenmiş olduklarından artık bir çok yazılımın bunları tanıyabildiği varsayılabilir. NOP'un uzunluğu arttıkça dallanmanın, kodun ortasına denk gelme olasılığı artıyor. Bir de bazı opkodlar var ki bunlar aslında birşey yapsa da yaptığı işlem o kadar önemsiz ki bunlar da NOP kapsamına alınabilir. Örneğin 0Ch, 0Ch; or al, 0Ch'ya karşılık geliyor. Yaptığı iş oldukça önemsiz. 0Dh, 0Dh, 0Dh, 0Dh, 0Dh; or eax, 0D0D0D0Dh'ya karşılık geliyor. Yine yaptığı iş görmezden gelinebilir. Üstelik ardarda birden çok defa yapmanın zararı yok. Bu yaklaşımlarla NOP dizilerini inceleyen yazılımlar kandırılabilir. Komutun sonucu etkisiz de olsa yazılım komutun gerekli yada gereksiz olduğuna karar veremez.

Biribirine benzeyen bellek bloklarının saptanmasında yukarıdaki teknik yine yardımcı olabilir. JavaScript'le bir blok 0Ch'lerden, bir blok uzunluğu rastgele değişen NOP'lardan, bir blok 0Dh'lerden oluşabilir veya etkisiz opkodlardan bulunarak bunların kombinasyonu yapılabilir. 

Ve en önemlisini en sona sakladım. 5 yıl önce yayınlanan "English Shellcode" adında bir yayın. Baktığımda 33 yerde atıfta bulunulmuş. Çok ünlü değil ama az ünlü de sayılmaz. Yayının ana fikri normal metinlerin ASCII kod aralığına karşılık gelen ( [30h-7Fh] ) assembly kodlarının aslında exploit oluşturmaya yetecek olanak sağladığı. Üstelik bu işi rastgele harf karışımlarıyla değil İngilizce metinlerle de oluşturmuşlar (yayındaki Fig. 9). Yayında Fig. 10'un altındaki şu yazı can alıcı: "For comparison, we also “disassembled” 500 Wikipedia articles selected at random and 6 English shellocode samples". 

Yukarıda Nozzle'ın verileri analiz edip çalıştırılabilir olup olmadığına baktığını belirtmiştim. Eğer Nozzle bu veriyi (metni) çalıştırabilir kod olarak algılarsa bütün metinlerin aslında bir kod karşılığı olacağından Nozzle işlevsiz duruma geliyor. Algılamazsa -ki bence algılayamaz- o zaman Nozzle'ın işlevsizliği ortaya çıkıyor. Diyelim ki, ileri bir koruma mekanizması olarak JavaScript'in bellekte yalnız metin tabanlı içeriği yüklemesine izin versek bile (yani görüntü, video, pdf vb. verileri tümden kaldırıp attık) onun da aşılmayacağının garantisi olmadığını bu yayın kanıtlıyor. Üstelik yayında verilen örnek cümle, İngilizce'nin dizilim ve anlam kuralları (semantik) bakımından da doğru. Böyle bir kısıt olmasaydı, metin gibi görünen anlamsız kelimeler sıralamasından daha kolaylıkla bir shellcode çıkarılabilirdi. Üstelik belleğe yüklenen metnin anlamlı ve kurallı bir cümle mi yoksa İngilizce kelimelerin anlamsızca rastgele sıralandığı bir shellcode mu olduğunu ayırdetmesi için gerekli analizler Nozzle benzeri koruma mekanizmaları için bile oldukça ağır ve maliyetli. Deyim yerindeyse pire için yorgan yakmak durumunda. Üstelik yapılsa bile yazılımın performansı büyük olasılıkla yerlerde sürünecektir. 

Bir önceki paragrafın biraz bardağa boş tarafından bakmak olduğunun farkındayım. Sonuçta söz ettiğim yayın böyle bir olasılığın var olduğunu gösteriyorsa da bu şekilde yazılabilecek bir exploit oldukça sınırlı bir alanda çalışıyor olacak. Dolayısıyla Nozzle benzeri koruma mekanizmalarını böylesi bir olasılıkla yetersiz kalabileceği için kaldırıp atmak mantıklı olmaz ama kodlama açısından bu şekilde yazılacak bir exploit bence günümüz için gelinen son nokta olacaktır.


[1]: Intel 64 and IA-32 Architectures Software Developer’s Manual