25 Ağustos 2016 Perşembe

Shared Library ve Position Independent Code

işletim sistemi linux ve mimari x86..

load-time esnasında shared libraryler üzerinde nelerin döndüğünden daha önceki yazılarımızda  bahsetmiştik.

yalnız önceki yazılarda bahsetmiş olmamıza rağmen yine de burada tekrarlayalım.

programımızın başlamadan önce çalıştırılmaya hazırlanması için
arkada bir takım senaryolar döner ve  bu senaryolardan biriside load-time relocationdır.

load-time da 10 relocation yapmak oldu bitti ile olmaz,belirli bir zaman harcanarak bu  iş halletilir.evet 10 relocation pek fazla süre almıyacaktır fakat büyük uygulamalarda  relocation sayısı 10 olmayacaktır..

bunu hatırlattıkdan sonra programımızda başlangıc süresini en iyileme için işin erbabları  bir takım yöntemleri shared librarylerde uygulamaya koydular.




işte position independent code.

peki tam olarak nedir position independent code ? basit bir örnek ile olayı anlıyacağız.

position-independent code :    ~~~~~~         position-independent olmayan code:

301:mov eax,13                                               301:mov eax,13   
302:add eax,11                                                302:add eax,11
303:JMP şu anki konum+20                            303:JMP 323
               .                                                                  .       
               .                                                                  .
               .                                                                  .
               .                                                                  .
               .                                                                  .
323:dallanma buraya..                             323:dallanma buraya..



burada iki kod arasındaki fark nedir? birisi kesin adres isterken diğeri relative adres ile  yani kendi konumuna göreceli bir adres ile çalışmakta.

o halde position-independent code memoryde nereye yüklenirse yüklensin çalışacak.fakat bunu position-independent olmayan code için söyleyemeyiz.
düzgün çalışması için onun gerçekten belirtilen adreslerde olması gerekiyor..

peki bunu anladık o zaman bunu aklımızda tutalım ..

PIC olmadan loadtime da relocation işleminde şunu görmüştük.kod içerisinde bir değişken  ne kadar kullanılırsa onun kadar relocation sectionına kayıt koyulmaktaydı.dinamik linker daha sonra load-time da  bu kayıtlar vasıtasıyla relocation işlerini gerçekleştirecekti...

örneğin 0x505 offsetinde değişken1 kullanıldıysa ve yine aynı şekilde 0x606 offsetinde değişken1   yine kullanıldıysa o zaman relocation tablomuzda değişken1 için 2  kayıt tutulacaktı..

peki bunu anladık o zaman bunu aklımızda tutalım.

düşününki sembollerimizin adreslerini bir tabloda tutmak isteydik,o zaman dinamik linkerımıza  şu sembolün adresini al tabloya yerleştir dememiz yeterli olurdu.ve bu sayede bir değişken için  relocation tablomuz içerisinde birden fazla kayıt tutmaya gerek kalmazdı.

şu çizim anlamımıza yardımcı olabilir.. (kötü  bir çizim.)




peki bunu  anladık o zaman bunu aklımızda tutalım...

tablomuzun adı Global Offset Table kısaca GOT.ve bu tablo  writable olan got sectionın kendisidir..
got sectionının text sectiondan ne kadar uzakta olduğu daha önceden belli.o halde static linkerımız değişkene ihtiyaç duyan herhangi bir instructionımız için bu uzaklığı hesaplayıp,daha sonrada yerleştirdiği bir takım kodlarla
ilgili değişkene erişmemizi sağlayabilir..

yalnız uzaklık değişmesede adreslerimizin değiştiğini unutmuyoruz bu yüzden ilgili instructionın konumunu  alıp daha sonra global offset tablosuyla arasındaki uzaklığı toplayıp global offset tablosunun kendisini  elde ediyoruz..

kod içerisinde bir instructionın konumunu kodun icrası sırasında nasıl alabiliriz..?

işte örnek kod :

0x200:call buraya
0x201:~~~~~~~~~~ -----> ecx registerına bu adresi transfer edicez..


buraya:
mov ecx,[esp]
ret



call instructionı "buraya" dallanmadan önce bir sonraki instructionın
adresini stacke iter,bizde bu adresi buradan alabiliriz ..

peki bunu  anladık o zaman bunu aklımızda tutalım...

