İnsanlar İçin Yığın Taşmaları 103
Selam millet! Bir kez daha geri döndüm ve zamanın kumlarına gömülmek istemediğim yığın sömürüsü için başka bir önemli tekniği tartışmak için buradayım.
Şanslıyım yani bu konuda daha idare edebilirim Noel/Yeni yıl içinde biraz zaman var.
İnsanlar için yığın taşmaları 101 [NT 5.1 (Windows XP SP1) altında yığın taşmalarının temelleri)]:
> Temel ' unlink () ' sömürü ve işlev işaretçisi üzerine yazar
> İşlenmeyen özel durum filtresini kötüye kullanma
> Vektörel İstisna İşleme Kötüye Kullanımı
İnsanlar İçin Yığın Taşmaları 102:
> Biz de dahil olmak üzere tahsisi ve serbest bırakılması için yığın veri yapıları ve algoritmalar hakkında daha fazla konu ele alınır.
RtlAllocateHeap () RtlFreeHeap (), birleştirme, blok bölme ve yığınlar, segmentler gibi yapılar,
Lookaside listesi, FreeList, yığın başlıkları, vb
> Güvenli Bağlantı Kesme
> Yığın başlık tanımlama bilgileri
> Sömürü tekniği: bir kenara bakarken bir parçanın üzerine aka 'ListHead yazma'
İnsanlar İçin Yığın Taşmaları 102.5:
> FreeList [0] ınsert attack olarak bilinen başka bir popüler teknik tanıtıldı
> Bağışıklık hata ayıklayıcı için yeni bir yığın eklentisi tanıtıldı
İnsanlar için yığın taşmaları 103 (bu öğretici):
> FreeListİnUse bitmap ile ilgili bazı teoriler ele alındı
> Nicolas Wisemans 'FreeListİnUse bitmap flip attack'tanıtıldı
> RtlCommitRoutine: / ile nasıl başarısız olduğumu açıklayın ve olası bir çözüm sağlayın
> Heaper'da freelistinuse bitmap'in daha fazla analizine izin veren bazı değişiklikler yaptı, oluşturuldu
kolay kod yönetimi için bir Github deposu
Hemen içine girelim! bir 0xc0ff3333 alın ve düşünmeye hazır olun!
FreeListInUse
Freelistınuse, yığın tabanının 0x0158 uzaklığında bulunan 16 baytlık (4 uzun) bir yapıdır ve hangi FreeList[n] girdilerinin boş parçalar içerdiğini tanımlayan bir tablo oluşturan bayt sayısından oluşur.
Freelistınuse bit maskesi Freelist ayırmalar gerçekleştirirken rtlallocateheap() hızlandırmaktır.
Bir ayırma sırasında yığın yöneticisi bit eşlem boyutu ayırma 8 ve / 8 ekleyerek tarar.
Örneğin, 664 baytlık bir boyut ayırdığımızı varsayalım, + 8 (674) ve / 8 = 84 (0x54) olur, böylece yığın yöneticisi FreeList[54] ' de taramaya başlar ve aşağı doğru devam eder.
Bu, yığın yöneticisinin hızını artırmak için tamamen bir optimizasyondur.
Bu bitmap'in neye benzeyebileceğine bir göz atalım:
Tamam, yani 4, toplam 128'i temsil eden 32 bitin her birini (tam olarak Freelistin boyutu) uzatır.
Ama bu şekilde bakmak zor, daha yakından bakalım:
Bir FreeList [n] girişinden ayrılıyorsanız ve bu son yığın ise, bitin işaretini kaldırır.
Benzer şekilde, HeapFree() kullanıyorsanız ve Freelist'e serbest bırakılırsa (lookaside'ın dolu olduğu varsayılarak), biti ayarlayacaktır.
Ntdll!RtlAllocateHeap, kod yeni bit değerini üretmek için geçerli biti 1 ile XOR olacaktır:
Bununla yapılması gereken önemli nokta, 'byteToSet' in her zaman 1 olacağıdır. Bazı xor testleri yapalım.
Bu nedenle, FreeListİnUse girdisinin değerinin son değerin ne olduğuna bağlı olduğunu görebiliriz.
Devam etmeden önce bir saniye durun ve düşünün, bu basit gerçek, xor'un sömürdüğümüz çalışma şekli ile birlikte, Freelistinuse'un tek bir parçasını kontrol edebileceğimiz bir duruma sahip olmayı başarırsak ne olur?
Freelistinuse (bitmap çevirme saldırısı)
FreeListİnUse bit eşlemindeki bitleri çevirmenin yollarını göstermeye başlamadan önce, böyle bir durumun sonuçlarını gösterelim.
FreeList[0x66] girişinde yığın yöneticisi için 0 boş parçanız olduğunu varsayalım.
Bu ne olacak biz buna bakalım:
Esasen, bu giriş için FreeListİnUse 0'a karşılık gelir.
Şimdi bir saniye için FreeListİnUse girişinin aslında 1 olarak ayarlandığını varsayalım.
Daha sonra belirli bir boyut (0x66*0x8/8) veya daha az bir tahsisat talep ettiysek, RtlAllocateHeap() Freelistinuse'u tarar ve bu isteği yerine getirebilecek karşılık gelen girişi arar (lookaside'ın boş olduğu varsayılarak).
Böylece istek devam eder ve 0x00a804a8 değerini 'geçerli' bir yığın olarak döndürür.
Şimdi, FreeList [n] liste girdisinin kendisine işaret edeceğinden, parça yoksa, bu yığın için belirli yönetim yapısı değerlerine yakın bir ofsete sahip bir değer döndürecektir.
Bu noktada, 0x00a804a8'den 216 bayt aşağı doğru üzerine yazmak ve 0x57c ofsetinde saklanan RtlCommitRoutine işaretçisinin üzerine yazmak düz olacaktır.
Ancak, bu saldırı tekniğini test ederken ve RtlAllocateHeap () tersine çevirirken, FreeListİnUse girişini çevirdikten sonra bir şarta ihtiyacımız olacağını fark ettim.
Tahsis ettiğimiz yığın, listedeki son yığın olmalıdır, aksi takdirde RtlAllocateHeap () girişleri yürütmeye ve erişim ihlaline neden olmaya çalışacaktır.
Koda bakalım:
Bakılacak talimat cl, 0x10 testidir. 0x10'un girişteki son parça olduğu anlamına geldiğini biliyoruz.
Bu nedenle, bir FreeList [n] girişine baktığımızda, chunk adresi, başlığın kendisine değil, flink/blink'e işaret edecektir.
Eğer yukarıdaki onay başarısız olursak chunk, belirlemek için incelemek için, parça adresi almak zorundayız ve - 0x8 ve daha sonra, Başlığı parça bayrak uzaklık 0x5+. Bu en iyi görsel olarak gösterilir:
Ve eğer yığını 0x00a804a8'e atacak olsaydık, başlık 0x00a804a0'da olurdu ve yığın bayrağımız 0x00a804a5'te olurdu (imlecin bulunduğu yer):
Bunu bilinen bir değere (0x04 yerine 0x10) değiştirirsek ve Testi 0x10 sabitine karşı çalıştırırsak, kontrol başarısız olur ve atlamayı almaz (tabii ki bir CMP daha iyi bir seçenek olurdu). Sözde kod böyle bir şey olurdu
Bu yüzden onu değiştirelim:
Kullandığımız onaltılık karakter ne olursa olsun, kontrolü atlamak için 8 bitlik bir gösterimin 0x5 konumunda ayarlanmış bir ikili değer içermelidir (örneğin: 00010000).
Tabii ki, bu tam olarak 256/2 olarak çalışır, bu yüzden son parçanın bu kontrolünü atlamak için 128 Olası bayt kullanılabilir. İşte değerler:
Daha önce de belirtildiği gibi, Freelist 0x0178 ile başlar ve 0x0570 ile biter.
Bu nedenle, olası değerler, freelist[n] ' nin kendisi olan bir parçayı ayırmak için asla çalışmayacak olan 0x01-0x05'dir.
Çok başarılı olmanın bir yolu da, ayırdığınız FreeList[n] girişinden önce FreeList[n] girişinde ücretsiz bir parçaya sahip olmaktır.
Bu yığının adresi, soldan 3. konumda yukarıda listelenen bir bayt değeri içermelidir.
Örneğin: bu gibi bir adres - 0XXXXXYYXX burada YY yukarıda listelenen baytlardan herhangi birine eşittir. Yine, bunu görsel olarak hayal edelim:
Önceki girdinin 0x4 yerine 0x32 baytına sahip olduğunu görebiliriz.
Döküm penceresinde buna bir göz atalım
0x32 denetimi atlıyor mu? Evet, çünkü değer yanlış döndürür ve atlamayı kabul etmez.
Böylece atlama yapılmayacak ve bir HeapAlloc(984) gerçekleştirdiğimizi varsayarak bağlantı kesilmeye başlanacaktır.
Bu noktada, eax'ta 0x00490558 değerini döndürmeliyiz.
Flipping the bit
Freelist [n] sahte bir yığın ayırdığımız noktaya bile ulaşmadan önce, bit maskesini bir şekilde kötüye kullanabilmemiz gerekir.
Şimdiye kadar, 3 farklı tekniğin farkındayım:
Bir yığın taşmasını tetikleyin ve yalnızca bir FreeList[n] yığınının boyutunu değiştirin (bu FreeList[n] girişindeki tek yığın olmalıdır).
Çevirmek istediğiniz bir boyut ile değiştirin.
Bu nedenle, serbest taşma parçası tahsis edildiğinde, değiştirilen boyuta karşılık gelen FreeList girişini değiştirecektir.
Bir yığın taşmasını tetikleyin ve boyutu değiştirin, yanıp sönün / yanıp sönün ve yığın bayrağını bir FreeList[n] yığınının 0x10'una ayarlayın(FreeList [n]giriş birden fazla parçaya sahiptir).
Flink'i yaptınız ve aynı değeri yanıp söndünüz ve bayrağı ayarladınız, böylece algoritma A'daki son parça olduklarını düşünecekfreelist girişi.
Bu parçayı ayırdığınızda, değiştirilen boyuta karşılık gelen Freelistınuse girişi 1 olarak değiştirilecektir.
Bir 'inc( ptr) 'ile ilkel bir kontrolü elde edersiniz. Boş bir giriş için Freelistınuse değiştirin.
Giriş boyutunun bir parçasını ayırın-8.
Bir yığın taşmasına ihtiyacınız olduğunu kim söyledi? Bu konuda daha fazlası aşağıda.
Bununla birlikte, Test ve analiz sırasında, bir FreeListİnUse bitflip simüle etmenin önemini anladım.
Bir işlev ekledim bunu yapabilmenizi sağlayan !heaper.
Yığın 0x00490000 için FreeListİnUse görüntülemenizi sağlar:
FreeListİnUse için FreeList [20] girişini değiştireceğiz:
Şimdi yapmamız gereken tek şey, FreeList[0x1c] ' de ücretsiz bir parçanın bulunduğundan ve yığın bayrağı için uyumlu bir değere sahip olduğundan emin olmaktır (yukarıya bakın).
30 Mayıs 2007'de Nicolas Waisman, dailydaves posta listesinde, güvenlik analistlerine, ilkeli kontrol ettiklerini varsayarak bir ınc [r32] talimatını nasıl kullanabileceklerini soran bir bilmece yayınladı.
Ruhu neşelendirmek için eğlenceli bir bilmece yapalım (saat 11'de dostum, bütün gece uykusuzluk).
Bilmece: eski bir Windows 2000'de (istediğiniz SP ne olursa olsun) uzak bir hizmeti kullanmaya çalıştığınızı ve ilkel olanın aşağıdaki ınc [edı] // edı'yi kontrol ettiğinizi söylesin.
Edı için en iyi seçenek ne olurdu?
Nico
Bir ilkelin kontrolünü ele geçirebilseydik ve herhangi bir yerde bir işaretçiyi birden çok kez artırabilseydik, böyle bir saldırı kurabilirdik: aşağıdakileri varsayarsak (varsayımlar kötü biliyorum):
Heapbase 0x00490000
FreeList[n] ' de FreeList [0] dışında hiçbir girişimiz yoktu]
EDX kontrollü bir değerdir
Şu anda sahip olduğumuz talimat şu şekildedir: ınc byte [edx]
Aslında artışı birden çok kez gerçekleştirebiliriz (eğer durum buysa, büyük olasılıkla başka saldırılar gerçekleştirebilirsiniz. Ama bu sadece yapmacık bir örnektir )
1) EDX'İ 0x0049015c'ye ayarlıyoruz ve Freelistinuse'u değiştiriyoruz.
FreeListİnUse görüntülenmesini sağlar:
2) Şimdi, girdinin kendisine (sahte bir yığın) işaret eden freelist[0x20] ' deki parçanın sahte başlık bayrağının doğru şekilde ayarlandığından emin olmalıyız.
Mevcut değerine bir göz atalım:
Yani bu basit, kontrollü ilkelimizi kullanarak 0x10'a yükseltelim:
Şimdi, 0x20 boyutu için bir tahsisat geldiğinde, yığın yöneticisi freelist[20] girişini mutlu bir şekilde iade edecektir! (0x00490278).
Bir yığın taşmasına bile ihtiyacımız yoktu! : )
FreeList [n] ' de herhangi bir ücretsiz parçaya ihtiyacımız yoktu!
Bir ınc byte [r32] talimatı için bir ilkel kontrol etmemiz gerekiyordu.
Belirli bir boyutta 2 tahsisatı kontrol etmemiz gerekiyordu.
Freelist[n] girdisini geçerli bir yığın olarak geri çekmek için bir tane ve ayırıcıyı RtlCommitRoutine() işlev işaretçisini tetikleyerek daha fazla bellek almaya zorlamak için bir tane.
RtlCommitRoutine ile sömürü
RtlCommitRoutine aracılığıyla sömürü benim için (şimdi en azından) başarılı olmadığını kanıtladı.
Bu, 0x57c ofsetinde RtlCommitRoutine işaretçisini aramadan önce bellekte başvurulan (okuma/yazma) görünen yığın yapısındaki birçok işaretçinin parçalanmasından kaynaklanmaktadır.
Bu tür işaretçiler, 0x578 ofsetinde Kilit değişkenini içerir.
None-the-less burada tetiklemeye çalıştığım aşağıdaki kod:
Bununla birlikte, genel bir write4'ünüz varsa veya bir lookaside liste parçasının flink'inin üzerine yazabilir ve döndürebilirseniz, işaretçinin kendisinin üzerine yazabilirsiniz.
heapbase + 0x57c - > heapbase + 0x608 - > RtlCommitRoutime. Heapbase'in 0x0049 olduğunu varsayarsak, flink'i 0x00490608 olarak ayarlamanız ve bu konumda arbitray kodu yazmanız yeterlidir.
Nasıl başarısız oldum?
Başlamak için 0x7c90100b'den başlayarak iki kontrol yapmamız gerekiyor.
Yığın yapısındaki kilit değişken ofsetinde 0xffffffff00000000 işaretçisi kullanarak çekleri geçirebiliriz (bunu yığın taşmasından ayarlayabiliriz).
Daha sonra FreeList[0] ' da boş parça olup olmadığını görmek için bir kontrol var.
Daha sonra, belirli değerler için 0x668 ofsetlerini (FrontEndHeapType referans olduğunu varsayıyorum) ve 0x654'ü kontrol eden başka bir koda geçiyoruz.
Ancak, EAX'IN değerinin muhtemelen yanlış olması nedeniyle bu ofsetlerin yanlış olduğundan şüpheleniyorum (0x00490640).
Sonunda bunu geçersek, sub_7c918ae3'ü çağırırız.
Sub_7c918ae3'te, yığın yapısına potansiyel olarak yanlış ofsetlerde birkaç kontrol daha görebiliriz
Daha sonra,'shellcode'muzu' yürütmesi gereken koda iniyoruz:
Tabii ki bu sadece bir kod yolu ve diğer kod yollarını belirlemek için analiz yapmadım, ancak hızlı bir bakış, değiştirilmiş işaretçimizi yürütmesi gereken işleve sadece iki çağrı ortaya koyuyor ve her iki kod yolu da başlangıçta RtlAllocateHeap ile başlayacak.
Olası bir çözüm
Bu engelleri aşmaya çalışmanın birçok yolu göz önüne alındığında, olası bir çözüm düşündüm: rtlcommitroutine işaretçisine ulaşana ve daha ileri gidene kadar tamponu doğru bir şekilde tahsis edebilir/doldurabiliriz.
Bu hala birkaç işaretçiyi parçalayacak olsa da, umut veriyor çünkü teorik olarak sadece FreeList[0] ' In boş olduğundan emin olmamız ve heapbase+0x570 -> heapbase+0x570'deki işaretçiyle birlikte kırdığımız kilit değişkenini değiştirmemiz gerekecek.
Unutmayın, heapbase mutlaka gerçek bir örnekte boş bayt içermeyebilir.
Freelisti biraz daha incelersek, belirli bir boyutta, Freelistin geri kalanını parçalayacak ve sadece işaretçilerin üzerine yazacak bir tahsisat yapabileceğimizi fark edeceğiz.
Örneğin, bu giriş için hesaplanan boyut 0x41 * 8 - 8 = 0x200 veya 512 bayt tahsisat için maksimum boyut: 512
Şimdi 0x0049057c (RtlCommitRoutine pointer) ve 0x00490380 (FreeList [41] chunk addr)arasındaki mesafe 508 bayttır, ancak FreeList[41] ' den ayırırsak, arabelleği 512 bayta kadar doldurabiliriz, bu yüzden esasen sadece yığın tabanının 0x57c ofsetinde işaretçiyi tahsis eder ve yazarız
Tabii ki, işaretçiyi 0x578'de ve işaretçiyi 0x570'de onarmamız gerekecek, ancak kesinlikle uygun bir seçenek gibi görünüyor
Güncelleme: teorinin gerçekten işe yaradığı ortaya çıktı !
Sahte yığın üstbilgisinin yığın bayrağı ve geçerli boyut için doğru değere sahip olduğundan emin olmalısınız.
Eğer keyfi dağıtımlarını oluşturmak ve onları özgür yeteneği varsa, o zaman (özgür ve dua ihtiyaçlarınıza uygun bir şey bulana kadar[40] FreeList içine yerleştirerek parçalar tutabilir misin?).
Bingo
Örnek saldırı
İşte benim yapmacık FreeListİnUse örneğim.c, bunu derleyebilir ve takip edebilirsiniz! Kodu indirin ve en sevdiğiniz derleyici ile derleyin.
Sadece Dev c++ notunu kullanıyorum: bu işe yaramayabilir (nedenini anlamaya çalışın).
Bağışıklık hata ayıklayıcısında ikili dosyayı açarak ve 0x004016C4 ve 0x00401612 (her ikisi de Heapalloc'a çağrı yapar)üzerinde bir kesme noktası ayarlayarak başlayın ve'!hidedebug Zwqueryınformationprocess'.
FreeListİnUse ve FreeList[n] analiz ederken, 0x3 için girişin serbest parçalarla ayarlandığını görebiliriz.
X010 boyut değerine dikkat edin.
Bu parçanın boyutunun üzerine yazacağız (bir bayt üzerine yazacağız) ve sonra ondan ayıracağız. Bu, ilgili boyut FreeListİnUse girişini, üzerine yazdığımız değerle çevirecektir.
16 bayt ve ardından bir " / " girin. Örnek: AAAAAAAAAAAAAAA|. Boyut değerinin üzerine "\x7c " yazacağız ve enter tuşuna basacağız. İlk kesme noktanızı 0x00401612'de vurmalısınız.
Şimdi parçaları tekrar inceleyelim.
Bu aşamada 0x7c için FreeListİnUse girişinin hala 0 olduğunu, ancak FreeList[0x3] adresinde bulunan yığıntıda ki boyut değerinin üzerine yazdığımızı unutmayın]:
F8 tuşuna basarak, HeapAlloc() üzerine adım atıyoruz ve FreeList ve FreeListİnUse tablosunu görüntülüyoruz ve 0x3 için FreeListİnUse girişinin ayarlandığını ve bunun için hiçbir parça olmadığını fark ediyoruz:
Amacımız 0x7c için biti çevirmekti, bunun olup olmadığını görelim:
Şimdi, ikinci kırılma noktamızda, kod 0x7c girişinden ayrılmaya çalışacaktır, ancak 0x7c girişinden hemen önce bir parça boşaltmadıkça bu başarısız olacaktır.
Uygulamayı şimdi çalıştırın ve 0x004016c4'te kesme noktanıza ulaştıktan sonra 0x7b'ye serbest bırakılan bir parça göreceksiniz.
İlgili Freelistinuse'un da ayarlandığına bahse girebilirsiniz.
Şimdi f8 tuşuna basın ve işlev çağrısının üzerine gelin. 0x00490558 (FreeList[0x7c] kendisi için ayarlanmış EAX değerini görmelisiniz!).
Yapılması gereken tek şey, 0x57c ofsetinde bulunan RtlCommitRoutine üzerine yazmaktır.
0x57c-0x558 = işaretçiyi kontrol etmek için 36 bayt + 4 bayt.
Tahsisatı yaptıktan sonra, 0x558 ofsetindeki tüm işaretçileri yok ettiğinizi unutmayın:
Ama, "\x41" * 36 + "\x44" * 4 işaretçiyi tüm D'ler olacak şekilde ayarlayarak, gets() ile yapının üzerine yazalım:
Bu noktada, bir yığın uzantısını tetiklemeniz gerekir (yığın segmentinden daha fazla bellek kaydetme).
Bu, FreeList[0] ' daki herhangi bir parçadan daha büyük bir boyuta sahip bir HeapAlloc() tetikleyerek kolayca yapılır.
Elbette, 0x7c'den sonra FreeList[n] ' den herhangi bir boyut parçasını ayıramazsınız, çünkü bunlar artık mevcut değildir.
Bu yürüyüşteki tüm cevapları vermedim çünkü okuyucuların kendi ayakları üzerinde düşünmelerini ve neden yukarıda sunduğum kodla tam olarak işe yaramayacağını anlamalarını istiyorum.
İpucu: boyuta bakın (olası bir çözüm olarak bahsettim)
Translator: @Qgenays
Source: https://www.fuzzysecurity.com/tutorials/mr_me/5.html
Selam millet! Bir kez daha geri döndüm ve zamanın kumlarına gömülmek istemediğim yığın sömürüsü için başka bir önemli tekniği tartışmak için buradayım.
Şanslıyım yani bu konuda daha idare edebilirim Noel/Yeni yıl içinde biraz zaman var.
İnsanlar için yığın taşmaları 101 [NT 5.1 (Windows XP SP1) altında yığın taşmalarının temelleri)]:
> Temel ' unlink () ' sömürü ve işlev işaretçisi üzerine yazar
> İşlenmeyen özel durum filtresini kötüye kullanma
> Vektörel İstisna İşleme Kötüye Kullanımı
İnsanlar İçin Yığın Taşmaları 102:
> Biz de dahil olmak üzere tahsisi ve serbest bırakılması için yığın veri yapıları ve algoritmalar hakkında daha fazla konu ele alınır.
RtlAllocateHeap () RtlFreeHeap (), birleştirme, blok bölme ve yığınlar, segmentler gibi yapılar,
Lookaside listesi, FreeList, yığın başlıkları, vb
> Güvenli Bağlantı Kesme
> Yığın başlık tanımlama bilgileri
> Sömürü tekniği: bir kenara bakarken bir parçanın üzerine aka 'ListHead yazma'
İnsanlar İçin Yığın Taşmaları 102.5:
> FreeList [0] ınsert attack olarak bilinen başka bir popüler teknik tanıtıldı
> Bağışıklık hata ayıklayıcı için yeni bir yığın eklentisi tanıtıldı
İnsanlar için yığın taşmaları 103 (bu öğretici):
> FreeListİnUse bitmap ile ilgili bazı teoriler ele alındı
> Nicolas Wisemans 'FreeListİnUse bitmap flip attack'tanıtıldı
> RtlCommitRoutine: / ile nasıl başarısız olduğumu açıklayın ve olası bir çözüm sağlayın
> Heaper'da freelistinuse bitmap'in daha fazla analizine izin veren bazı değişiklikler yaptı, oluşturuldu
kolay kod yönetimi için bir Github deposu
Hemen içine girelim! bir 0xc0ff3333 alın ve düşünmeye hazır olun!
FreeListInUse
Freelistınuse, yığın tabanının 0x0158 uzaklığında bulunan 16 baytlık (4 uzun) bir yapıdır ve hangi FreeList[n] girdilerinin boş parçalar içerdiğini tanımlayan bir tablo oluşturan bayt sayısından oluşur.
Freelistınuse bit maskesi Freelist ayırmalar gerçekleştirirken rtlallocateheap() hızlandırmaktır.
Bir ayırma sırasında yığın yöneticisi bit eşlem boyutu ayırma 8 ve / 8 ekleyerek tarar.
Örneğin, 664 baytlık bir boyut ayırdığımızı varsayalım, + 8 (674) ve / 8 = 84 (0x54) olur, böylece yığın yöneticisi FreeList[54] ' de taramaya başlar ve aşağı doğru devam eder.
Bu, yığın yöneticisinin hızını artırmak için tamamen bir optimizasyondur.
Bu bitmap'in neye benzeyebileceğine bir göz atalım:
Tamam, yani 4, toplam 128'i temsil eden 32 bitin her birini (tam olarak Freelistin boyutu) uzatır.
Ama bu şekilde bakmak zor, daha yakından bakalım:
Bir FreeList [n] girişinden ayrılıyorsanız ve bu son yığın ise, bitin işaretini kaldırır.
Benzer şekilde, HeapFree() kullanıyorsanız ve Freelist'e serbest bırakılırsa (lookaside'ın dolu olduğu varsayılarak), biti ayarlayacaktır.
Ntdll!RtlAllocateHeap, kod yeni bit değerini üretmek için geçerli biti 1 ile XOR olacaktır:
Kod:
; FreeListInUse modification:
7C910CEE . 0FB70E MOVZX ECX,WORD PTR DS:[ESI] ; ecx = chunk->size
7C910CF1 . 8BC1 MOV EAX,ECX ; eax = ecx
7C910CF3 . C1E8 03 SHR EAX,3 ; ListOffset = size / 8
7C910CF6 . 8985 28FFFFFF MOV DWORD PTR SS:[EBP-D8],EAX
7C910CFC . 83E1 07 AND ECX,7 ; entryByte = size & 7, ecx = entryByte
7C910CFF . 33D2 XOR EDX,EDX ; edx = NULL
7C910D01 . 42 INC EDX ; edx = 0x01
7C910D02 . D3E2 SHL EDX,CL ; byteToSet = 1 << entryByte
7C910D04 . 8995 04FFFFFF MOV DWORD PTR SS:[EBP-FC],EDX
7C910D0A . 8D8418 5801000>LEA EAX,DWORD PTR DS:[EAX+EBX+158] ; eax = 0x004907A6 FreeListInUse Offset
7C910D11 . 33C9 XOR ECX,ECX ; ecx = NULL
7C910D13 . 8A08 MOV CL,BYTE PTR DS:[EAX] ; current_val = FreeListInUse[
; FreeListInUseOffset ]
7C910D15 . 33CA XOR ECX,EDX ; current_val = xor(current_val,byteToSet)
7C910D17 . 8808 MOV BYTE PTR DS:[EAX],CL ; FreeListInUse[ FreeListInUseOffset ] =
; current_val
Bununla yapılması gereken önemli nokta, 'byteToSet' in her zaman 1 olacağıdır. Bazı xor testleri yapalım.
Kod:
>>> current_val = 0
>>> byteToSet = 1
>>> current_val^byteToSet
1
>>> current_val = 1
>>> current_val^byteToSet
0
Devam etmeden önce bir saniye durun ve düşünün, bu basit gerçek, xor'un sömürdüğümüz çalışma şekli ile birlikte, Freelistinuse'un tek bir parçasını kontrol edebileceğimiz bir duruma sahip olmayı başarırsak ne olur?
Freelistinuse (bitmap çevirme saldırısı)
FreeListİnUse bit eşlemindeki bitleri çevirmenin yollarını göstermeye başlamadan önce, böyle bir durumun sonuçlarını gösterelim.
FreeList[0x66] girişinde yığın yöneticisi için 0 boş parçanız olduğunu varsayalım.
Bu ne olacak biz buna bakalım:
Kod:
FreeList[0x066] 0x00a804a8 -> [ flink: 0x00a804a8 | blink: 0x00a804a8 ]
Şimdi bir saniye için FreeListİnUse girişinin aslında 1 olarak ayarlandığını varsayalım.
Daha sonra belirli bir boyut (0x66*0x8/8) veya daha az bir tahsisat talep ettiysek, RtlAllocateHeap() Freelistinuse'u tarar ve bu isteği yerine getirebilecek karşılık gelen girişi arar (lookaside'ın boş olduğu varsayılarak).
Böylece istek devam eder ve 0x00a804a8 değerini 'geçerli' bir yığın olarak döndürür.
Şimdi, FreeList [n] liste girdisinin kendisine işaret edeceğinden, parça yoksa, bu yığın için belirli yönetim yapısı değerlerine yakın bir ofsete sahip bir değer döndürecektir.
Bu noktada, 0x00a804a8'den 216 bayt aşağı doğru üzerine yazmak ve 0x57c ofsetinde saklanan RtlCommitRoutine işaretçisinin üzerine yazmak düz olacaktır.
Ancak, bu saldırı tekniğini test ederken ve RtlAllocateHeap () tersine çevirirken, FreeListİnUse girişini çevirdikten sonra bir şarta ihtiyacımız olacağını fark ettim.
Tahsis ettiğimiz yığın, listedeki son yığın olmalıdır, aksi takdirde RtlAllocateHeap () girişleri yürütmeye ve erişim ihlaline neden olmaya çalışacaktır.
Koda bakalım:
Bakılacak talimat cl, 0x10 testidir. 0x10'un girişteki son parça olduğu anlamına geldiğini biliyoruz.
Bu nedenle, bir FreeList [n] girişine baktığımızda, chunk adresi, başlığın kendisine değil, flink/blink'e işaret edecektir.
Eğer yukarıdaki onay başarısız olursak chunk, belirlemek için incelemek için, parça adresi almak zorundayız ve - 0x8 ve daha sonra, Başlığı parça bayrak uzaklık 0x5+. Bu en iyi görsel olarak gösterilir:
Ve eğer yığını 0x00a804a8'e atacak olsaydık, başlık 0x00a804a0'da olurdu ve yığın bayrağımız 0x00a804a5'te olurdu (imlecin bulunduğu yer):
Bunu bilinen bir değere (0x04 yerine 0x10) değiştirirsek ve Testi 0x10 sabitine karşı çalıştırırsak, kontrol başarısız olur ve atlamayı almaz (tabii ki bir CMP daha iyi bir seçenek olurdu). Sözde kod böyle bir şey olurdu
Kod:
if (chunk->flag & 0x10) <= 0:
walk_freelist_entry()
Kullandığımız onaltılık karakter ne olursa olsun, kontrolü atlamak için 8 bitlik bir gösterimin 0x5 konumunda ayarlanmış bir ikili değer içermelidir (örneğin: 00010000).
Tabii ki, bu tam olarak 256/2 olarak çalışır, bu yüzden son parçanın bu kontrolünü atlamak için 128 Olası bayt kullanılabilir. İşte değerler:
Kod:
0x10-0x1f
0x30-0x3f
0x50-0x5f
0x70-0x7f
0x90-0x9f
0xb0-0xbf
0xd0-0xdf
0xf0-0xff
Bu nedenle, olası değerler, freelist[n] ' nin kendisi olan bir parçayı ayırmak için asla çalışmayacak olan 0x01-0x05'dir.
Çok başarılı olmanın bir yolu da, ayırdığınız FreeList[n] girişinden önce FreeList[n] girişinde ücretsiz bir parçaya sahip olmaktır.
Bu yığının adresi, soldan 3. konumda yukarıda listelenen bir bayt değeri içermelidir.
Örneğin: bu gibi bir adres - 0XXXXXYYXX burada YY yukarıda listelenen baytlardan herhangi birine eşittir. Yine, bunu görsel olarak hayal edelim:
Önceki girdinin 0x4 yerine 0x32 baytına sahip olduğunu görebiliriz.
Döküm penceresinde buna bir göz atalım
0x32 denetimi atlıyor mu? Evet, çünkü değer yanlış döndürür ve atlamayı kabul etmez.
Kod:
>>> print (0x32 & 0x10) <= 0
False
Bu noktada, eax'ta 0x00490558 değerini döndürmeliyiz.
Flipping the bit
Freelist [n] sahte bir yığın ayırdığımız noktaya bile ulaşmadan önce, bit maskesini bir şekilde kötüye kullanabilmemiz gerekir.
Şimdiye kadar, 3 farklı tekniğin farkındayım:
Bir yığın taşmasını tetikleyin ve yalnızca bir FreeList[n] yığınının boyutunu değiştirin (bu FreeList[n] girişindeki tek yığın olmalıdır).
Çevirmek istediğiniz bir boyut ile değiştirin.
Bu nedenle, serbest taşma parçası tahsis edildiğinde, değiştirilen boyuta karşılık gelen FreeList girişini değiştirecektir.
Bir yığın taşmasını tetikleyin ve boyutu değiştirin, yanıp sönün / yanıp sönün ve yığın bayrağını bir FreeList[n] yığınının 0x10'una ayarlayın(FreeList [n]giriş birden fazla parçaya sahiptir).
Flink'i yaptınız ve aynı değeri yanıp söndünüz ve bayrağı ayarladınız, böylece algoritma A'daki son parça olduklarını düşünecekfreelist girişi.
Bu parçayı ayırdığınızda, değiştirilen boyuta karşılık gelen Freelistınuse girişi 1 olarak değiştirilecektir.
Bir 'inc( ptr) 'ile ilkel bir kontrolü elde edersiniz. Boş bir giriş için Freelistınuse değiştirin.
Giriş boyutunun bir parçasını ayırın-8.
Bir yığın taşmasına ihtiyacınız olduğunu kim söyledi? Bu konuda daha fazlası aşağıda.
Bununla birlikte, Test ve analiz sırasında, bir FreeListİnUse bitflip simüle etmenin önemini anladım.
Bir işlev ekledim bunu yapabilmenizi sağlayan !heaper.
Yığın 0x00490000 için FreeListİnUse görüntülemenizi sağlar:
FreeListİnUse için FreeList [20] girişini değiştireceğiz:
Şimdi yapmamız gereken tek şey, FreeList[0x1c] ' de ücretsiz bir parçanın bulunduğundan ve yığın bayrağı için uyumlu bir değere sahip olduğundan emin olmaktır (yukarıya bakın).
30 Mayıs 2007'de Nicolas Waisman, dailydaves posta listesinde, güvenlik analistlerine, ilkeli kontrol ettiklerini varsayarak bir ınc [r32] talimatını nasıl kullanabileceklerini soran bir bilmece yayınladı.
Ruhu neşelendirmek için eğlenceli bir bilmece yapalım (saat 11'de dostum, bütün gece uykusuzluk).
Bilmece: eski bir Windows 2000'de (istediğiniz SP ne olursa olsun) uzak bir hizmeti kullanmaya çalıştığınızı ve ilkel olanın aşağıdaki ınc [edı] // edı'yi kontrol ettiğinizi söylesin.
Edı için en iyi seçenek ne olurdu?
Nico
Bir ilkelin kontrolünü ele geçirebilseydik ve herhangi bir yerde bir işaretçiyi birden çok kez artırabilseydik, böyle bir saldırı kurabilirdik: aşağıdakileri varsayarsak (varsayımlar kötü biliyorum):
Heapbase 0x00490000
FreeList[n] ' de FreeList [0] dışında hiçbir girişimiz yoktu]
EDX kontrollü bir değerdir
Şu anda sahip olduğumuz talimat şu şekildedir: ınc byte [edx]
Aslında artışı birden çok kez gerçekleştirebiliriz (eğer durum buysa, büyük olasılıkla başka saldırılar gerçekleştirebilirsiniz. Ama bu sadece yapmacık bir örnektir )
1) EDX'İ 0x0049015c'ye ayarlıyoruz ve Freelistinuse'u değiştiriyoruz.
FreeListİnUse görüntülenmesini sağlar:
2) Şimdi, girdinin kendisine (sahte bir yığın) işaret eden freelist[0x20] ' deki parçanın sahte başlık bayrağının doğru şekilde ayarlandığından emin olmalıyız.
Mevcut değerine bir göz atalım:
Yani bu basit, kontrollü ilkelimizi kullanarak 0x10'a yükseltelim:
Şimdi, 0x20 boyutu için bir tahsisat geldiğinde, yığın yöneticisi freelist[20] girişini mutlu bir şekilde iade edecektir! (0x00490278).
Bir yığın taşmasına bile ihtiyacımız yoktu! : )
FreeList [n] ' de herhangi bir ücretsiz parçaya ihtiyacımız yoktu!
Bir ınc byte [r32] talimatı için bir ilkel kontrol etmemiz gerekiyordu.
Belirli bir boyutta 2 tahsisatı kontrol etmemiz gerekiyordu.
Freelist[n] girdisini geçerli bir yığın olarak geri çekmek için bir tane ve ayırıcıyı RtlCommitRoutine() işlev işaretçisini tetikleyerek daha fazla bellek almaya zorlamak için bir tane.
RtlCommitRoutine ile sömürü
RtlCommitRoutine aracılığıyla sömürü benim için (şimdi en azından) başarılı olmadığını kanıtladı.
Bu, 0x57c ofsetinde RtlCommitRoutine işaretçisini aramadan önce bellekte başvurulan (okuma/yazma) görünen yığın yapısındaki birçok işaretçinin parçalanmasından kaynaklanmaktadır.
Bu tür işaretçiler, 0x578 ofsetinde Kilit değişkenini içerir.
None-the-less burada tetiklemeye çalıştığım aşağıdaki kod:
Kod:
7C918B26 . 8B88 7C050000 MOV ECX,DWORD PTR DS:[EAX+57C]
7C918B2C . 85C9 TEST ECX,ECX
7C918B2E . 0F85 9F210300 JNZ ntdll.7C94ACD3
;jump taken if ecx != 0
7C94ACD3 > 57 PUSH EDI
7C94ACD4 . 8D55 0C LEA EDX,DWORD PTR SS:[EBP+C]
7C94ACD7 . 52 PUSH EDX
7C94ACD8 . 50 PUSH EAX
7C94ACD9 . FFD1 CALL ECX <- code execution
heapbase + 0x57c - > heapbase + 0x608 - > RtlCommitRoutime. Heapbase'in 0x0049 olduğunu varsayarsak, flink'i 0x00490608 olarak ayarlamanız ve bu konumda arbitray kodu yazmanız yeterlidir.
Nasıl başarısız oldum?
Başlamak için 0x7c90100b'den başlayarak iki kontrol yapmamız gerekiyor.
Yığın yapısındaki kilit değişken ofsetinde 0xffffffff00000000 işaretçisi kullanarak çekleri geçirebiliriz (bunu yığın taşmasından ayarlayabiliriz).
Daha sonra FreeList[0] ' da boş parça olup olmadığını görmek için bir kontrol var.
Daha sonra, belirli değerler için 0x668 ofsetlerini (FrontEndHeapType referans olduğunu varsayıyorum) ve 0x654'ü kontrol eden başka bir koda geçiyoruz.
Ancak, EAX'IN değerinin muhtemelen yanlış olması nedeniyle bu ofsetlerin yanlış olduğundan şüpheleniyorum (0x00490640).
Sonunda bunu geçersek, sub_7c918ae3'ü çağırırız.
Sub_7c918ae3'te, yığın yapısına potansiyel olarak yanlış ofsetlerde birkaç kontrol daha görebiliriz
Daha sonra,'shellcode'muzu' yürütmesi gereken koda iniyoruz:
Tabii ki bu sadece bir kod yolu ve diğer kod yollarını belirlemek için analiz yapmadım, ancak hızlı bir bakış, değiştirilmiş işaretçimizi yürütmesi gereken işleve sadece iki çağrı ortaya koyuyor ve her iki kod yolu da başlangıçta RtlAllocateHeap ile başlayacak.
Olası bir çözüm
Bu engelleri aşmaya çalışmanın birçok yolu göz önüne alındığında, olası bir çözüm düşündüm: rtlcommitroutine işaretçisine ulaşana ve daha ileri gidene kadar tamponu doğru bir şekilde tahsis edebilir/doldurabiliriz.
Bu hala birkaç işaretçiyi parçalayacak olsa da, umut veriyor çünkü teorik olarak sadece FreeList[0] ' In boş olduğundan emin olmamız ve heapbase+0x570 -> heapbase+0x570'deki işaretçiyle birlikte kırdığımız kilit değişkenini değiştirmemiz gerekecek.
Unutmayın, heapbase mutlaka gerçek bir örnekte boş bayt içermeyebilir.
Freelisti biraz daha incelersek, belirli bir boyutta, Freelistin geri kalanını parçalayacak ve sadece işaretçilerin üzerine yazacak bir tahsisat yapabileceğimizi fark edeceğiz.
Kod:
Heap dump 0x00490000, item 75
Address=0x00490380
Chunks=[041] 0x00490380 -> [ 0x00490380 | 0x00490380 ]
Şimdi 0x0049057c (RtlCommitRoutine pointer) ve 0x00490380 (FreeList [41] chunk addr)arasındaki mesafe 508 bayttır, ancak FreeList[41] ' den ayırırsak, arabelleği 512 bayta kadar doldurabiliriz, bu yüzden esasen sadece yığın tabanının 0x57c ofsetinde işaretçiyi tahsis eder ve yazarız
Tabii ki, işaretçiyi 0x578'de ve işaretçiyi 0x570'de onarmamız gerekecek, ancak kesinlikle uygun bir seçenek gibi görünüyor
Güncelleme: teorinin gerçekten işe yaradığı ortaya çıktı !
Sahte yığın üstbilgisinin yığın bayrağı ve geçerli boyut için doğru değere sahip olduğundan emin olmalısınız.
Eğer keyfi dağıtımlarını oluşturmak ve onları özgür yeteneği varsa, o zaman (özgür ve dua ihtiyaçlarınıza uygun bir şey bulana kadar[40] FreeList içine yerleştirerek parçalar tutabilir misin?).
Bingo
Örnek saldırı
İşte benim yapmacık FreeListİnUse örneğim.c, bunu derleyebilir ve takip edebilirsiniz! Kodu indirin ve en sevdiğiniz derleyici ile derleyin.
Sadece Dev c++ notunu kullanıyorum: bu işe yaramayabilir (nedenini anlamaya çalışın).
Kod:
/*
FreeListInUse bitmap flip example
technique by Nicolas Wiseman
PoC example by Steven Seeley
Note: we have to play around with a few more al********s/frees than I wanted to
due to the lookaside. Np this is just an example..
*/
#include <stdio.h>
#include <windows.h>
int main(int argc,char *argv[])
{
char *a,*b,*c,*d,*e,*f,*g,*h,*i,*j,*k,*l,*m,*trigger;
char *x, *y, *w, *u, *z, *q, *o;
long *hHeap;
hHeap = HeapCreate(0x00040000,0,0);
a = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
b = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
c = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
d = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
e = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
f = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16); // forces onto freelist[0x3] because two busy chunks exist
g = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16); // around the chunk
// allocate for freelist[0x7b]
z = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976);
x = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976);
y = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976);
w = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976);
q = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976);
u = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976); // forces onto freelist[0x7b] because two busy chunks exist
o = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,976); // around the chunk
// fill lookaside[0x3]
HeapFree(hHeap, 0, a);
HeapFree(hHeap, 0, b);
HeapFree(hHeap, 0, c);
HeapFree(hHeap, 0, d);
// insert into freelist[0x3]
HeapFree(hHeap, 0, f);
printf("(+) Chunk e: 0x%08x\n",e);
printf("(+) Fill chunk e (using 16 bytes), overflowing into chunk f (0x%08x) by 1 byte:\n",e);
printf("(+) Overflow with size 0x7c (AAAAAAAAAAAAAAAA|)...\n");
gets(e);
// remove the chunks on the lookaside
h = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
i = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
j = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
k = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
// this is where we flip the bitmap. FreelistInUse [0x7c] = 1
l = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
// fill lookaside[0x7b]
HeapFree(hHeap, 0, z);
HeapFree(hHeap, 0, x);
HeapFree(hHeap, 0, y);
HeapFree(hHeap, 0, w);
// insert into freelist[0x7b]
HeapFree(hHeap, 0, u);
// return the chunk that points to itself in freelist[0x7c], so we can overwrite management structures...
m = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,984);
printf("(+) Fill chunk m and destroy the management structure:\n");
gets(m);
// force the heap to be extended triggering RtlCommitRoutine
trigger = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,4096);
exit(0);
}
FreeListİnUse ve FreeList[n] analiz ederken, 0x3 için girişin serbest parçalarla ayarlandığını görebiliriz.
X010 boyut değerine dikkat edin.
Bu parçanın boyutunun üzerine yazacağız (bir bayt üzerine yazacağız) ve sonra ondan ayıracağız. Bu, ilgili boyut FreeListİnUse girişini, üzerine yazdığımız değerle çevirecektir.
16 bayt ve ardından bir " / " girin. Örnek: AAAAAAAAAAAAAAA|. Boyut değerinin üzerine "\x7c " yazacağız ve enter tuşuna basacağız. İlk kesme noktanızı 0x00401612'de vurmalısınız.
Şimdi parçaları tekrar inceleyelim.
Bu aşamada 0x7c için FreeListİnUse girişinin hala 0 olduğunu, ancak FreeList[0x3] adresinde bulunan yığıntıda ki boyut değerinin üzerine yazdığımızı unutmayın]:
F8 tuşuna basarak, HeapAlloc() üzerine adım atıyoruz ve FreeList ve FreeListİnUse tablosunu görüntülüyoruz ve 0x3 için FreeListİnUse girişinin ayarlandığını ve bunun için hiçbir parça olmadığını fark ediyoruz:
Amacımız 0x7c için biti çevirmekti, bunun olup olmadığını görelim:
Şimdi, ikinci kırılma noktamızda, kod 0x7c girişinden ayrılmaya çalışacaktır, ancak 0x7c girişinden hemen önce bir parça boşaltmadıkça bu başarısız olacaktır.
Uygulamayı şimdi çalıştırın ve 0x004016c4'te kesme noktanıza ulaştıktan sonra 0x7b'ye serbest bırakılan bir parça göreceksiniz.
İlgili Freelistinuse'un da ayarlandığına bahse girebilirsiniz.
Şimdi f8 tuşuna basın ve işlev çağrısının üzerine gelin. 0x00490558 (FreeList[0x7c] kendisi için ayarlanmış EAX değerini görmelisiniz!).
Yapılması gereken tek şey, 0x57c ofsetinde bulunan RtlCommitRoutine üzerine yazmaktır.
0x57c-0x558 = işaretçiyi kontrol etmek için 36 bayt + 4 bayt.
Tahsisatı yaptıktan sonra, 0x558 ofsetindeki tüm işaretçileri yok ettiğinizi unutmayın:
Ama, "\x41" * 36 + "\x44" * 4 işaretçiyi tüm D'ler olacak şekilde ayarlayarak, gets() ile yapının üzerine yazalım:
Bu noktada, bir yığın uzantısını tetiklemeniz gerekir (yığın segmentinden daha fazla bellek kaydetme).
Bu, FreeList[0] ' daki herhangi bir parçadan daha büyük bir boyuta sahip bir HeapAlloc() tetikleyerek kolayca yapılır.
Elbette, 0x7c'den sonra FreeList[n] ' den herhangi bir boyut parçasını ayıramazsınız, çünkü bunlar artık mevcut değildir.
Bu yürüyüşteki tüm cevapları vermedim çünkü okuyucuların kendi ayakları üzerinde düşünmelerini ve neden yukarıda sunduğum kodla tam olarak işe yaramayacağını anlamalarını istiyorum.
İpucu: boyuta bakın (olası bir çözüm olarak bahsettim)
Translator: @Qgenays
Source: https://www.fuzzysecurity.com/tutorials/mr_me/5.html