10 Kasım 2019 Pazar

Format String Exploitation

Format String Exploitation açığı C dilinde printf, sprint, vsprintf ... gibi methodların doğru bir şekilde kullanılmayışından ötürü ortaya çıkan bir açıktır.

Format kullanılan methodlardan biri olan printf methodunu bu şekilde kullanması gerekirken mesela.

printf("%d", age)

Programcının aşağıdaki şekildeki gibi kullanması açığa sebep olmakta.

printf(age)

Eğer age değeri dışarıdan alınan bir değer ise art niyetli kişiler değişken içerisinde format değerleri kullanarak sistemi sömürebilir. ( %x %n %u ...)

Çok fazla anlatım yapmak istemiyorum çünkü çok fazla şeyi anlatmam gerekecek ve yazı çok uzayacak.. Açığı sömürelim o vakit...


Güvenlik açığı ulan kodumuz bu şekilde. hi() methodu hiçbir şekilde kodumuzda çağrılmadığı halde bizim amacımız bu methodu çağırmak. Bir nevi CTF sorusu yaptık kendimize.


Evet üstteki resimde ne yaptık bir bakalım.. Kodumuzda da görüldüğü gibi bizden argüman alıyor 1 adet ve bu argümanı printf ile ekrana basıyor. Tabi bir de bize hakaret eden şu You Noob yazısını...

%x format belirleyicisini verdiğimizde değişik veriler de çıktı gördüğümüz gibi. Peki bu değerler nedir ? Bu değerler stackte tutulan dataların hexadecimal karşılıklarıdır.

Amacımız hi() methodunu çağırmak olduğu için ve 32bit bir programda adres 8 bit yani 4 bayt olduğu için 4 tane AAAA kullanıp daha sonra bu AAAA değerinin stackte sahip olduğu yeri bulmaya çalıştık. Bulduğumuz yere payloadımızı yazacağız ve böylelikle exploit etmiş olacağız.

9. %x formatında bulduk (41414141 -- > AAAA) ve 9 tane %x yazmak yerine şu şekilde 9. değeri seçtik. %9$x bash $ işaretini değişkenlerde kullandığı için bypasslamak adına %9\$x ters slash kullandık.


Bundan sonraki adım yukarıdaki kodda da gözüktüğü gibi %n format belirleyicisi ile uzunluk yazdırmak olacak. %n format belirleyicisinden önce gelen verinin uzunluğu val değişkenine atanıyor.

blah+<boşluk> = 5

%n bu şekilde bir format belirleyicisi. Kendisinden önce gelen verinin uzunluğunu değişkene atamak için var. Biz de bu format belirleyicisi ile printften sonra çalışacak olan methodun GOT (Global of Table)'daki adresine hi() methodumuzun adresini yazdıracağız. Bu sayede methodumuz çalıştığında bir sonraki method çalıştığında hi() methodumuz çalışacak aslında..


Programımızı disassemble ettiğimizde printf() metohundan sonra çalışacak olan method puts methodu. Bu methodun PLT adresini istemiyoruz çünkü bu adres sürekli olarak değişir.. PLT adresi değişmez bir tabloya bağlıdır GOT bu tablodaki adres ise gerçek methodu çağırmaktadır.

çağrılan adrese breakpoint koyup tekrar programımızı çalıştıralım ve GOT'taki adresini bulmaya çalışalım..


Programımızın durduğu satır burası. jmp komutu ile bir adrese sıçrama yapıyor ve ardından bir işlem daha yapıp main methoduna yeniden sıçrayarak dönüyor. [ ebx + 0x18 ] adresinin ne olduğuna bakalım...



p/x ($ebx + 0x18 ) ile adresi yazdırdıktan sonra bu adresin olduğu noktayı disassemble edince bu adresin puts methodunun GOT adresi olduğunu görüyoruz.. Bingo

Üzerine yazdıracağımız adres ----> 0x56559018



Programı bu sefer python yardımıyla adresimize değer yazdırmak için bu şekilde çalıştırdık.
AAAA yerine bu sefer adresimizi yazdık. Adresi ters bir şekilde \x18\x90\x55\x56 olarak yazdık Little Endian'dan ötürü. Ters slash değerlerini bash yok sayacağı için python kullandık. Yine printf de kullanabilirsiniz. Önemli olan 4 bytelık veriyi argüman olarak verebilmek. Daha sonra adresimize ilgili stringin uzunluğunu yazdırmak için %9\$x yerine %9\$n yazdık.

EIP registerı programda bir sonraki çalıştırılacak olan methodun adresini tutar. Orada da görüldüğü gibi 0x5 adresini çağırmaya çalışmış program hatta verdiği hata da şöyle:

0x00000005 in ?? ()

nerede bu adres kardeş çalıştıramıyorum ??? diyor bize

Neden 5 yazdı onu da anlatayım. Adresimiz zaten 4 byte idi. Bir de "-" işareti kullanmıştık bununla beraber 5 byte veri oldu. Hatta istersek biraz daha veri ekleyip 10 byte da yazdırabiliriz.

