C Güvenli Kod Geliştirme - Stack Overflow Zafiyeti

Pentester

Özel Üye
8 Eyl 2016
1,646
1,001
Merhabalar, C programlama dilinde yazılım geliştirirken gözden kaçırdığımız ufak ayrıntılarla güvenlik zafiyetlerine sebebiyet verebiliyoruz. Bu ders boyunca bazı küçük C programları yazarak koddaki güvenlik sorunlarını tespit edip güvenli hale getirme üzerine konuşacağız. Aslında konumuzun odak noktası Stack Overflow zafiyetlerini ele almak olacak. Tabii bunun içinde biraz bellek yapısı ve stack üzerine konuşalım.

Bellek Yapısı ve Adresleme

Şimdi biz belirli değişkenler tanımlıyoruz, bazı değerler tutuyoruz ve bunlarda RAM üzerinde saklanıyor. Yani atıyorum biz bir değişken tanımladık bu değişken RAM üzerinde rastgele bir adreste tutuluyor. Tabii burada tanımladığınız değişkenin veri tipine göre bellekte kapladığı alanda değişkenlik gösterir. Örneğin Integer ile char veri tiplerinin hafızada kapladığı değerler birbirinden farklıdır.

j6mi35o.png

Şimdi burada main fonksiyonu biliyoruz ki C programımızın çalışmasının başladığı ana yer. Eğer main fonksiyonu yoksa programımız çalışmayacaktır buna dikkat edelim. Burada integer tipinde bir değer tutuyorum sayısal olarak 10 değerini veriyorum.


pdf1pat.png


Bakın ilk kısımda sayı değişkeninin içerisinde tutulan değeri yazdırırken ikinci kısımda ise bu değişkenin yani bu değeri tutan yerin bellekteki adresini yazdırıyorum. Eğer bu programı arka arkaya tekrar derleyip çalıştırırsanız her seferinde bu durumda bellekte farklı adresleri gösterdiğini göreceksiniz.

mkizapp.png


Şimdi ise iki adet değişken tanımlayarak sadece bunların bellekteki adreslerini görüyorum.

dfqrirf.png


Adreslere dikkat ettiniz mi? Son değerlerinden hesap ederseniz 276 ve 280 numaralı adreslerde tutuluyor bu değişkenler. RAM’de birbirlerinin arkasından geliyorlar. Ancak aralarında 4bytelık bir fark var işte bu da integer tipinin hafızada 4 byte değer kapladığının bir kanıtı oluyor. Buradaki değerler kullanılan veri tipinin kapladığı alana göre değişkenlik gösterir.

STACK

Şimdi de biraz stack üzerine konuşalım. Stack, RAM’in iki yapısından biridir. İçerisinde değer türlü değişkenleri ve değerlerini tutar. Yani integer X değişkeniniz ve bunun değeri olsun bunları tutabiliyorsunuz. Hem değişkenin adını hem de değerini tutabiliyorsunuz. LIFO yapısıyla çalışır. Yani Last In First Out dediğimiz son girenin ilk çıktığı bir yapıdır. Bunu da genellikle şu şekilde örneklendirirler bir kitaplık düşünün en son aldığınız kitabı rafta en üste koyarsınız ve o kitabı ilk olarak alabilirsiniz. Şimdi stack veri yapısını incelemek küçük bir örnek yazalım.

34eqe5a.png


Şimdi burada 5 elemanlık integer yer kaplayan bir dizi oluşturdum daha stack’e bu dizi elemanlarını eklemek için push, çıkarmak için pop fonksiyonları yazdım. Bunlar aslında özel şeyler değil bildiğiniz diziye eleman ekliyor ve o eklenen elemanları tek tek çıkarıyor. E şimdi düşünürsek bu dizi elemanları da stack üzerinde tutuluyor. Yani biz şuan stack üzerinden push ve pop yapacağız. Empty adında ayrı bir fonksiyonum var bu da geriye i’nin 0 olduğu durumu döndürüyor, bu da aslında dizi de eleman yok stack boş anlamına geliyor. Main fonksiyonumda ise for döngüsü yardımıyla 0-4 arası eleman alıyorum yani 5 eleman alarak bu elemanları push fonksiyonum vasıtasıyla diziye ekliyorum. Döngü sonuna geldiğimizde ise ekleme işlemini sonlandırıp pop fonksiyonunu tetikleyerek bu eklenen elemanların stackten çıkarılmasını sağlıyorum. Şimdi aldığımız çıktıyı inceleyelim.

