GDB ile Vakit Öldürmeceler
Dün GNU Debugger ile yaptığım alıştırmalar sırasında öğrendiklerimi gerek tekrar için gerekse geçerken gören olursa onlarda öğrensinler diye buraya yazıyorum.
Herşey arkadaşımla OverTheWire'ın Narnia level 5 sorusunu çözerken başladı.
Bizden isteneni gerçekleştirdikten sonra arkadaşım: "Şayet 'i' değişkeninin adresi yazdırılmamış olsaydı adresi nasıl bulabilirdik?" şeklinde bir soru sordu. Bende debugger ile main fonksiyonunu disassemble edip '1' değerinin yüklendiği adresi elde etmesini söyledim (biraz daha detaylıca). Daha sonra bunu farklı değişkenler ve farklı değişken türleri içinde denemeye başladık.
Daha çok değişken içermesi sebebiyle Level 0 üzerinden gideceğim. Kaynak kodu aşağıdadır.
Ilk değişkenimiz olan val ı bulmak için 0x41414141 değerinin yüklendiği adresi bulmamız gerektiği aşikar. Assembly'de bütün veriler hexadecimal tabanda ifade edildiği için bulmak zor değil.
GDB'ye girip main() fonksiyonunu disassemble edelim.
Gregory House'un bakışları altında sizi main() fonksiyonunun ilk kısmı bekliyor. Az evvel bahsettiğim komut ilk bakışta göze çarpıyor olmalı.
Yazdığım mealin meali: ESP adlı register stack hafızasının en üstündeki byte'ın adresini tutar. Dolayısıyla bu adrese 0x2c eklersek i değişkeninin adresine ulaşırız.
Tabi stack'e sürekli veri eklenip çıkarıldığından doğal olarak ESP'nin işaret ettiği adres sürekli olarak değişmektedir. Doğru adrese erişmek için doğru yere breakpoint koymamız -ki bu yer o komuttan bir sonraki komutun adresidir- ve akabinde programı çalıştırmamız gerekiyor.
Breakpoint'imizi koyup programı çalıştıralım.
Programımız breakpoint koyduğumuz komuta gelince çalışmayı durdurdu. Şimdi ESP+0x2c adresinin barındırdığı değere bakalım. Şayet tahminimiz doğruysa bu değerin 0x41414141 olması gerekecektir.
Tahminimiz doğru ve değişkenimizin adresi 0xffffd6cc !
Sıradaki değişkene geçelim. Bu sefer karşımızda 20 byte alan ayrılmış bir karakter dizisi var. Sabit boyutlu dizilerde alanı ayarlama işlemi derleme sırasında gerçekleştiği için bu değişkenin tanımlamasını direkt olarak assembly kodunda göremeyiz. Ancak şans eseri programı yazan kişi scanf() fonksiyonuna parametre olarak bu değişkenin adresini göndermiş. Bizde bundan yararlanarak adresi elde edeceğiz.
Adresi bulmak için 0x0804852e adresine breakpoint koyup programı çalıştırmaya devam ettirelim.
Adresimiz 0xffffd6a4 içerisinde ise değişkenin point ettiği adres var. Ancak henüz bir değer girilmediğinden program hafızada hangi değer saklıysa onu gösterecektir. Doğrulamak adına scanf() çalışana kadar programı devam ettirip değer girdikten sonra adresin barındırdığı değere tekrar bakıyorum.
Bu değişkeninde adresini bulmuş olduk böylece. Program içerisindeki herhangi bir değişkenin veya sabit stringin adresini bu yöntemler aracılığıyla bulmak mümkün.
Gelecek yazıda görüşmek üzere..
Herşey arkadaşımla OverTheWire'ın Narnia level 5 sorusunu çözerken başladı.
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char **argv){ int i = 1; char buffer[64]; snprintf(buffer, sizeof buffer, argv[1]); buffer[sizeof (buffer) - 1] = 0; printf("Change i's value from 1 -> 500. "); if(i==500){ printf("GOOD\n"); system("/bin/sh"); } printf("No way...let me give you a hint!\n"); printf("buffer : [%s] (%d)\n", buffer, strlen(buffer)); printf ("i = %d (%p)\n", i, &i); return 0; }
Bizden isteneni gerçekleştirdikten sonra arkadaşım: "Şayet 'i' değişkeninin adresi yazdırılmamış olsaydı adresi nasıl bulabilirdik?" şeklinde bir soru sordu. Bende debugger ile main fonksiyonunu disassemble edip '1' değerinin yüklendiği adresi elde etmesini söyledim (biraz daha detaylıca). Daha sonra bunu farklı değişkenler ve farklı değişken türleri içinde denemeye başladık.
Daha çok değişken içermesi sebebiyle Level 0 üzerinden gideceğim. Kaynak kodu aşağıdadır.
#include <stdio.h> #include <stdlib.h> int main(){ long val=0x41414141; char buf[20]; printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n"); printf("Here is your chance: "); scanf("%24s",&buf); printf("buf: %s\n",buf); printf("val: 0x%08x\n",val); if(val==0xdeadbeef) system("/bin/sh"); else { printf("WAY OFF!!!!\n"); exit(1); } return 0; }
Ilk değişkenimiz olan val ı bulmak için 0x41414141 değerinin yüklendiği adresi bulmamız gerektiği aşikar. Assembly'de bütün veriler hexadecimal tabanda ifade edildiği için bulmak zor değil.
GDB'ye girip main() fonksiyonunu disassemble edelim.
gdb -q narnia0
disass main
0x08048506 <+9>: mov DWORD PTR [esp+0x2c],0x41414141Bu komutun meali: ESP registerinin point ettiği adresin 0x2c byte ilerisindeki DWORD boyutundaki alana 0x41414141 değerini taşı.
Yazdığım mealin meali: ESP adlı register stack hafızasının en üstündeki byte'ın adresini tutar. Dolayısıyla bu adrese 0x2c eklersek i değişkeninin adresine ulaşırız.
Tabi stack'e sürekli veri eklenip çıkarıldığından doğal olarak ESP'nin işaret ettiği adres sürekli olarak değişmektedir. Doğru adrese erişmek için doğru yere breakpoint koymamız -ki bu yer o komuttan bir sonraki komutun adresidir- ve akabinde programı çalıştırmamız gerekiyor.
Breakpoint'imizi koyup programı çalıştıralım.
b *0x0804850e
r
Programımız breakpoint koyduğumuz komuta gelince çalışmayı durdurdu. Şimdi ESP+0x2c adresinin barındırdığı değere bakalım. Şayet tahminimiz doğruysa bu değerin 0x41414141 olması gerekecektir.
x/dwx $esp+0x2c
Tahminimiz doğru ve değişkenimizin adresi 0xffffd6cc !
Sıradaki değişkene geçelim. Bu sefer karşımızda 20 byte alan ayrılmış bir karakter dizisi var. Sabit boyutlu dizilerde alanı ayarlama işlemi derleme sırasında gerçekleştiği için bu değişkenin tanımlamasını direkt olarak assembly kodunda göremeyiz. Ancak şans eseri programı yazan kişi scanf() fonksiyonuna parametre olarak bu değişkenin adresini göndermiş. Bizde bundan yararlanarak adresi elde edeceğiz.
Dump of assembler code for function main:Assembly ile az çok uğraşmış biri bir fonksiyon çağırılmadan önce parametrelerin stack'e yükleneceğini bilir. scanf() fonksiyonu iki parametre alıyor; dolayısıyla bu fonksiyonun çağırılmasından önceki iki komuta bakmamız lazım.
0x080484fd <+0>: push ebp
0x080484fe <+1>: mov ebp,esp
0x08048500 <+3>: and esp,0xfffffff0
0x08048503 <+6>: sub esp,0x30
0x08048506 <+9>: mov DWORD PTR [esp+0x2c],0x41414141
0x0804850e <+17>: mov DWORD PTR [esp],0x8048630
0x08048515 <+24>: call 0x80483a0 <puts@plt>
0x0804851a <+29>: mov DWORD PTR [esp],0x8048663
0x08048521 <+36>: call 0x8048390 <printf@plt>
0x08048526 <+41>: lea eax,[esp+0x18]
0x0804852a <+45>: mov DWORD PTR [esp+0x4],eax
0x0804852e <+49>: mov DWORD PTR [esp],0x8048679
0x08048535 <+56>: call 0x80483f0 <__isoc99_scanf@plt>
0x0804853a <+61>: lea eax,[esp+0x18]
0x0804853e <+65>: mov DWORD PTR [esp+0x4],eax
0x08048542 <+69>: mov DWORD PTR [esp],0x804867e
0x08048549 <+76>: call 0x8048390 <printf@plt>
0x0804854e <+81>: mov eax,DWORD PTR [esp+0x2c]
0x08048552 <+85>: mov DWORD PTR [esp+0x4],eax
0x08048556 <+89>: mov DWORD PTR [esp],0x8048687
0x0804855d <+96>: call 0x8048390 <printf@plt>
0x08048562 <+101>: cmp DWORD PTR [esp+0x2c],0xdeadbeef
0x0804856a <+109>: jne 0x804857a <main+125>
0x0804856c <+111>: mov DWORD PTR [esp],0x8048694
0x08048573 <+118>: call 0x80483b0 <system@plt>
0x08048578 <+123>: jmp 0x8048592 <main+149>
0x0804857a <+125>: mov DWORD PTR [esp],0x804869c
0x08048581 <+132>: call 0x80483a0 <puts@plt>
0x08048586 <+137>: mov DWORD PTR [esp],0x1
0x0804858d <+144>: call 0x80483d0 <exit@plt>
0x08048592 <+149>: mov eax,0x0
0x08048597 <+154>: leave
0x0804852a <+45>: mov DWORD PTR [esp+0x4],eaxBurada dikkat etmemiz gereken parametrelerin sırası. Parametreler stack'e yüklenirken normal sıranın tersi sırayla yüklenirler. Yani buradaki ilk komut aslında buf değişkenin adresini; ikinci komut ise "%24s" format stringinin adresini stack'e yüklemekte.
0x0804852e <+49>: mov DWORD PTR [esp],0x8048679
0x08048535 <+56>: call 0x80483f0 <__isoc99_scanf@plt>
Adresi bulmak için 0x0804852e adresine breakpoint koyup programı çalıştırmaya devam ettirelim.
b *0x0804852eŞimdi ESP+0x4 adresinin içeriğine bakalım.
c
Adresimiz 0xffffd6a4 içerisinde ise değişkenin point ettiği adres var. Ancak henüz bir değer girilmediğinden program hafızada hangi değer saklıysa onu gösterecektir. Doğrulamak adına scanf() çalışana kadar programı devam ettirip değer girdikten sonra adresin barındırdığı değere tekrar bakıyorum.
Gelecek yazıda görüşmek üzere..
Ohoo şöyle sıfırdan güzel yazılar olsada bol bol okusak :)
YanıtlaSil