Yazdırmak istediğimiz methodun tuttuğu değere baktık decimal olarak ve orada da 5 yazdığını görmüş olduk.

x/d 0x56559018

Artık tek yapmamız gereken hi() methodunun adresinin uzunluğu kadar byte üretmek ve bunu yazdırmak zaten yazdırabiliyoruz. Sadece birazcık matematik lazım olacak.


Daha iyi anlaşılması açısından yine aynı payloada 5 adet A harfi daha ekledim ve tekrar çalıştırdım. bu sefer

0x0000000a in ?? ()

demekte. 0xa hexadecimal bir değer olduğu için onun decimal hali 10'dur. Gördüğümüz gibi 10 byte veri uzunluğunu ilgili adrese yazdırabildik...

hi() methodumuzun adresi bu --- > 0x565561cd

#> disas hi komutunu debuggerda yazınca bulabilirsiniz.

Amacımız 0x565561cd sayısına ulaşabilmek. Bunun için bir bu kadar karakter yazdırabiliriz fakat buffer overflow olabilir ya da çok fazla bir sayı olduğu için bilgisayarın bunu işlemesi çok uzun sürebilir. Bunun için başka bir yöntem var.

0x5655 ve 0x61cd bu iki değeri iki byte kaydırarak yazdırıyoruz ve toplatıyoruz.

Şöyle hayal edebilirsiniz tam olarak böyle olmasa da. Mantıken bir sakınca yok düşüncede

0x000061cd + 0x56550000 --> 0x565561cd

0x5655 sayısını hesaplarkenki kullandığımız adresin 2 byte büyüdüğünü göreceksiniz zaten. Amaç 2 byte sola kaymak.

ilk olarak 0x61cd sayısını bulmak gerek.

0x61cd hex değeri decimal olarak 25037 sayısına tekabül ediyor. Biz 25037 bytelık bir veri uzunluğu yazdırmalıyız fakat öncesinde 5 byte veri uzunluğu olduğu için 25037-5 = 25032 byte veri yazdırmamız gerek. Peki bu kadar uzunlukta A harfi mi koyacağız önüne ??

Hayır tabiki de. Bunun için yine başka bir format belirleyici kullanacağız %u . Unsigned char için kullanılan bu belirleyici işimizi çok kolaylaştıracak. %10u dersek mesela 10 byte veri yazmış olacağız. Çünkü unsigned char veri uzunluğu 1 byte'dır.

payloadımız artık böyle oldu --> run $(python2 -c "print '\x18\x90\x55\x56'")-%25032u%9\$n

Sonuç:


Tek vuruşta hesapladık :

0x000061cd in ?? ()

Şimdi sıra geldi diğer 2 byte uzunluğundaki adresi 0x5655 hesaplamada.. Sola kaymak adına adresi 2 byte daha arttırıyoruz.

Payload :

run $(python2 -c "print '\x18\x90\x55\x56' + '\x1a\x90\x55\x56'")-%25032u%9\$n-%10\$n

değerini verdiğimde...


0x61d261d1 in ?? () sonucu çıktı... Olamaz 0x61cd sayımız bozulmuş, 0x61d1 olmuş...

Hemen üzülmeyin çünkü bu değerin bozulmasının nedeni sol tarafa 4 byte veri daha eklememiz yüzünden oldu.

\x1a\x90\x55\x56 == 4 byte

Yani 4 byte daha veri çıkarırsak hesaplamaya sağ taraftan devam edebileceğiz..

25032 - 4 = 25028 eder.

Sonuç:



Şimdi Sağ taraftan devam edeceğim ve adresin büyük olan kısmı yani 0x5655 kısmını üreteceğiz. Fakat gördüğümüz gibi öncesinde sayı oluştu onun üzerine bir eklemeli hesap yapmamız gerek.

0x61ce61cd oluşan sayımız bu şekil

0x565561cd adresi de istediğimiz adres.

Oluşan adres istediğimiz adresten büyük ve bizim çıkarma yapmamız gerek.. Ama çıkarma yok ki ? Negatif bir sayı ile toplayacağız bu yüzden de... Bu da bir çıkarmadır.

Aradaki farkı hespalayalım --> 0x15655 - 0x61ce == F487 çıktı.

0x5655 sayısı 0x61ce den küçük olduğu için başına 1 koyduk. Aslında bu sayının pozitif ya da negatif olduğuna dair bir işarettir. 1 koyduk ama aslında - (eksi) koymuş olduk.

F487 decimal hali ise -> 62599'dır. Yani biz 62599 byte veri ekleyeceğiz sağ tarafa...

Son payload:

run $(python2 -c "print '\x18\x90\x55\x56' + '\x1a\x90\x55\x56'")-%25028u%9\$n-%62599u%10\$n

Sonuç :



Good Job !

Bir sonraki yazılarda görüşmek üzere esen kalın...

Not: ASLR korumasını kaldırarak da debugger dışında da payloadınızı çalıştırabilirsiniz..

echo 0 > /proc/sys/kernel/randomize_va_space


0 yorum:

Yorum Gönder