gv2r7cq.png



Şimdi burada dikkatinizi çekmiş olması gerekiyor stack’e 5 eleman ekledik yani push yaptık hemen ardından pop yaparak stackten elemanları çıkardık. Burada hemen aklımıza stack’i tanımlarken söylemiş olduğum LIFO yapısı gelmesi gerekiyor. Ne demiştik, ilk giren son çıkar. 1 değeri ilk girdi, en son çıktı. Stack’e elemanlar üst üst eklendi. En üstte 5 değeri vardı ve stackten çıkarmaya çalışırken üstten başlayarak çıkarma işlemi gerçekleştirdi. Bu da LIFO teorisinin uygulamada bir göstergesi oluyor. Şimdi biraz temel konulardan bahsetmiş oldum. Yavaş yavaş ana konumuza giriş yapalım ve stack buffer overflow zafiyetlerinin nasıl meydana geleceğini örnek kodlar yazarak inceleyelim.


STACK BUFFER OVERFLOW

gven56r.png



İlk örneğimizden başlayalım. Str1 adında 5 bytelık yer kaplayan bir char değişkeni oluşturuyorum. Str2 adında da “test” karakterlerini tutuyorum. Bir tane ret_val adında yine karakter dizisi tutan boş bir değişken tutuyorum bunu kullanacağız. Strcpy ile str2 içindeki karakterleri str1 içerisine kopyalıyorum. En sonunda da str1’in değeri str2 içinde tutulan karakter dizisi ile oluşmuş oluyor. Ayrıca burada ek olarak şunu söyleyeyim, str1 5 bytelık yer tutuyor, str2’yi oraya kopyalacağız ve str2’nin 5 bytelık yer kapladığını biliyoruz. E ama “test” karakterleri 4 harf, char tipinde her karakter 1 byte ise bu “test” değeri 4 byte değil mi? Evet 4 byte ancak burada bu ayrılan boyutun bittiğini ifade eden 1 byte yer kaplayan, bir nevi dizinin sonunu belirleyen \0 karakteri de var bunu da hesaba katarak 5 bytelık yer ayırıyorum bu ayrıntıya dikkat edin. İşin sonunda ise str1 içerisine 5 bytelık bir char dizisi eklenmiş oldu. Şimdi overflow zafiyetini konuşmaya burada başlıyoruz. Biz 5 byte sabit bir değer belirledik, 5 byte değer aldık süper her şey yolunda. Ancak eğer str2 değişkeni yani veri aktarımı yapan değişken kopyalanacağı alandaki belirlenmiş 5 bytelık bölgeyi aşarsa ne olacak? İşte bu durumda stack smashing dediğimiz olayla karşılaşacağız bu da stack tabanlı bir taşma zafiyeti meydana getirmiş olacak.

3uossgl.png


Bakın ne yaptım, 5 birimlik yere beklediğinden daha fazla karakter gönderdim. Derleyicimde bu şekilde cevabını verdi.

fqr91ac.png


Bakın burada eklediğim son t harfinden sonrasını döndüremedi, çünkü stackte 5 bytelık yer ayırdığından geri kalan değerleri stack’e ekleyemedi. Şimdi burada ben değişkene manuel girdi ekledim kafanıza takılmasın bunu kullanıcıdan da alırsam aynı senaryo ile karşılaşacağız. Şimdi buradaki çözüm üzerine konuşalım. Problemi zaten anladık çözüm olarak ise şunu düşünüyoruz ben eğer belirli kapasitesi olan bir yere veri yazacaksam alacağım veriyi de o kapasiteyle sınırlandırmam gerekecek. Bu senaryo içinde C dilimiz bize string.h kütüphanesi altında bir metotla çözüm sunuyor.

iaziqm7.png


String.h kütüphanesi altındaki metotlara bakarsak burada strncpy adında bir metot tanımlamışlar. Bu metot, char tipinde dst (hedef) ve src(kaynak) değerlerine ek olarak size_t ile de bir uzunluk istiyor. Yani biz bir kaynağı hedefe kopyalayacağız ancak alacağımız bu kaynağın uzunluğunu 3.argüman olarak bu metoda vermemiz gerekiyor.

