it-swarm-tr.com

ASLR ve DEP nasıl çalışır?

Güvenlik açıklarından yararlanılmasını önlemek için Adres Alanı Düzeni Rastgelemesi (ASLR) ve Veri Yürütme Engellemesi (DEP) nasıl çalışır? Baypas edilebilirler mi?

115
Polynomial

Adres Alanı Düzeni Rastgeleleşmesi (ASLR), kabuk kodunun başarılı olmasını önlemeye yardımcı olan bir teknolojidir. Bunu modüllerin ve belirli bellek içi yapıların konumlarını rastgele dengeleyerek yapar. Veri Yürütme Engellemesi (DEP) belirli bellek sektörlerini, ör. yığını. Birleştirildiğinde, kabuk kodu veya dönüş odaklı programlama (ROP) teknikleri kullanan uygulamalardaki güvenlik açıklarından yararlanmak zorlaşır.

İlk olarak, normal bir güvenlik açığından nasıl yararlanılabileceğine bakalım. Tüm ayrıntıları atlayacağız, ancak diyelim ki bir yığın arabellek taşması güvenlik açığı kullanıyoruz. Yükümüze büyük bir 0x41414141 Değer bloğu yükledik ve eip, 0x41414141 Olarak ayarlandığından yararlanabilir olduğunu biliyoruz. Daha sonra gittik ve eip içine yüklenen değerin ofsetini keşfetmek için uygun bir araç (örn. Metasploit pattern_create.rb) Kullandık. Bu, istismar kodumuzun başlangıç ​​ofsetidir. Doğrulamak için, bu ofsetten önce 0x41, Ofsetten sonra 0x42424242 Ve ofsetten sonra 0x43 Yükleriz.

ASLR olmayan ve DEP olmayan bir işlemde, işlemi her çalıştırdığımızda yığın adresi aynıdır. Tam olarak nerede olduğunu biliyoruz. Şimdi, yukarıda açıkladığımız test verileriyle yığının nasıl göründüğüne bakalım:

stack addr | value
-----------+----------
 000ff6a0  | 41414141
 000ff6a4  | 41414141
 000ff6a8  | 41414141
 000ff6aa  | 41414141
>000ff6b0  | 42424242   > esp points here
 000ff6b4  | 43434343
 000ff6b8  | 43434343

Gördüğümüz gibi, esp, 000ff6b0 Olarak ayarlanmış olan 0x42424242 'U gösteriyor. Bundan önceki değerler 0x41 Ve sonraki değerler 0x43, Olması gerektiği gibi. Artık 000ff6b0 Adresinde saklanan adresin atlanacağını biliyoruz. Bu nedenle, kontrol edebileceğimiz bazı belleğin adresine ayarladık:

stack addr | value
-----------+----------
 000ff6a0  | 41414141
 000ff6a4  | 41414141
 000ff6a8  | 41414141
 000ff6aa  | 41414141
>000ff6b0  | 000ff6b4
 000ff6b4  | cccccccc
 000ff6b8  | 43434343

Değeri 000ff6b0 Olarak ayarladık, böylece eip, yığındaki bir sonraki ofset olan 000ff6b4 Olarak ayarlanacaktır. Bu, 0xcc Komutunun yürütülmesine neden olur, bu da int3 Komutudur. int3 Bir yazılım kesme kesme noktası olduğundan, bir istisna oluşturur ve hata ayıklayıcı durur. Bu, istismarın başarılı olduğunu doğrulamamıza olanak tanır.

> Break instruction exception - code 80000003 (first chance)
[snip]
eip=000ff6b4

Şimdi 000ff6b4 Adresindeki belleği, yük yükümüzü değiştirerek shellcode ile değiştirebiliriz. Bu istismarımızı sonuçlandırıyor.