yapılacak olan artık tek şey bir shared library oluşturmak ve bunu bir executable file bağlayıp ardından  analizini yapmak..

shared.c  şöyle olsun :

int tut=1;

int multiply(int a,int b){
tut=tut+a;
tut=tut*b;
return tut;
}

gcc -fpic -o shared.o -c shared.c
gcc -shared -o libshared.so shared.o


parametreleriyle derleme işlemlerini yapıyoruz.

readelf ile relocation tablomuza bir göz atıyoruz.multiply içerisinde 3 kez tut değişkeni kullanıldı bunu  unutmuyoruz..

root@kali:~/shared/pic# readelf -r libshared.so

Relocation section '.rel.dyn' at offset 0x314 contains 9 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
000016d8  00000c06 R_386_GLOB_DAT    00001704   tut


tut değişkenine ilişkin bir kayıt var.R_386_GLOB_DAT typemız dinamik linkerımıza offsete symbol değerini  yükle der..

16d8 offseti global offset tablomuzun tut kayıtına ait.

artık statik linkerımız orada tutun adresi  varmış gibi kabul edip onun üzerinde
işlemler yapabilir..çünkü biliyorki dinamik linker tarafından oraya tutun kendi
adresi koyulacak..


objdump ile libshared.so dosyamızı inceliyoruz.

objdump -d -M intel libshared.so  diyerek intel assembly notasyonuyla görebilirsiniz..

root@kali:~/shared/pic# objdump -d libshared.so
000004f5 <multiply>:
 4f5:    55                                push   %ebp
 4f6:    89 e5                           mov    %esp,%ebp
 4f8:    e8 3b 00 00 00            call   538 <__x86.get_pc_thunk.cx>
 4fd:    81 c1 ef 11 00 00        add    $0x11ef,%ecx
 503:    8b 81 ec ff ff ff            mov    -0x14(%ecx),%eax
 509:    8b 10                          mov    (%eax),%edx
 50b:    8b 45 08                     mov    0x8(%ebp),%eax
 50e:    01 c2                          add    %eax,%edx
 510:    8b 81 ec ff ff ff           mov    -0x14(%ecx),%eax
 516:    89 10                         mov    %edx,(%eax)
 518:    8b 81 ec ff ff ff           mov    -0x14(%ecx),%eax
 51e:    8b 00                         mov    (%eax),%eax
 520:    0f af 45 0c                 imul   0xc(%ebp),%eax
 524:    89 c2                         mov    %eax,%edx
 526:    8b 81 ec ff ff ff          mov    -0x14(%ecx),%eax
 52c:    89 10                        mov    %edx,(%eax)
 52e:    8b 81 ec ff ff ff        mov    -0x14(%ecx),%eax
 534:    8b 00                    mov    (%eax),%eax
 536:    5d                       pop    %ebp
 537:    c3                       ret   

00000538 <__x86.get_pc_thunk.cx>:
 538:    8b 0c 24                  mov    (%esp),%ecx
 53b:    c3                        ret 



 kodları inceleyelim.
 
 4f8:    e8 3b 00 00 00           call   538 <__x86.get_pc_thunk.cx>
 4fd:    81 c1 ef 11 00 00        add    $0x11ef,%ecx
 503:    8b 81 ec ff ff ff        mov    -0x14(%ecx),%eax
 509:    8b 10                    mov    (%eax),%edx
 50b:    8b 45 08                 mov    0x8(%ebp),%eax
 50e:    01 c2                    add    %eax,%edx
 

 
ilk 2 instruction ile  global offset tablemızın base adresi hesaplanıyor.---->0x11ef+0x4fd---->0x16ec
 
3.instruction ile global offset table başlangıcı base adress olarak kullanılıp tut1'in adresini tutulduğu yer   eax registerına transfer ediliyor.
0x16ec-0x14=0x16d8 bu değer eaxa transfer ediliyor. 

4.instruction ile tut1'in içerisindeki değer edx registerına transfer ediliyor.
5.instructionımız ile  a değişkenimizin(dikkat stackden çekiliyor) değeri eax registerına transfer ediliyor.
son instructionımız ile eax ve edx registerları toplanıp sonucumuz edx registerına yükleniyor.


 510:    8b 81 ec ff ff ff        mov    -0x14(%ecx),%eax
 516:    89 10                    mov    %edx,(%eax)

 
