Stack Canary Bypass – Part 1

Merhabalar,

Geçenki blogumuzda ASLR korumasının nasıl atlatılabileceğinden bahsetmiştik. Bugün Stack Canary, diğer adıyla Stack Cookie’nin nasıl bypass edilebileceğinden bahsedeceğim.

Stack Canary Nedir?

Stack Canary modern derleyiciler tarafından stack tabanlı overflow saldırılarına karşı kullanılan bir güvenlik önlemidir. Temel amacı, bir fonksiyonun yerel değişkenlerinin bulunduğu stack alanında beklenmeyen değişiklikleri tespit ederek saldırıyı engellemektir.

Stack Canary mantığını uzunca ve çok basit örneklerle açıklamakta fayda görüyorum. Stack overflow zafiyetinde, zayıflıktan faydalanarak registerların üzerine yazıp, programın akışını istediğimiz yönde değiştirip, stackde bulunan zararlı kodumuzun çalışmasığını sağlayabiliyorduk. Stack Canary önlemi şöyle çalışıyor,  program başlamadan önce stack canaryi değeri bir registera (32 bit FS, 64 bit GS) yazdırılır. Programdan çıkışta bu stack canaryi değeri tekrar kontrol edilir eğer değer aynıysa herhangi bir sorun olmadığı anlaşılır ve program akışına devam eder. Eğer herhangi bir şekilde bu değer değişmişse stack canaryi koruması devreye girer ve program sonlandırılır. Stack canary koruması olan programı tahayyül ederken şöyle bir yapıyı kafamızda canlandırabiliriz. (farazi canlandırmadır)

Buradaki akışı rastgele anlaşılsın diye herhangi bir gerçek kurala bağlı kalmadan oluştum. Amaç kafamızda oturması. Programın başındaki canaryi değeri programın sonunda kontrol ediliyor. Değer bu değer değiştirilmişse (overflow da binlerce pattern oluşturup registerların üzerine yazıyorduk haliyle canaryi değerinin olduğu ofsete de yazıyoruz bu durumda) programın akışının manipule edildiği anlaşılıyor. Değer aynıysa bir manipulasyonun olmadığı anlaşılıyor ve akışa devam ediyor.

Günlük hayattan gene bir örnek vermek gerekirse, havaist’e binenleriniz vardır. Burada valizinizi verirsiniz, valizinize çantanıza bir numara takılır ve bu numaranın kopyasını da size verirler. Sizde varış yerine ulaştığınızda sizdeki numarayı verirsiniz valizinizi böylelikle alırsınız, o numara o valizin size ait olduğunu belirler. Stack canaryi korumasıda böyle, program başlarken bir değer belirlenir, bu değer değişmişse bir manipulasyon olduğu anlaşılır ve program sonlanır.

Teknik kuralların dışında verdiğimiz örnekleri bir kenara bırakıp işin biraz teknik tarafına dönelim ve anlamaya çalışalım.

Basit bir overflow zafiyetini tespit etme aşamasında, hatırlayın yüzlerce binlerce bytelık veriler gönderip programı crash’e zorluyorduk. Yani registerların üzerine yazıp kontrol edebilmeye çalışıyorduk. Stack canaryi koruması olan bir programda crash durumunu tetikleyebilmek için 2000 byte’lık bir veri gönderdiğimizi düşünün. Bu veri registerların üzerine yazılacak ve haliyle stack canaryi değerinin bulunduğu GS veya FS segmentinin de üzerine yazacak. Bu durumda canaryi değeri değişmiş olacak.  Bu da istismar kodu geliştirmemize engel bir durum oluşturacak.

Modern sistemlerde, GS segment registerı, bir thread’e özgi veri alanını işaret etmek için kullanılır. (TLS – Thread Local Storage). Stack canaryi değeri bu TLS alanında tutulur. Derleyici, stack canary değerini işlemciye özgü bir konuma, genellikle GS segmentindeki bir ofsete yerleştirir. Bu değer genellikle işlemler sırasında sadece okunur. Bir fonksiyon çağırıldığında, stack canary değeri stack’e yazılır. GS register üzerinden TLS alanındaki asıl canaryi değeri okunur ve stacke’e yerleştirilir. Fonksiyon sonlanırken, stackdeki canaryi değeri tekrar GS register üzerinden TLS’deki asıl değerle karşılaştırılır. Eğer değişmişse program sonlanır. Örnek bir assembly kod parçasını inceleyelim. Fonksiyonun başlangıcı ve sonu stack canary kontrolünü içerir.

Fonksiyon girişinde canary atama

mov    %fs:0x28, %rax   # FS veya GS segmentinden canary değerini al

mov    %rax, -0x8(%rbp) # Stack’te bir yere yaz

Fonksiyon çıkışında canary kontrolü

mov    -0x8(%rbp), %rax # Stack’teki canary’yi al