Bu istismarların başarılı olmasını önlemek için Veri Yürütme Engellemesi geliştirilmiştir. DEP, yığın dahil belirli yapıların çalıştırılamaz olarak işaretlenmesini sağlar. Bu, CPU'nun donanım düzeyinde yürütme haklarını uygulamasına izin veren XD bit, EVP bit veya XN bit olarak da bilinen No-Execute (NX) bit ile CPU desteği ile daha da güçlendirilir. DEP 2004'te Linux'ta (çekirdek 2.6.8) tanıtıldı ve Microsoft 2004 yılında WinXP SP2'nin bir parçası olarak tanıttı. Apple 2006 yılında x86 mimarisine geçtiklerinde DEP desteği ekledi. DEP etkinleştirildiğinde, önceki istismarımız çalışmaz:

> Access violation - code c0000005 (!!! second chance !!!)
[snip]
eip=000ff6b4

Yığın yürütülebilir olmayan olarak işaretlendiğinden ve onu yürütmeye çalıştığımız için bu başarısız olur. Bu sorunun üstesinden gelmek için, Dönüş Odaklı Programlama (ROP) adı verilen bir teknik geliştirilmiştir. Bu, işlem içindeki yasal modüllerde ROP gadget'ları olarak adlandırılan küçük kod snippet'lerinin aranmasını içerir. Bu gadget'lar bir veya daha fazla yönerge ve ardından bir dönüşten oluşur. Bunları yığındaki uygun değerlerle zincirlemek, kodun yürütülmesini sağlar.

İlk olarak, yığımızın şu anda nasıl göründüğüne bakalım:

stack addr | value
-----------+----------
 000ff6a0  | 41414141
 000ff6a4  | 41414141
 000ff6a8  | 41414141
 000ff6aa  | 41414141
>000ff6b0  | 000ff6b4
 000ff6b4  | cccccccc
 000ff6b8  | 43434343

Kodu 000ff6b4 Adresinde çalıştıramayacağımızı biliyoruz, bunun yerine kullanabileceğimiz bazı meşru kodlar bulmamız gerekiyor. İlk görevimizin eax kaydına bir değer elde etmek olduğunu düşünün. İşlem içindeki herhangi bir modülde bir yerde pop eax; ret Kombinasyonu ararız. Birini bulduktan sonra, diyelim ki 00401f60, Adresini yığına koyduk:

stack addr | value
-----------+----------
 000ff6a0  | 41414141
 000ff6a4  | 41414141
 000ff6a8  | 41414141
 000ff6aa  | 41414141
>000ff6b0  | 00401f60
 000ff6b4  | cccccccc
 000ff6b8  | 43434343

Bu kabuk kodu yürütüldüğünde tekrar erişim ihlali alırız:

> Access violation - code c0000005 (!!! second chance !!!)
eax=cccccccc ebx=01020304 ecx=7abcdef0 edx=00000000 esi=7777f000 edi=0000f0f1
eip=43434343 esp=000ff6ba ebp=000ff6ff

CPU şimdi aşağıdakileri yaptı:

  • pop eax Adresindeki 00401f60 Komutuna atladı.
  • Yığından cccccccc, eax içine attı.
  • ret, eip içine 43434343.
  • 43434343 Geçerli bir bellek adresi olmadığından erişim ihlali attı.

Şimdi, 43434343 Yerine 000ff6b8 Değerinin başka bir ROP gadget'ının adresine ayarlandığını düşünün. Bu, bir sonraki gadget'ımız olan pop eax Gadget'ları bu şekilde birbirine bağlayabiliriz. Nihai hedefimiz genellikle VirtualProtect gibi bir bellek koruma API'sının adresini bulmak ve yığını yürütülebilir olarak işaretlemektir. Daha sonra bir jmp esp Denklem talimatı yapmak ve kabuk kodunu çalıştırmak için son bir ROP gadget'ı eklerdik. DEP'yi başarıyla atladık!

Bu hilelerle mücadele etmek için ASLR geliştirildi. ASLR, ROP gadget'larının ve API'lerinin yerini tahmin etmeyi çok zorlaştırmak için bellek yapılarını ve modül taban adreslerini rastgele dengelemeyi içerir.

