Merhaba arkadaşlar. Uzun bir süredir üzerinde çalıştığım konu hakkında bir şeyler anlatabilecek seviyeye gelmenin heyecanı ile bu yazıya başlıyorum. Yazıya geçmeden önce söylemem gereken şeyler var… Buffer Overflow saldırıları teoride çok basit gözükse de, iş uygulamaya geldiğinde fazlasıyla kompleks hale gelebiliyor. Özellikle bu, dinamik hafıza alanı bozma olunca ve üstüne üstlük linked list’lerin iyice işin içerisine girdiği zaman beyin nöronları teker teker intihar etmeye başlıyor  Bu noktada “hatasız kul olmaz” diyor ve böylesine soyut bir konuda herkesin hata yapabileceğini belirtiyoruz. Fark ettiğiniz her türlü hatalı anlatım durumunda iletişime geçmeniz, benim ve diğer arkadaşlar için hayati önem taşıdığını söyleyip ufaktan konumuza geçelim. Önceki yazımızda olduğu gibi bu konunun anlatımında farklı bir kaynaktaki egzersizi kullanacağız. İlgili siteye buradan erişebilirsiniz.

Dinamik hafıza tahsilatı için kullanılan ‘malloc()’ fonksiyonunun farklı türevlerinden bu yazıda bahsetmiştik. İlgili egzersizde de ‘dlmalloc()’ kullanılmaktadır. Bizden istenilen ise heap metadata alanını modifiye etme yoluyla programın çalışmasını değiştirmek. Yani winner() fonksiyonunu çağırmak. Peki bunu nasıl yapabiliriz? Öncelikle dlmalloc hakkında gerekli bilgilere bakalım. Aşağıdaki şekillerde tahsil edilmiş chunk ve free chunk gösterilmiştir. Dlmalloc’ta her chunk free olup olmamasına bakılmaksızın 4 tane 4 bayt’lık metadata alanları bulunur. Bu 16 baytın ilk 4 baytlık alan ‘prev_size’ alanıdır. Bu alan, bir önceki chunk’ın boyutunu gösterir ve sadece bir önceki chunk free halde ise kullanılır. Free değilse kullanıcı veri alanı olarak kullanılır. Sonraki 4 baytlık ‘size’ alanı ise geçerli chunk’ın boyutunu gösterir.

Tahsis edilmiş chunk:

_config.yml

free chunk:

_config.yml

Yeri gelmişken ‘size’ alanı hakkında önemli bir ayrıntı verelim. Size alanının en düşük değerli biti 1 ise önceki chunk kullanımda demek oluyor. Aksine 0 olursa önceki chunk kullanımda değil yani free haldedir. Aynı şekilde bir sonraki en düşük değerli biti chunk’ın mmap ile tahsil edilip edilmediğini belirtir. Bu bitler PREV_INUSE ve IS_MMAPPED olarak adlandırılır. Geriye kalan 8 baytlık FD ve BK alanları, sadece chunk free ise kullanılır. Bildiğimiz gibi free halde bulunan chunklar çift bağlı listelerde bir birlerine bağlı şekilde bulunurlar. Bu listeler de ‘Bin’ alanlarında tutulurlar. Boş bir chunk’ın önceki ve sonraki chunk’lara bağlanmasını, FD ve BK pointer’ları sağlar. FD yani forward pointer, gelecek boş chunk’ı işaret eder. Bk yani backward pointer ise önceki boş chunk’ı işaret eder.

Bazı önemli bilgileri hatırladıktan sonra çözümün nasıl olacağını konuşalım. İlk aşamada sömürü yapacağımız kodu adım adım inceleyelim. Main fonksiyona baktığımızda, 3 tane char pointer oluşturulmuş ve daha sonra 32 şer baytlık 3 ayrı dinamik alan tahsilatı yapılmış. Tahsilat sonucu ayrılan alanların başlangıç noktasını işaret eden adresler char pointerlara aktarılmış. Daha sonra strcpy() fonksiyonu ile kullanıcıdan alınan değerler tahsil edilmiş alanlara veri olarak kopyalanıyor. Son olarak tahsili yapılmış kullanımda olan chunklar free() fonksiyonu ile boşaltılıyor.