xor    %fs:0x28, %rax   # Orijinal değerle karşılaştır

je     .normal_exit     # Değişiklik yoksa devam et

call   __stack_chk_fail # Değişiklik varsa programı sonlandır

Unutulmaması gereken bir diğer husus bu stack cookie değerlerinin her programın açılışında değişebileceğidir.

Artık somut örneklerle duruma giriş yapabiliriz.

Bu aşamada elimizde C ile yazılmış ve buffer overflow zafiyetini üzerinde barındıran strcpy fonksiyonu kullanılmış bir program var. Kodları şöyle yazabiliriz.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>  
Int main(int argc, char **argv){
Char alan[64];
Strcpy(alan, argv[1]);
Printf(alan);
}

Bu kodu linux makinemde gcc derleyicisiyle derleyelim.

gcc -Wl, -z execstack -z,norelro -no-pie stack.c -o stack

Derleme işlemi gerçekleştirdikten sonra ASLR korumasını kapatmamız gerekir. Bunun için randomize_va_space dosyasına 0 değerini set etmemiz gerekir.

echo “0” > /proc/sys/kernel/randomize_va_space

Derlediğimiz programı crash olmaya zorlayalım.

Görüldüğü üzere tampon alanının alabileceğinden fazla byte veri gönderdiğimizda stack smashing detected hatası alıyor. Burada dikkat edilmesi gereken normal sıradan bir segmantation fault hatası görünmüyor, stack smashing detected hatası görünüyor. Bu Stack Canary güvenlik önleminin çalıştığını gösteriyor.

Burada, disassemble haldeki kodu bir incelemek istiyoruz. Bunun için “gdb –q ./stack” komutuyla programımızı gdb ile açıyoruz. Ardından  gdb içerisinde “disas main” komutuyla disassemble kodları incelemeye başlıyoruz.

Ekran görüntüsünde birkaç nokta gözümüze çarpıyor. Bunlar:

0x0000000000401189 <+19>:    mov    %fs:0x28,%rax

0x0000000000401192 <+28>:    mov    %rax,-0x8(%rbp)

0x00000000004011c8 <+82>:    mov    -0x8(%rbp),%rcx

0x00000000004011cc <+86>:    xor    %fs:0x28,%rcx

0x00000000004011d5 <+95>:    je     0x4011dc <main+102>

0x00000000004011d7 <+97>:    callq  0x401070 <__stack_chk_fail@plt>

Program 64 bit derlendiğinden bellek adreslerinin 32 bite göre daha uzun oldugunu görebilirsiniz. Assembly koduna baktıgınızda, FS flaginin içerisinde 0x28 offseti RAX segmentine atıyor.

Burada 0x28 içerisinde stack canary cokie değeri var. Bunu tespit etmek için programı gdb de “run” ettik.

Ardından

p/x *(void **)($fs_base + 0x28)

komutunu çalıştırdık ve bize dönen değer stack canary cookie değeri oldu.

Buradaki 0xc590c85d5a2d1200 değeri bizim stack canary cookie’miz.

“0x00000000004011c8 <+82>:    mov    -0x8(%rbp),%rcx” kısmında, stack cookie’sini RCX registerına atıyor.

Ardından aşağıdaki XOR’lama ile bu stack canary cookie değerinin değiştirilip değiştirilmediğine bakıyor.

“0x00000000004011cc <+86>:    xor    %fs:0x28,%rcx”

Değiştirilme işlemi, yani bir üzerine yazma (overflow da oldugu gibi) işlemi olmadıysa program olağan akışını sürdürüyor.

0x00000000004011d5 <+95>:    je     0x4011dc <main+102>”

Eğer bu değer değişmişse, __stack_chk_fail fonksiyonu çağırılıyor ve programı exit(0) ile kapatıyor. Ardından bizim ilk başta gördüğümüz stack smashing detected hatasını yazdırıyor.

“0x00000000004011d7 <+97>:    callq  0x401070 <__stack_chk_fail@plt>”

Farkındaysanız, overflow da programı crash etmek isterken EIP registerının üzerine yazmak istiyoruz ama stack cookie değerinin de üzerine yazıp bu değeri değiştiriyoruz. Haliyle koruma mekanizması devreye giriyor ve saldırı olduğunu anlayıp exit ile programdan bizi atıyor.

Gdb de programı taşıracak ve stack cookie üzerine yazacağımız boyut kadar 80 byte A karakteri gönderiyorum.

Programın kapandığını ve stack smashing detected hatasını verdiğini görebiliyoruz. Stack cookie değerimizin üzerine yazdık ve program bu değerin değiştiğini anladı, programı kapattı.

Burada programın kaç byte’dan sonra crash oldugunu bulmamız lazım. Benim testimde 72 byte sonrasında crash gözlemledim. Yani 72 byte da stack cookie’yi değiştirip üzerinden geçebilmişim.

Devam edecek…