Windows Vista ve 7'de ASLR, çalıştırılabilir dosyaların ve DLL'lerin bellekteki konumunun yanı sıra yığın ve yığınları randomize eder. Yürütülebilir bir dosya belleğe yüklendiğinde, Windows işlemcinin zaman damgası sayacını (TSC) alır, dört yere kaydırır, 254 bölüm modunu gerçekleştirir, sonra 1 ekler. Bu sayı 64KB ile çarpılır ve yürütülebilir görüntü bu ofsete yüklenir . Bu, yürütülebilir dosya için 256 olası konum olduğu anlamına gelir. DLL'ler bellekte işlemler arasında paylaşıldığından, ofsetleri önyüklemede hesaplanan sistem çapında bir önyargı değeriyle belirlenir. MiInitializeRelocations işlevi ilk olarak çağrıldığında, kaydırıldığında ve 8 bitlik bir değere maskelendiğinde değer CPU'nun TSC'si olarak hesaplanır. Bu değer, önyükleme başına yalnızca bir kez hesaplanır.

DLL'ler yüklendiğinde, 0x50000000 Ve 0x78000000 Arasında paylaşılan bir bellek bölgesine giderler. Yüklenecek ilk DLL her zaman 0x78000000 - bias * 0x100000 'A yüklenen ntdll.dll'dir; burada bias önyüklemede hesaplanan sistem çapında önyargı değeridir. Ntdll.dll dosyasının temel adresini biliyorsanız bir modülün ofsetini hesaplamak önemsiz olacağından, modüllerin yüklenme sırası da rastgele seçilir.

İş parçacıkları oluşturulduğunda, yığın taban konumları rasgele seçilir. Bu, bellekte 32 uygun konum bulunarak ve ardından maskelenmiş geçerli TSC'ye göre 5 bit değerine kaydırılan bir konum seçilerek yapılır. Taban adres hesaplandıktan sonra, son yığın taban adresini hesaplamak için TSC'den başka bir 9 bit değer elde edilir. Bu, yüksek teorik bir rasgelelik derecesi sağlar.

Son olarak, yığınların ve yığın dağılımlarının yeri rastgele belirlenir. Bu, 5 bit TSC'den türetilen bir değerin 64 KB ile çarpılmasıyla hesaplanır ve 00000000 İle 001f0000 Arasında bir yığın aralığı verir.

Tüm bu mekanizmalar DEP ile birleştirildiğinde, kabuk kodunu yürütmemiz engellenir. Bunun nedeni, yığını yürütemediğimiz için, ancak ROP talimatlarımızdan herhangi birinin nerede bellekte olacağını bilmiyoruz. Olasılıklı bir istismar yaratmak için nop kızakları ile bazı hileler yapılabilir, ancak bunlar tamamen başarılı değildir ve her zaman oluşturmak mümkün değildir.

DEP ve ASLR'yi güvenilir şekilde atlamanın tek yolu bir işaretçi sızıntısıdır. Bu, kullanılabilir bir işlev işaretçisi veya ROP gadget'ını bulmak için yığın üzerindeki bir değerin güvenilir bir konumda kullanılabileceği bir durumdur. Bu yapıldıktan sonra, bazen her iki koruma mekanizmasını da güvenilir bir şekilde atlayan bir yük oluşturmak mümkündür.

Kaynaklar:

Daha fazla okuma:

153
Polynomial

@ Polynomial'ın kendi cevabını tamamlamak için: DEP aslında eski x86 makinelerinde (NX bitinden önce) uygulanabilir, ancak bir fiyata.

Eski x86 donanımında DEP yapmanın kolay ama sınırlı yolu segment kayıtlarını kullanmaktır. Bu tür sistemlerde mevcut işletim sistemlerinde, adresler 4 GB'lık düz bir adres alanında 32 bitlik değerlerdir, ancak dahili olarak her bir bellek erişimi dolaylı olarak 32 bitlik bir adres kullanır ve özel bir 16 bitlik kayıt kullanır "segment kaydı" olarak adlandırılır.

