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ı.
    #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
 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ı.
0x08048506 <+9>:    mov    DWORD PTR [esp+0x2c],0x41414141
Bu 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:
   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 
 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.

   0x0804852a <+45>:    mov    DWORD PTR [esp+0x4],eax
   0x0804852e <+49>:    mov    DWORD PTR [esp],0x8048679
   0x08048535 <+56>:    call   0x80483f0 <__isoc99_scanf@plt>
Burada 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.
Adresi bulmak için 0x0804852e adresine breakpoint koyup programı çalıştırmaya devam ettirelim.

b *0x0804852e
c
Şimdi ESP+0x4 adresinin içeriğine bakalım.

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

Yorumlar

  1. Ohoo şöyle sıfırdan güzel yazılar olsada bol bol okusak :)

    YanıtlaSil

Yorum Gönder

Bu blogdaki popüler yayınlar

DKHOS - Rev300 Çözümü

Part 1: Tersinden Tersine Mühendisliğe Giriş

Bir Güvenlikçi, Virüs ile Karşılaşırsa