Bu yazıda 1996'da yılında Aleph1 tarafından yazılan "smashing the stack for fun and profit" başlıklı yazısında geçen örnek ele alınarak, program akışının nasıl değiştirilebileceğinden bahsedilecektir. Aleph1'in ilgili makalesine http://insecure.org/stf/smashstack.html adresinden erişim sağlanabilir.
Exploit geliştirmenin ilk adımı olarak sayılan "stack based overflow" yöntemi için temel oluşturacak aşağıdaki örnek ile 'stack nasıl çalışır'ı anlamak daha da mümkün olacaktır. Buradaki amaç kısaca program akışının nasıl değiştirilebileceğini göstermek ve sonrasında yazılım akışının istenildiği gibi değiştirilmesini sağlamaktır
# include <stdio.h>
void deneme()
{
char buf[6];
int *a;
a = buf + 14;
(*a) += 12;
}
int main (void)
{
printf("1\n");
deneme();
printf("2\n");
printf("3\n");
return 0;
}
Kod deneme.c ismiyle kaydedilmiş ve ardından aşağıdaki derleme seçenekleri ile derlenmiştir.
# gcc -g -o deneme deneme.c
deneme isimli fonksiyonun içeriğinin aşağıdaki gibi olduğu varsayıldığında
void deneme()
{
char buf[6];
int *a;
}
kod derlenip çalıştırıldığında aşağıdaki çıktıyı verecektir.
1
2
3
Ancak deneme isimli fonksiyonun içeriği aşağıdaki şekilde düzenlendiğinde ise;
void deneme()
{
char buf[6];
int *a;
a = buf + 14;
(*a) += 12;
}
1
3
programın çıktısı yukarıdaki şekilde olacaktır. Peki deneme isimli fonksiyonun içerisinde belirtilen
a = buf + 14;
(*a) += 12;
satırlar ile ne sağlanmaktadır. int *a ile tanımlanması gerçekleştirilen a değişkeni ile önce buf karakter dizisinden 14 byte uzaklıktaki adrese konumlanmıştır. Ardından da bu adresin içeriği 12 byte ötelenerek fonksiyon sonlandırılmıştır. Bu şekilde fonksiyon akışının ekran çıktısında 1 2 3 olması gerekirken, 1 3 olarak tamamlanmış olmaktadır. Kod gdb ile disassemble edildiğinde aşağıdaki gibi bir görünüm alacaktır.
# gdb -q deneme
(gdb) list
5 char buf[6];
6 int *a;
7
8 a = buf + 14;
9 (*a) += 12;
10 }
11
12 int main ()
13 {
14 printf("1\n");
(gdb)
15 deneme();
16 printf("2\n");
17 printf("3\n");
18
19 return 0;
20 }
(gdb) disassemble main
Dump of assembler code for function main:
0x080483d2 <+0>: push ebp
0x080483d3 <+1>: mov ebp,esp
0x080483d5 <+3>: sub esp,0x4
0x080483d8 <+6>: mov DWORD PTR [esp],0x80484d4
0x080483df <+13>: call 0x80482f0 <puts@plt>
0x080483e4 <+18>: call 0x80483b4 <deneme>
0x080483e9 <+23>: mov DWORD PTR [esp],0x80484d6
0x080483f0 <+30>: call 0x80482f0 <puts@plt>
0x080483f5 <+35>: mov DWORD PTR [esp],0x80484d8
0x080483fc <+42>: call 0x80482f0 <puts@plt>
0x08048401 <+47>: mov eax,0x0
0x08048406 <+52>: leave
0x08048407 <+53>: ret
End of assembler dump.
(gdb)
main+18 satırında deneme isimli fonksiyon çağrısının gerçekleştirildiği görülmektedir. deneme isimli fonksiyonun çağrılmasından hemen önceki satırda ise ekrana 1 çıktısını göstermek için yazılmış printf("1\n") çağrımı görülmektedir. Normal şartlar altında deneme isimli fonksiyonun ardından main+30 satırında bulunan ve ekrana 2 çıktısını göstermek üzere kullanılacak printf("2\n") çağrım bilgisi gerçekleştirilecekken bunun yerine main+42 satırında bulunan ve ekrana 3 çıktısını göstermek üzere kullanılacak printf("3\n") çağrım bilgisine geçiş sağlanmıştır.
a = buf + 14; ifadesi ile, a değişkenin eip register değerinin adresine konuşlanması sağlanmaktadır. deneme isimli fonksiyon çağrımı gerçekleştirildiğinde stack görünümü kabaca aşağıdaki gibi olmaktadır. Derleyici tarafından hem güvenlik hem de optimizasyon amaçlı eklenen padding değerleri ve buf karakter dizisinin uzunluğu göz önüne alındığında, a = buf + 14 ifadesi ile eip register değerinin adresine konuşlanmaktadır.
a = buf + 14; ifadesi ile, a değişkenin eip register değerinin adresine konuşlanması sağlanmaktadır. deneme isimli fonksiyon çağrımı gerçekleştirildiğinde stack görünümü kabaca aşağıdaki gibi olmaktadır. Derleyici tarafından hem güvenlik hem de optimizasyon amaçlı eklenen padding değerleri ve buf karakter dizisinin uzunluğu göz önüne alındığında, a = buf + 14 ifadesi ile eip register değerinin adresine konuşlanmaktadır.
buf ebp eip
[ ][ ][ ]
Ardından kabaca bir hesap ile iki adres arasındaki mesafe 12 byte uzunluğunda olmaktadır. Yani (*a) += 12; ifadesi ile printf("2\n") ifadesi es geçilmiş yerine printf("3\n") ifadesi çalıştırılarak program akışı değiştirilmiştir.
Kısaca burada deneme isimli fonksiyon içerisinden öncelikle eip register değerine erişim sağlanmış ve hemen ardından bu register değerinin içeriği değiştirilerek program akışı istenildiği gibi değiştirilmiştir.
Bu yazıda stack nedir, nasıl çalışır ve herhangi bir fonksiyon çağrıldığında stack içerisinde neler olur sorularına değinilmeden kabaca program akışının değiştirilebileceği gösterilmiştir. Bu soruların cevaplarına ise daha sonraki yazılarda ayrıntılı olarak değinilecektir.
Kısaca burada deneme isimli fonksiyon içerisinden öncelikle eip register değerine erişim sağlanmış ve hemen ardından bu register değerinin içeriği değiştirilerek program akışı istenildiği gibi değiştirilmiştir.
Bu yazıda stack nedir, nasıl çalışır ve herhangi bir fonksiyon çağrıldığında stack içerisinde neler olur sorularına değinilmeden kabaca program akışının değiştirilebileceği gösterilmiştir. Bu soruların cevaplarına ise daha sonraki yazılarda ayrıntılı olarak değinilecektir.
0 comments:
Post a Comment