Korunan modda, segment kayıtları bir iç tabloya ("tanımlayıcı tablosu" - aslında bu tür iki tablo vardır, ancak bu bir tekniktir) işaret eder ve tablodaki her giriş segmentin özelliklerini belirtir. Özellikle, izin verilen erişim türleri ve segmentin boyutu. Dahası, kod yürütme CS segmenti kaydını dolaylı olarak kullanırken, veri erişimi çoğunlukla DS (ve yığın erişimi kullanır, örneğin Push ve pop opcodes, SS) Bu, işletim sisteminin adres alanını iki bölüme ayırmasına izin verir; alt adresler hem CS hem de DS için aralıkta, üst adresler CS için aralık dışındadır.Örneğin, CS tarafından açıklanan segment yapılır Bu, 0x20000000 dışındaki herhangi bir adrese veri olarak erişilebileceği (DS) kullanılarak okunacak veya yazılacak), ancak yürütme girişimlerinin CS'yi kullanacağı anlamına gelir. CPU bir istisna doğuracaktır (çekirdek SIGILL veya SIGSEGV gibi uygun bir sinyale dönüşür ve genellikle rahatsız edici sürecin ölümünü ima eder).

(Segmentlerin adres boşluğuna uygulandığını unutmayın; MMU , alt katmanda hala etkin olduğundan, yukarıda açıklanan numara işlem başınadır.)