8b9ab12.png


Aynı olayı bu sefer strncpy metodum vasıtasıyla gerçekleştiriyorum ve benden istediği 3.argüman olarak 5 değerini veriyorum. Bu da aslında kaynağımın tutacağı değerin 5 olduğu anlamına geliyor. E zaten hedefim yani kopyalanacak alanımda stack içerisinde 5 byte değer tutuyor. Bu sebepten alan da veren de memnun oluyor. Ben burada size olayı biraz daha net göstermek için bir breakpoint oluşturuyorum.

pp3acbs.png


12. satıra bir breakpoint yani durma noktası koydum. Programın bu noktada duracak yani sonraki satırları işlemeyecek ama ret_val değişkeni içerisine kopyalama işlemini gerçekleştirecek bende burada size hafızayı göstereceğim.

90avseb.png


Visual Code da debug menüsünde bu alanı görebilirsiniz. Locals altında 3 adet değişkenimi görüyorum ve bunların tuttuğu değerleri de görüyorum. Ben ret_val yani elde ettiğim datayı inceliyorum. Burada “0101” şeklinde yazan ikona tıklıyorum ve binary düzende memorydeki değerlerimi görüyorum.

7c1qou7.png


Burada işaretlediğim satıra dikkat ederseniz stack üzerindeki bu adreste benim verilerim tutuluyor. Sağ tarafta bu binary düzeninin yani decimal değerlerin decode edilmiş haline bakarsak “testt” değerleri hafızada yer edilmiş oluyor yani burada stack üzerinde belirlediğimiz değerin dışına çıkılmamış ve overflow gerçekleşmemiş buradan da anlayabiliyoruz. Eğer bir önceki metotla aktarımı sağlasaydık da overflow sonunda memory aşağıdaki görseldeki gibi olacaktı.

4zg18qs.png


Bu şekilde zafiyetli kod neticesinde gördüğünüz gibi stackte decimal 74, ascii karşılığı t olan yani benim art arda gönderdiğim t karakterleri hafızada bu şekilde yer kaplayacak belirlenen alanın dışına çıkarak overflow oluşturmuş olacak. Yani burada çok basit bir kontrolle gelen girdinin, inputun veri boyutunu kontrol ederek zafiyetin önüne geçmiş oluyoruz. Şimdi farklı güvensiz kodlar yazarak onları da güvenli hale getirelim.

pmzafu3.png


Burada ise deneme yazısını str1 içindeki test yazısının sonuna ekleme işlemini strcat metodu vasıtasıyla yapıyoruz.

o7s0fpf.png


Bu şekilde iki değeri birleştirerek yeni değeri elde ediyoruz bu değerleri de str1 içerisine kopyalıyoruz ve str1’in 20 bytelık bir değeri var. Biz “testdeneme” olarak 10 byte yer oluşturduk stack üzerinde boşta 10 byte yerimiz var şu an itibariyle yeni gelen veriler boş alanları doldurabilir durumda.

tog9zvj.png


Buradan da görüldüğü gibi boş kalan değerler stack içerisindeki 20 byte’ın bittiği noktayı gösterene kadar \0 karakterleri ile doldurulmuş oluyor. Bizde bu \0 karakterlerini kaydıra kaydıra stack’i taşıracağız. Peki nasıl taşırabiliyoruz? E yine 20bytelık bir alana veri kopyalanıyor, bir kaynaktan alınıyor ama ortada kaynaktan gelen verinin uzunluğu kontrol edilmiyor, güvenli kod geliştirme konusu atlatılmış. Hemen fuzzing işlemimizi yapalım.

chplmvc.png

11. satıra breakpoint koyarak debug ettiğim taktirde stackte neler olup bitmiş bakalım.


167v4fd.png


Str2 değişkenim 0x65 değerlerini gösteriyor. Burada üretilen hata mesajına bakarsak “Cannot Access memory at address” yani belirtilen adrese erişim sağlanamadığı söyleniyor yani kısaca programımız doğru sonuçlar üretemiyor, crash oluyor.

2kjtdsi.png