Bir de winner() fonksiyonu bulunuyor. Ama herhangi bir yerde çağrılmamış. Bu haliyle beklenilen şekilde bu kod çalıştığında bize “dynamite failed?” çıktısını verecek. Bu noktada bizim yapmamız gerek winner() fonksiyonunu yazdırabilmek.

Kodu inceledik anladık ve hemen açığın nerden kaynaklandığını fark ettik. Bu kodda kullanıcıdan girdi alırken herhangi bir kontrol yapılmamış. Dolayısıyla taşırma olayı burdan kaynaklanıyor. Biz bu yazıda tampon A’yı taşırıcaz ve tampon B’nin metadata alanını bozup program akışını değiştiricez.

Açığı keşfettik, tamponu taşırdık, sonraki tamponu bozduk ve sıra da tetiğe basma kaldı. Tetikleme işlemini free() fonksiyonu sağlayacak. Bunu yaparken kendi içerisinde bulunan başka bir işlev olan ‘unlink()’ sayesinde yapacak. Evet konunun özü ve can alıcı noktası burası. Şimdi free() ve unlink() süreçlerini inceleyelim.

(Tam emin olmamakla birlikte..) Free() fonksiyonu tahsil edilmiş kullanımda olan chunkları boşaltarak diğer boş chunk’larla birleştirir. Eğer hali hazırda kullanımda olmayan boş bir chunk free() işlemine tabi tutulursa diğer bir işlev olan unlink() çağrılarak ilgili chunk bağlı listeden kaldırılır. Chunk_free() ve unlink() işlevlerinin ilgili kaynak kod bölümleri aşağıdaki gibidir.

INTERNAL_SIZE_T hd = p->size;
 ...
 if (!hd & PREV_INUSE))     /* consolidate backward */    /* [A] */
 {
   prevsz = p->prev_size;
   p = chunk_at_offset(p, -(long)prevsz);                 /* [B] */
   sz += prevsz;
 
   if (p->fd == last_remainder(ar_ptr))
     islr = 1;
   else
     unlink(p, bck, fwd);
 }
#define unlink( P, BK, FD ) {            \
    BK = P->bk;                          \
    FD = P->fd;                          \
    FD->bk = BK;                         \
    BK->fd = FD;                         \
}

unlink() işlevi ile free chunk’ı listeden kaldırma işlemi, çift bağlı listelerdeki node silme işlemi ile aynıdır.

_config.yml

Bu egzersizde bir free chunk bulunmuyor. Yani unlink() ile bağlantılarını kaldırabileceğimiz bir chunk gözükmüyor. Yalnızca 3 tane kullanımda olan chunk bulunuyor. Biz de bu işlevi kullanabilmek için sahte bir chunk oluşturacağız. Bu sahte chunk, B tamponu içerisinde bulunacak. Free halde olduğu için de FD ve BK alanları bulunacak.

Tampon A’yı taşırıp tampon B’nin metadata alanını modifiye ederken size alanın en düşük değerlikli bitini 0 yapmaya dikkat etmeliyiz. Çünkü bir önceki chunk yani sahte chunk’ımızın kullanımda olmadığını belirterek bilgisayarı aldatıp unlink() işlevini çağırmasını sağlayacağız. Neden sahte chunk free halde kalmıyor diye düşünenler olabilir. Bunun nedeni dlmalloc’ta bir chunk serbest bırakıldığında aynı zamanda bitişik chunk da (sahte chunk) serbest halde ise iki parça tek büyük parçaya birleştirilir.