burada ise tut değişkenimizin adresini eax registerına transfer ediyoruz,ve sonra da toplamımızı(edx)  tut değişkenimizin adresine transfer ediyoruz...
 
 
sanırım olayları daha iyi anladık.adreslerin değişmesi durumunda herhangi bir problem yok  çünkü konuma göre bir hesaplama var o da kod içerisinde hesaplanıyor..


evet bu statik analizden sonra gnu debuggerımız ile dinamik bir analiz
yapalım ve konumuzu bitirelim..
 
shared libraryimizi executable dosyamıza bağlıyoruz..
 
exec.c şöyle olsun :

int main {}

gcc -o exec.o -c exec.c
gcc -o exec.out -L. -lshared


gdb -q exec.out diyoruz..ve daha sonra şöyle...

Dump of assembler code for function multiply:
   0xf7fd54f5 <+0>:    push   ebp
   0xf7fd54f6 <+1>:    mov    ebp,esp
   0xf7fd54f8 <+3>:    call   0xf7fd5538 <__x86.get_pc_thunk.cx>
   0xf7fd54fd <+8>:    add    ecx,0x11ef
   0xf7fd5503 <+14>:    mov    eax,DWORD PTR [ecx-0x14]
   0xf7fd5509 <+20>:    mov    edx,DWORD PTR [eax]
   0xf7fd550b <+22>:    mov    eax,DWORD PTR [ebp+0x8]
   0xf7fd550e <+25>:    add    edx,eax
   0xf7fd5510 <+27>:    mov    eax,DWORD PTR [ecx-0x14]
   0xf7fd5516 <+33>:    mov    DWORD PTR [eax],edx
   0xf7fd5518 <+35>:    mov    eax,DWORD PTR [ecx-0x14]
   0xf7fd551e <+41>:    mov    eax,DWORD PTR [eax]
   0xf7fd5520 <+43>:    imul   eax,DWORD PTR [ebp+0xc]
   0xf7fd5524 <+47>:    mov    edx,eax
   0xf7fd5526 <+49>:    mov    eax,DWORD PTR [ecx-0x14]
   0xf7fd552c <+55>:    mov    DWORD PTR [eax],edx
   0xf7fd552e <+57>:    mov    eax,DWORD PTR [ecx-0x14]
   0xf7fd5534 <+63>:    mov    eax,DWORD PTR [eax]
   0xf7fd5536 <+65>:    pop    ebp
   0xf7fd5537 <+66>:    ret   

End of assembler dump.

0xf7fd54fd+0x11ef ------> 0xf7fd66ec -----> GOT için base addresimiz.
0xf7fd66ec-0x14 ------> 0xf7fd66d8 -----> tut değişkeninin adresini barındıran yer


o halde şunları yapalım..

gdb-peda$ x 0xf7fd66d8
0xf7fd66d8:    0xf7fd6704 -------> buradaki tutun adresi 
gdb-peda$ x 0xf7fd6704
0xf7fd6704 <tut>:    0x00000001 -------> tutun ilk değeri(tut=1)


peki x64 mimaride işler nasıl dönüyor?

dikkat ettiyseniz   konumu elde edebilmek için bir yere zıplıyor hopluyor ve oradan dönüp işte elde ettim adresi diyorduk çünkü x86 mimarisi
ile instruction pointerın(EIP) kendisine direk erişimimiz yok.

x64 mimarisinde RIP adında bir registerımız mevcut,ve bu register bu iş için kullanılıyor.

aynı dosyayı -fPIC ile relocatable file çevirirseniz ve sonra shared dosyaya çeviririp disass ederseniz.şunu görürsünüz..

0x00007ffff7bd865a : mov    0x200277(%rip),%rax        # 0x7ffff7dd88d8

bu adresimizde 0x7ffff7dd88d8 -------> 0x7ffff7dd8920 yani tut değişkenimizin adresi..




peki bu işler functionlar için nasıl dönüyor ?

fonksiyonun kendisi çağrılmayana kadar dinamik linker onun adresini çözümlemez ve bu adres çözümleme işlemine binding denir.bu olayların
kendisinede  LAZY BINDING denilir.

ve bu işlerin nasıl döndüğü ELF executable file analizinde anlatmıştık..
o yüzden sizi oraya jumplıyoruz..


EOF

Hiç yorum yok:

Yorum Gönder