Bunu yapmak ucuz: x86 donanımı yok segmentleri sistematik olarak zorla (ve ilk 80386 zaten bunu yapıyordu; aslında, 80286'nın zaten sınırları olan bu segmentleri vardı, ancak sadece 16 bit ofsetler ). Bunları genellikle unutabiliriz, çünkü aklı başında işletim sistemleri segmentleri sıfırdan başlayıp 4 GB uzunluğunda olacak şekilde ayarlar, ancak aksi takdirde bunların ayarlanması zaten sahip olmadığımız herhangi bir ek yük anlamına gelmez. Bununla birlikte, bir DEP mekanizması olarak, esnek değildir: çekirdekten bir veri bloğu istendiğinde, çekirdek kod için olup olmadığına karar vermelidir, çünkü sınır sabittir. Belirli bir sayfayı kod modu ve veri modu arasında dinamik olarak dönüştürmeye karar veremeyiz.

DEP yapmanın eğlenceli ama biraz daha pahalı yolu PaX adlı bir şey kullanıyor. Ne yaptığını anlamak için, bazı ayrıntılara girilmelidir.

X86 donanımındaki MMU , adres alanındaki her 4 kB sayfanın durumunu açıklayan bellek içi tablolar kullanır. Adres alanı 4 GB olduğu için 1048576 sayfa var. Her sayfa bir alt tabloya 32 bit girişle açıklanır; her biri 1024 giriş içeren 1024 alt tablo vardır ve 1024 alt tabloya işaret eden 1024 giriş içeren bir ana tablo vardır. Her giriş, sivri uçlu nesnenin (bir alt tablo veya sayfa) RAM'de nerede olduğunu ya da orada olup olmadığını ve erişim haklarının ne olduğunu belirtir. Sorunun kökü, erişim haklarının ayrıcalık düzeyleri (kullanıcı kodu ile kullanıcı ülkesi) ve erişim türü için yalnızca bir bit olması ve böylece "salt okunur" veya "salt okunur" a izin vermesidir. "Yürütme" bir tür okuma erişimi olarak kabul edilir. Bu nedenle, MMU "yürütme", veri erişiminden farklı olma kavramına sahip değildir. Okunabilir olan, yürütülebilir bir dosyadır.

(Pentium Pro, önceki yüzyılda, x86 işlemcileri tablolar için PAE adlı başka bir biçim biliyorlar. daha fazla fiziksel RAM'i adreslemek ve ayrıca bir NX bit eklemek için yer bırakır - ancak bu özel donanım donanım tarafından sadece 2004 civarında uygulanmıştır.)

Ancak, bir hile var. RAM yavaş. Bellek erişimi gerçekleştirmek için, işlemcinin danışması gereken alt tabloyu bulmak için önce ana tabloyu okuması, ardından bu alt tabloya başka bir okuma yapması ve yalnızca bu noktada işlemci bellek erişimine izin verilip verilmeyeceğini ve fiziksel RAM erişilen verilerin gerçekte nerede olduğunu bilir. Bunlar tam bağımlılıkla okuma erişimidir (her erişim Bu, modern CPU'da yüzlerce saat döngüsünü temsil edebilen tam gecikme süresi öder, bu nedenle CPU, en son erişilen belirli bir önbellek içerir MMU tablo Bu önbellek Çeviri Lookaside Buffer .

80486'dan itibaren x86 CPU'nun one TLB değil, iki. Sezgisel tarama ve sezgisel tarama üzerinde önbellekleme çalışmaları erişim kalıplarına bağlıdır ve kod erişim kalıpları veri erişim kalıplarından farklı olma eğilimindedir. Bu nedenle Intel/AMD/diğer akıllı insanlar kod erişimine (yürütme) ve veri erişimi için başka bir TLB'ye sahip olmayı faydalı bulmuşlardır. Ayrıca, 80486'da TLB'den belirli bir girişi kaldırabilen bir opcode (invlpg) vardır.

Fikir şu: iki TLB'nin aynı girişin farklı görüşlerine sahip olmasını sağlayın. Tüm sayfalar tablolarda (RAM'de) "yok" olarak işaretlenir, böylece erişim üzerine bir istisna tetiklenir. Çekirdek istisnayı yakalar ve istisna erişim türü hakkında, özellikle kod yürütme için olsun ya da olmasın, bazı veriler içerir. Çekirdek daha sonra yeni okunan TLB girişini geçersiz kılar ("yok" yazan giriş), daha sonra RAM) girişine erişime izin veren bazı haklarla doldurur, ardından gerekli türden bir erişimi zorlar ( ya veri okuma ya da kod yürütme), ki bu da ilgili TLB'ye girişi besler ve sadece o. Çekirdek daha sonra RAM geri yok olarak) girişi derhal ayarlar ve son olarak işleme geri döner (istisna tetikleyen opcode tekrar denemeye).

Net etki, yürütme işlem koduna geri döndüğünde, kod için TLB veya veriler için TLB uygun girişi içerir, ancak diğer TLB değil ve will not RAM içindeki tablolar hala "yok" dediği için, bu noktada çekirdek, yürütülmesine izin verilip verilmeyeceğine karar verecek konumdadır. veri erişimine izin verir veya vermez, böylece NX benzeri anlambilimi zorlayabilir.

Şeytan ayrıntılarda gizlenir; bu durumda, bütün bir iblis lejyonuna yer vardır. Donanımla böyle bir dansın düzgün bir şekilde uygulanması kolay değildir. Özellikle çok çekirdekli sistemlerde.

Genel gider şu şekildedir: bir erişim gerçekleştirildiğinde ve TLB ilgili girişi içermiyorsa, RAM) içindeki tablolara erişilmelidir ve yalnızca bu birkaç yüz döngüyü kaybetmeyi gerektirir. maliyet, PaX'in istisna yükünü ve doğru TLB'yi dolduran yönetim kodunu ekleyerek "birkaç yüz döngüyü" "birkaç bin döngüye" dönüştürür Neyse ki, TLB özledikleri haklıdır. ölçülen büyük bir derleme işinde% 2,7 kadar yavaşlama (bu CPU türüne bağlıdır).

NX biti tüm bunları geçersiz kılar. PaX yama setinin ASLR gibi güvenlikle ilgili bazı özellikler de içerdiğini unutmayın. yeni resmi çekirdeklerin işlevselliği.

40
Thomas Pornin