Size alanının en düşük değerlikli bitinin 0 olabilmesi için negatif bir değer vereceğiz. Bu değeri -4 olarak alacağız. Nedeniz ise bitişik chunk’ın boyut alanının, tampon A’nın boyut alanından 32 bayt önce değil de tampon B’nin prev_size alanından 4 bayt önce olduğunu düşünmesini istiyoruz.

Tampon B’nin prev_size alanını ise -8 yapıyoruz. Bunun nedeni ise dlmalloc’ta, önceki chunk’ın konumu, önceki chunk’ın boyutunu mevcut chunk’ın pointer’ından çıkartılarak bulunmasıdır.

(p = chunk_at_offset(p, -(long)prevsz);)

Prev_size ve size alanları için 8 baytlık rastgele değerler veriyoruz.

Böylelikle sahte chunk’ımızı B tampunu içerisinde konumlandırdık.

Free(b) işlevi anındanda sahte chunk’a unlink() işlevi uygulandığı sırada pointer’ları (fd ve bk) işaret etmemesi gereken bir yere işaret edeceğiz.

Bunu GOT sayesinde yapacağız. GOT: Global Offset Table: Proses adres alanındaki içe aktarılan fonksiyonların bulunduğu noktayı gösteren bir fonksiyon işaretçi tablosudur. Bu tablodan printf’in adresini kullanacağız.

_config.yml

Derleyici opmizasyonu ile printf yerine puts kullanılmış.

Burada atlamamamız gereken bir ayrıntı daha bulunuyor. Unlink() işlevi FD->bk = BK ayarlaması yaptığı için ve bk pointer’ı bir chunk’ın başlangıcından itibaren 12 bayt uzaklıkta (offset) bulunduğundan puts() ‘un GOT adresinden 12 değeri çıkarmalıyız. Yeni değerimiz –> 0x0804b128-0xc = 0x0804b11c

Bu adresi sahte chunk’ın fd alanına kopyalayacağız. Bk alanına ise puts tarafından yazdıralacak olan winner() fonksiyonunun adresini vermeliyiz. Winner() fonksiyonun adresini resimdeki gibi elde ediyoruz.

_config.yml

Fonksiyonu puts()’a yazdırtabilmek için gerekli shellcode’u hazırlayıp tampon A’ya argüman olarak girdireceğiz. Daha sonra shellcode’un başladığı yerden bir adres alıp bu adresi bk alanına kopyalayacağız.

Gerekli assembly komutları:

push 0x08048864 ret

Bu komutların opcode’ları: \x68\x64\x88\x04\x08\c3

Tüm modifiyeleri yaptıktan sonra tamponların son hali şekildeki gibi olacak:

_config.yml

Şekle göre tamponlara verilecek değerler ise şöyle olacak:

Tampon A: 14 bayt NOP + Shellcode + Geriye kalan 12 baytlık alan için rastgele karakter + Tampon B’nin prev_size alanı için ‘-8’ değeri + Tampon B’nin size alanı için ‘-4’ değeri.

Tampon B: Sahte chunkın prev_size ve size alanları için rastgele 8 bayt + Sahte chunkın fd alanına puts() için kullanılacak adres. + Sahte chunkın bk alanına NOP sled içerisinden herhangi bir adres.

Tampon C: “C”

Bu bilgiler sonucunda hazırladığımız exploit:

$./heap3 python -c 'print “\x90”*14 + "\x68\x64\x88\x04\x08\xc3" + “A”*12 + "\xf8\xff\xff\xff" + "\xfc\xff\xff\xff”' python -c 'print "\xde\xad\xbe\xef"*2+"\x1c\xb1\x04\x08"+"\x08\xc0\x04\x08” C

Yararlanılan Kaynaklar:

http://liveoverflow.com/binary_hacking/protostar/heap3.html

https://conceptofproof.wordpress.com/2013/11/19/protostar-heap3-walkthrough/

https://www.cs.uoregon.edu/groups/uosec/files/slides/HeapExploitationSlides.pdf