Şimdi strncat fonksiyonunu kullanarak güvenliği sağlamak adına str2den gelen verinin sadece ilk 2 karakterini al diyorum ve str2 içerisinde çok fazla karakter göndererek testimi gerçekleştiriyorum.

4mdq86d.png


Bakın “de” yani ilk 2 karakteri aldı ve gönderdiğim değerleri kopyalamadı bunu da çok net bir şekilde 6.indisten 19.indise kadar \0 karakterleri ile doldurmasından anlıyoruz. Bu şekilde overflowun önüne geçerek kodumuzu güvenli hale getirdik.

7zctven.png


Şimdi bu örneğimizde ne oldu? 10 bytelık string değişkenimize kullanıcının girdiği inputu yazacağız. 10 karakter bekliyoruz diyor program ama biz 10 karakter girer miyiz hiç yapıştırıyoruz daha fazlasını ve overflow yaratıyoruz. Yani burada gets fonksiyonu ile kullanıcıdan alınan girdi string değişkenine kopyalanıyor ama alınan girdinin boyutunu gets fonksiyonu kontrol etmiyor bu sebeple daha fazla bir girdi gelirse 10bytelık alanı aşırıyoruz.

ercocsg.png


Bakın stdio.h kütüphanesi altında bulunan bu metot parametre olarak sadece char tipinde karakteri aynen alıyor.

76ck52x.png


Gets metodu yerine muadili olan fgets metodunu kullanarak güvenliği sağlıyoruz 10 karakterlik girdi alacağımızı metoda parametre olarak söylememiz yeterli oluyor. Gördüğünüz gibi uzun bir karakter gönderdim ancak 10bytelık kısmı alınıp işlendi.


d0b184f.png


Fgets argüman olarak sırayla nereye kopyalanacağını ilk parametrede, integer olarak alınacak girdinin uzunluğunu ikinci parametrede, nereden alacağını da son parametresinde bekliyor. Bizde stdin diyerek klavyeden alacaksın diyoruz.

Bu konumuzun da sonuna geldik. Basitçe C uygulamalarında stack tabanlı taşmanın mantığını, secure code nasıl yazacağımızı anlatmaya çalıştım. Bunun gibi çok daha farklı zafiyete sebep veren fonksiyonlar bulunmakta ancak temelde alacağımız girdinin yazacağımız alanın kapasitesinden fazla yazmasını engelleyerek aşıyoruz. Hiçbir zaman kullanıcıdan alınan girdiye güvenmiyoruz. Okuduğunuz için teşekkürler.


 
Son düzenleme:

green.php

Katılımcı Üye
27 Haz 2021
732
874
Ellerinize sağlık hocam.
Piyasada C ve c++ bilen çok az kişi kaldı.
Tüm yazılım dilleri bu dil üzerinden geliştirildi.
 

Maveraün Nehr

Blue Expert / Head of Malware Team
25 Haz 2021
980
1,889
41.303921, -81.901693
Eline, emeğine, yüreğine, klavyene, farene, USB'de takılı olan flash diske, bayramlarda elini öptüğün teyzeye, ailene, sülalene, beynine, düşüncene, sabah yaptığın kahvaltıya, bugün içtiğin bardak suya, yürüdüğün yola, attığın her bir adıma sağlık.( Geliştirilmiş yorum)
 

Pentester

Özel Üye
8 Eyl 2016
1,646
1,001
Eline, emeğine, yüreğine, klavyene, farene, USB'de takılı olan flash diske, bayramlarda elini öptüğün teyzeye, ailene, sülalene, beynine, düşüncene, sabah yaptığın kahvaltıya, bugün içtiğin bardak suya, yürüdüğün yola, attığın her bir adıma sağlık.( Geliştirilmiş yorum)
Allah razı olsun.
 

icehead

Uzman üye
19 Şub 2022
1,137
833
Eline sağlık @Pentester
stack overflow ile tanışmam üniversitedeki bir arkadaşım banka otomosyonu projesindeile olmuştu :)
şifre için 20 char dizi tanımlamış char değerini aşınca strcmp kısmını atlayıp direk login oluyordu.

java,.net gibi ortamların çıkmasıyla sanırsam bu tür zafiyetlere az rastlanıyor. :)
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.