17 Ağustos 2016 Çarşamba

ELF { eXecutable and Linkable Format }

ELF-eXecutable and Linkable format.


ELF,icra edilebilir ve bağlanabilir format olarak türkçeleştirilebilir.peki nedir tam olarak ELF bu yazıda kısaca ELF hakkında konuşucaz.


ELF  executable(çalıştırılabilir),relocatable file,shared libraries için ortak bir formattır.ELF oldukça geniş analiz edilebilir fakat biz burada basit analiz yaparak(bilgimiz dahilinde) en azından bir ELF executable dosya içinde neler dönüyor bunları anlamaya çalışıcaz.




örnek olarak basit bir program yazıp bunun analizini yapalım.

example.c şöyle olsun


#include <stdio.h>

void printzz(char arr[]);

char chr=0x69;

int main(){
int stacks_ =0x7396;
printf("hello world..!\n");
printzz("hello world again..!\n");
}

void printzz(char str[]){
printf("mesaj ------ %s",str);
}

readelf programı ile "readelf -h example.out" diyerek executable dosyamızın kendisine ilişkin ELF Headerını okuyabiliriz.



ELF Header :


  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048320
  Start of program headers:          52 (bytes into file)
  Start of section headers:          3952 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           40 (bytes)
  Number of section headers:         31
  Section header string table index: 28



ELF Header dosyanın kendisi için bir yol haritasıdır herhangi bir bozulmada işletim sistemi ELF dosyasına ilişkin bilgi edinemez veya yanlış bilgi edinir mesela 7f değilde 8f diye başlarsak

root@kali:~/Desktop/shared/example# ./8f.out
bash: ./8ff.out: cannot execute binary file: Exec format error

root@kali:~/Desktop/shared/example# readelf --all 8f.out
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start

gibi hatalar alırız.veya program headerın başlangıcını  52 yerine 132 yaparsak

root@kali:~/Desktop/shared/example# ./example.out
Segmentation fault

gibi bir hata alırız.

şimdi kısaca ELF Headerı inceleyelim.


7f 45 4c 46 magic number olarak biliniyor yani bu dosyanın kendisinin bir ELF dosyası olduğunu belirtiyor.

Class ise ELFin 32-bit mi 64-bit mi  olduğunu söylüyor.

Data kısmında ELF'in little endian/big endian mı olduğu bilgisi tutuluyor.

Type kısmı ile ELF'in türü belirtiliyor.(1, 2, 3, 4 sırasıyla  relocatable,executable,shared,core  dosyalarını belirtir)

Type dosyanın 0x10 offseti içerisindedir buraya bakınca bizim example.out dosyamız için 02(executable) olduğunu görüyoruz.


Entry point address=0x8048320 bu address bilgisi ise dosyanın ilk  çalıştırılmaya başlayacağı adresi  belirtir.

Start of program headers:52 (bytes into file)      
Start of section headers:3952 (bytes into file)

yukarıdaki bu iki bilgi ise program header ve section headerın dosyanın 0.konumundan  ne kadar uzakta olduğunu bize bildirir.

program headerın başlangıcının dosyanın başlangıcından  52 byte ötede olduğunu buraya bakarak anlıyoruz.

yine buradan ELF headerın kendi sizenı,program headerlarının ve section headerlarının  entrylerinin(kayıtlarının) sizenı öğrenebiliyoruz.


sanırım ELF header için bu kadar göz atış yeterli.

bir sonrası için section headerlara  göz atalım.section headerlar linkleme aşamasında linker tarafından göz atılan yer. burayı inceleyecek olursak bir çok section görmekteyiz bunlardan bir kaçını incelemeye çalışalım.

"readelf --sections example.out"  ile sectionlarımızı görebiliyoruz...bunlardan bazılarını anlatalım.

.text         ------ > çalıştırılabilir(executable) kodlar burada tutulur.
.init_array   ------ > burada constructorlarımızın adresini bulabiliriz.
.fini_array   ------ > burada destructorlarımızın adresinin bulabiliriz.

constructor ve destructorlar için bilgi verelim.main fonksiyonunun öncesi ve sonrasında çalışacak olan fonksiyonları belirtir..

şöyle basit bir örnek ile anlayabiliriz..
void before_main (void) __attribute__((constructor));
void after_main (void) __attribute__((destructor));

int main (void){
  printf ("burası main()\n");
}

void before_main (void){
  printf ("burası before_main()\n");
}

void after_main (void){
  printf ("burası after_main()\n");
}

çıktı şu şekilde olacaktır:

burası before_main()
burası main()
burası after_main()


devam edelim......


.data         ------ > initialized data(ataması yapılmış) için kullanılmakta.mesela global olarak bir variable tanımladığımızda biz bunu şu şekilde tanımlamıştık --> char chr=0x69; ve buna 0x69 değerini atamıştık.bu sectionda bu değerin tutulduğunu görmekteyiz.0x770 offsetinde 0x69 değeri tutulmakta.

.bss         --------> bu section içerisinde uninitialized datalarımız için yer ayrılmakta kendisi var ama değeri henüz yok.örneğin int exec_; gibi..

.shstrtab   ---------> sectionların adlarının  tutulduğu kısım.(.interp,.text,.data,......)

.symtab      --------> burası sembol tablosunun tutulduğu sectiondır.sembollere örnek olarak kod içerisinde tanımladığımız function(fonksiyon) ve variable(değişken) isimlerini  verebiliriz..örnek olarak -- > printzz,main,chr...

ayrıca programımızdaki tüm sembolleri "readelf --symbols example.out" ile görebiliriz.

.strtab      --------> sembol tablosu içerisinde bulunan sembollerin adlarının tutulduğu yer.


bu son  bahsettiğimiz sembol tablosu ile linker(static) executable dosyayı oluştururken sembollerin adreslerini buradan bulup yerleştirir ve bu şekilde executable file için sembol çözümlemesi bu aşamada tamamlanmış olur.chr adında bir değişken tanımladığımızda ve bunu kod içerisinde kullandığımızda compile aşamasının linkleme kısmında(eXecutable formata dönüşümde) chr sembolünün kod içerisinde bulunduğu yerlere sembol tablosunda ona karşılık düşen adres yerleştirilir.ve eğer bu sembol bir çok yerde kullanılıyorsa tabiki  her seferinde linker sembolün adresini tablodan bulur ve yerleştirir...


yalnız sembol tablomuzun tamamını göz gezdirdiğimizde tüm semboller için adres olmadığını görebiliyoruz mesela bizim printf functionımız için herhangi bir adres görünmüyor.bu kısım ise dynamic linkerın işi.

görüldüğü gibi herhangi bir adres ataması yok.

00000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.0

.dynsym -- >  dynamic linkleme için sembol tablosudur.


aklımıza şöyle bir soru takılabilir? hem symtab hem de dynsym sectionlarının olması saçma değilmi ? sonuçta ikiside aynı sembolleri barındırmakta hatta symtab sectionı dynsym sectionınıda kapsayan geniş bir sembol tablosu.evet aslında haklısınız,fakat zamanında shared object libraryler ve dynamic linkleme meseleleri yok iken bu gibi sectionlara ihtiyac duyulmamış herşey symtab üzerinden halletilebiliyormuş ama ne zaman dynamic linkleme ihtiyacı ortaya çıkmış işte o zaman bu gibi(.dynsym) sectionlara ihtiyaç duyulmuş.

şu olayı bilince daha da iyi anlıyacağız neden iki section var?

dosyayı çalıştırmak için sectionlarımızın tamamına işletim sistemi ihtiyaç duymaz yani ne demek bu şimdi ? şöyle ki  sectionları iki gruba ayırabiliriz.allocable ve non-allocable.

allocable olanların kesinlikle memorye maplenmesi yani yüklenmesi gerekirken non-allocable olan sectionlar için bu gerekli değildir.peki onlar neden var ? onlar genelde linker,debugger ve başka bazı toollar için faydalı olan sectionlar.mesela ELF eXecutable file der ki : symtab ha olmuş ha olmamış umrumda değil çünkü zaten olan olmuş ben olmuşum eXecutable file. o halde bu bir fazlalıkmıdır ? evet fazlalıktır.dosyanızın içerisinde bu sectionı bulup tamamen silebilir ve dosyanızın çalıştığını görebilirsiniz..

bu silme sonucu belirli offsetlerde kayma olucak ve bu sebeple readelf ile tekrar dosyaya ilişkin sectionları okumak istediğimizde hata alıcaz.isterseniz silmeden direk bu sectionları sıfırlayabilirsiniz.ya da silip dosyanızı çalıştırabilirsiniz silme sonrası readelf ile

"readelf --all example.out" derseniz :

readelf: Error: Section headers are not available!

gibi bir hata alırsınız , readelf bu hata ile bize section headerların mevcut olmadığını ifade ediyor.
bu sadece readelf gibi toollar için sorun.dosyamızda herhangi bir bozulma yok.




gereksiz olan(eXecutable için) bir section da .strtab istersek bunuda kaldırabiliyoruz dediğimiz gibi bu sectionların bazıları non-allocable yani kısaca çalıştırılabilir dosyamızın ihtiyaç duymadığı sectionlardır.

kafamızda daha iyi oturtabilmek için .shstrtab kendisini silelim ve tekrar readelf --sections ile okuma yapıp section isimlerinin yerinin boş olduğunu görelim.

bu bahsi daha fazla uzatmayıp kaldığımız yerden devam edelim.

.dynstr ---> bu section içerisinde dinamik linkleme aşamasında lazım olacak  string ifadeler tutulur.burayı kontrol ettiğimizde libc.so.6 yani shared object file'ın kendisi ve dinamik sembol tablosu içerisinde bulunacak olan functionların isimlerini görüyoruz..eğer libc.so dışında başka shared object file ile linklenmiş olsaydı executable filemız,o zaman onların isimlerini ve onların içerisinden bize lazım olan functionların isimlerinide burada görmüş olacaktık.

dynamic sembol tablomuzu "readelf --dyn-sym example.out" ile görebiliyoruz.
yine dikkatimizi çeken  unsur dinamik sembol tablosunun içerisinde de printf functiona ait sembol adresi bulunmamaktadır.

00000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.0 (2)

dinamik sembol tablosunun tek amacı linklenmemiş sembolleri görmek ve onlara dinamik linker aracılığı ile müdahalede bulunmak ..

bu tablolara iki section üzerinden erişilebilmekte.bunlar nelerdir ? diye soracak olursak.

rel.dyn ve rel.plt sectionlarıdır.

rel.dyn --- > burası load-time da relocation için kullanılır.
rel.plt  --- > plt baz alınarak relocation işlemleri için burası kullanılır.

"readelf -r example.out"  diyerek bu iki tabloya ulaşabiliriz.

buraya kadar example.c üzerinden analiz yaptık fakat işin içine dynamic linker gibi birşey girince bu örneğin şu an bizim için yetersiz olduğunu görmekteyiz bu yüzden bizde bir adet shared object file oluşturup daha sonra bununla bir eXecutable file oluşturarak işin dinamiklik kısmında nelerin döndüğünü daha iyi anlamak için bir başka executable file oluşturuyoruz.


shared.c adında bir dosyamız ve içeriği şöyle :


char chr=0x70;
int  number=10;

int multiply(int x){
return number * x;
}

void ixir(){
number-=3;
chr=0x71;
}

bu dosyayı direk eXecutable file olarak derlemiyoruz aşağıdaki parametrelerle c dosyasını ELF relocatable file olacak şekilde derliyoruz.:

gcc -c shared.c -o shared.o


shared.o adında bir relocatable filemız oldu.bu dosyayı şimdi shared object dosyasına çevirmek için şunu yapıyoruz.

gcc -shared -o libshared.so shared.o
libshared.so bizim shared object filemız.bir sonraki aşamaya geçiyoruz.

execute.c adında bir dosya oluşturuyoruz ve içeriği şöyle :
#include <stdio.h>

extern char chr;
extern int  number;
extern int multiply(int);
extern void ixir();

int no_shared_object=0x47;

void printzz(char str[]){printf("merhaba --> %s",str);}

int main(){

printzz("xyz\n");
printf("number --- > %d\n",number);
printf("chr    --- > %c\n",chr);

int result=multiply(10);
printf("sonuc ---- > %d\n",result);

ixir();

printf("ixirden sonra number --- > %d\n",number);
printf("ixirden sonra chr    --- > %c\n",chr);

result=multiply(10);
printf("ixirden sonra sonuc ----- > %d\n",result);
}
bunuda  relocatable file olacak şekilde derliyoruz.şu şekilde :

gcc  -c execute.c -o execute.o

şu an elimizde iki adet ELF relocatable file var ve bunları birleştirerek bir ELF eXecutable file oluşturuyoruz.

gcc -o execute.out execute.o -L. -lshared diyoruz ve execute.out dosyamızı elde ediyoruz.yalnız burada dikkat edilmesi gereken bir husus shared.so dışında başka librarylerinde bu executable dosyasına bağlandığıdır.

"ldd execute.out" ile executable file bağlanan diğer shared object fileların kendisini görebilirsiniz.ayrıca bunların bağlanma sebepinide umarım anlamışızdır.sebepi içerde kullandığımız(bazıları arkaplanda) fonksiyonlar örneğin printf fonksiyonu ve bir takım başka işler..


artık ELF eXecutable file analizine bu dosya üzerinden devam edelim.birçok sectionı daha önce görmüştük.

biz tekrar "readelf -r execute.out" diyoruz ve dinamik linkleme aşamasında yapılacak olanları anlamaya çalışıyoruz.

bu arada export LD_LIBRARY_PATH=. diyerek libshared.so yu nerede barındırıyorsak orayı bu PATH'e belirtiyoruz.

dynamic linkerın shared objectleri memorye yüklemek için ararken baktığı bir kaç yerden biriside bu PATHin belirttiği yer/yerlerdir.

devam edelim...


"readelf --dynamic execute.out" diyerek .rel.dyn ve .rel.plt sectionlarına  bir göz atalım.


Relocation section '.rel.dyn' at offset 0x404 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049a04  00000506 R_386_GLOB_DAT    00000000   __gmon_start__
08049a30  00000d05 R_386_COPY        08049a30   number
08049a34  00000b05 R_386_COPY        08049a34   chr

Relocation section '.rel.plt' at offset 0x41c contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049a14  00000207 R_386_JUMP_SLOT   00000000   printf@GLIBC_2.0
08049a18  00000307 R_386_JUMP_SLOT   00000000   multiply
08049a1c  00000407 R_386_JUMP_SLOT   00000000   ixir
08049a20  00000607 R_386_JUMP_SLOT   00000000  __libc_start_main@GLIBC_2.0
 



buradaki TYPE kısmının dinamik linker için bir anlamı var  örneğin multiply için  R_386_JUMP_SLOT türünü görmekteyiz.bu typeın dinamik linker için anlamı  offset içerisindeki değerin PLT(aşağılarda anlıyacağız) içerisindeki multiply functionın konumu olduğudur ve buna göre yerleştirmeyi yapar.aşağılarda her bir entryinin başında jump emrini görünce bu olayı daha iyi anlıyacağız..


gnu debuggerımız ile bu olayları görebiliriz.


root@kali:~/Desktop/examples# gdb -q execute.out

Reading symbols from execute.out...(no debugging symbols found)...done.
(gdb) x 0x08049a30
0x8049a30 <number>:    0x00000000
(gdb) x 0x08049a34
0x8049a34 <chr>:    0x00000000
(gdb) break _start
Breakpoint 1 at 0x80484c0
(gdb) r
Starting program: /root/Desktop/examples/execute.out

Breakpoint 1, 0x080484c0 in _start ()
(gdb) x 0x08049a30
0x8049a30 <number>:    0x0000000a
(gdb) x 0x08049a34
0x8049a34 <chr>:    0x00000070

dikkat..! breakpointi _start fonksiyonuna koyuyoruz çünkü  programımız için entrypoint noktası _start functiondır.dinamik linker shared object libraryleri yükleyip , relocation işlemlerini(yeniden yerleştirmeleri) yaptıktan sonra programın entrypoint noktasına atlama yapar ve program çalışmaya başlar.

bizde bu yüzden _starta break koyup önce değerlerimize bakıyoruz sonra run deyip _starta giriş veriyoruz ve sonra tekrar variablelarımızın(chr,number) içeriğini kontrol ediyoruz ve yerleştirmenin gerçekleştiğini görüyoruz..

yukarıdaki incelemelerden bunun böyle olduğunu anlıyoruz peki variablelarımız/değişkenlerimiz için bu işlem geçerliyken functionlarımız içinde aynımı yani .rel.plt içerisinde belirtilen adresler direk multiply kendisimi yoksa  yukarıda bahsettiğimiz gibi PLT entry ile bir endirek atlamamı var..teorikde böyle ama pratik yapalım.


multiply functionımızın  için belirtilen offset de ne var ? ilk önce o offsetin içeriğini inceleyelim ve daha sonra biz programın kendi akışı içerisinde multiply functionı nasıl çağırdığına bir bakalım.gnu debuggerımız yine işbaşında..

root@kali:~/Desktop/examples# gdb -q execute.out
Reading symbols from execute.out...(no debugging symbols found)...done.
(gdb) x  0x08049a18
0x08049a18:    0x8048486
(gdb) break *0x0804862e 
Breakpoint 1 at 0x804862e
(gdb) r
Starting program: /root/Desktop/examples/execute.out
merhaba --> xyz
number --- > 10
chr    --- > p

Breakpoint 1, 0x0804862e in main ()

=> 0x0804862e <+87>:    call   0x8048480 <multiply@plt>

öncelikle 0x08049a18 içerisinde 0x8048486 değerini görmekteyiz.
multiplyın kendisi ise call emri ile çağrılırken bunun 6 byte gerisinde.
sanki herşey sistematik  gibi deyip yolumuza devam ediyoruz..

buradayken next instruction değilde bu call emrinide akışın içine dahil etmek için stepi diyoruz.

devam edelim.....

(gdb) stepi
0x08048480 in multiply@plt ()
(gdb) disass
Dump of assembler code for function multiply@plt:
=> 0x08048480 <+0>:    jmp    *0x8049a18
   0x08048486 <+6>:    push   $0x8
   0x0804848b <+11>:    jmp    0x8048460
End of assembler dump.
(gdb)


yine o adresi görüyoruz merakımızı gidermek için devam diyoruz.. jmp emri ne garipdir ki 6 byte ötedeki push emrine dallanıyor..ya ne bekliyorduk ki zaten yukarlarda bunu incelemiştik ..


neyse biz incelemeye kaldığımız yerden devam ediyoruz.. evet bir 6 byte ötede stacke 0x8 itiliyor ve daha sonra 0x8048460 adresine dallanılıyor.

(gdb) x/2i 0x08048460
=> 0x8048460:    pushl  0x8049a0c
   0x8048466:    jmp    *0x8049a10


(gdb) x/x 0x8049a10
0x8049a10:    0xf7fedf00


işte nihayetinde gideceği yer 0xf7fedf00  peki burası neresi ? jmp emri gerçekleşsin ki bunu görebilelim hemen bir stepi diyoruz ve oranın neresi olduğunu anlıyoruz..


(gdb) stepi
0xf7fedf00 in ?? () from /lib/ld-linux.so.2


meğersem burası dynamic linkerın yeri ve yurduymuş..büyük bir şaşkınlık geçiriyoruz..


kendimizi kandırmayalım teorikde şunu biliyorduk ,dynamic linker  runtime esnasında  adress çözümlemesi yapacak kimin için multiply için peki daha sonra ne yapacak işte multiply adresini barındıran adresin içine bu yeni adresi gömecek..yani 0x8049a18 adresinin içerisinde push emrinin adresi 0x08048486 değil de artık gerçek multiply adresi olacak.evet gelecek zamanı bırakıp,olayların akışına bırakalım kendimizi bakalım neler oluyor..

(gdb) stepi
0xf7fedf00 in ?? () from /lib/ld-linux.so.2
(gdb) ni
0xf7fedf01 in ?? () from /lib/ld-linux.so.2
              .
              .
              .
              .

evet yaklaşık bir 8 instruction sonra

(gdb) ni
0xf7fd1520 in multiply () from ./libshared.
so


multiplyın anayurdu olan libshared.so içerisine dallanma gerçekleşti ve buradan multiply kendisi çalışmaya başlayacak.


bu multiply çalışmadan önce biz 0x8049a18 adresinin içerisinde ne var yani multiplyın adresini barındıran adresin içinde ne var ona bir gözatıyoruz..

(gdb) x 0x8049a18
0x8049a18:    0xf7fd1520

evet adres değişimi olmuş hemen bu adres içerisinde ne var ona bakıyoruz.aslında ne olduğunu biliyoruz ama teorik bilgimizden şüphe edip pratiğe döküyoruz.(orada multiply olmalı)

(gdb) x/6i 0xf7fd1520
=> 0xf7fd1520 <multiply>:    push   %ebp
   0xf7fd1521 <multiply+1>:    mov    %esp,%ebp
   0xf7fd1523 <multiply+3>:    mov    0x8049a30,%eax
   0xf7fd1528 <multiply+8>:    imul   0x8(%ebp),%eax
   0xf7fd152c <multiply+12>:    pop    %ebp
   0xf7fd152d <multiply+13>:    ret  



evet final kısmına geldik işte bizim orjinal multiplyımız burada.şimdi biraz gerilere gidip orada yaşanan olayları daha iyi anlıyoruz..

=> 0x08048480 <+0>:    jmp    *0x8049a18

bu kısmı yukarıda hatırladık değilmi ? olduki tekrar multiply çağrıldı şimdi
bu emir bizi nereye götürecek 0x8049a18 adresinin içerisindeki adres neyse oraya peki orada ne var ? 0xf7fd1520 adresi ve orada kesinlikle artık multiply var yani ikinci çağrımda artık adres çözümlemesi ile uğraşmaya gerek yok yapılacak tek şey direk çözümlenmiş adres olan multiplyımıza zıplamak.

.rel.plt section içerisindeki tüm  fonksiyonlarımıza bakarsak adres çözümlemesi yapılması için  hepsinin jmp 0x8048460 gibi bir adrese jumpladığını görebiliyoruz bu adrese jumplamakla dynamic linkera jumplama yapılıyor ki fonksiyonun adresinin yeniden yerleştirmesi/çözümlemesi yapılsın diye..


bu olaylara literatürde Lazy Binding deniliyor.ELF PLT(Procedure Linkage Table) üzerinden lazy bindingi desktekliyor.

sebepini az çok tahmin edebiliriz programın başlangıcından önce yani load-time içerisinde dinamik linkerın bu işlerle uğraşmasını istemiyoruz çünkü bazen kodlarımız içerisinde öyle koşullar olur ki bazı fonksiyonlar bu süreç çerisinde hiç çağrılmayabilir,o halde çağrılmayacak olan fonksiyonun ne diye adres çözümlemesi load-time aşamasında dinamik linker tarafından yapılsın ki diye düşünülmüş.eğer ihtiyaç olunan fonksiyonlar varsa bunları bir kereliğine mahsus dinamik linker ile runtime sırasında çözeriz demişler.ve gerçekten bunu yapmışlar..

şimdi bu olayların tamamını aklımızda tutuyor ve  bu olayların böyle güzel güzel cereyan etmesine sebep olan diğer sectionlardan da bahsetmenin yeri ve zamanı geldi diyoruz.



.plt  ---------- > bu section içerisinde plt entrylerini barındırmakta hemen bu sectiona göz atalım.(0x8048460 ile 0x80484b0 arasındaki kısım bu sectiona ait.)


!..eğer sizde sectionlarınızın adresini görmek isterseniz gdb de "mai i sect" komutunu kullanabilirsiniz..

ayrıca bir şey daha   jmp    *0x8049a14   emrinde jumplanacak yer 0x8049a14 değil onun içerdiği değerin kendisidir..


(gdb) x/16i 0x8048460
   0x8048460:    pushl  0x8049a0c
   0x8048466:    jmp    *0x8049a10
 
   0x8048470 <printf@plt>:    jmp    *0x8049a14    ---->
   0x8048476 <printf@plt+6>:    push   $0x0          ---->  printf'in plt entrysi   
   0x804847b <printf@plt+11>:    jmp    0x8048460     ---->

   0x8048480 <multiply@plt>:    jmp    *0x8049a18   
   0x8048486 <multiply@plt+6>:    push   $0x8         
   0x804848b <multiply@plt+11>:    jmp    0x8048460     

   0x8048490 <ixir@plt>:    jmp    *0x8049a1c
   0x8048496 <ixir@plt+6>:    push   $0x10
   0x804849b <ixir@plt+11>:    jmp    0x8048460

   0x80484a0 <__libc_start_main@plt>:    jmp    *0x8049a20
   0x80484a6 <__libc_start_main@plt+6>:    push   $0x18
   0x80484ab <__libc_start_main@plt+11>:    jmp    0x8048460

  

fonksiyonlarımızın adreslerinin tutulduğu yer(PLT entryler içindeki dolaylı jumplamada kullanılan adreslerdir) ve adres çözümlemeleri yapıldıktan sonra gerçek adresin yerine koyulduğu yer? evet o yer  işte o yer .got.plt sectionımız.

orayıda bu akış içerisinde inceleyecek olursak  şöyle birşeyle karşılaşıyoruz..

(gdb) x/10w 0x8049a08
0x8049a08: 0x08049914    0xf7ffd918    0xf7fedf00    0xf7e45f30   ----- >  
                                                                                                                       got.plt      
0x8049a18: 0xf7fd1520    0x08048496    0xf7e15500    0x00000000   ----- >

burası fonksiyonlarımızın gerçek adresini barındırmakta hatırlıyoruz ki 0xf7fd1520 adresi multiplyımızın orjinal adresiydi ve multiplyımızın plt entrysi içerisinde jmp *0x8049a18 şöyle bir kod görüyoruz..sanırım herşey daha açık hale geldi..

ilk sefere mahsus plt entrylerimizden herhangi birinde pushlamalarla stacke itme görüyoruz bunun sebepide push itmesiyle dinamik linkera fonksiyonun gerçek adresini nereye yerleştireceğini söylemek...

[.rel.plt] + 0x0 --- >  0x08049a14 ----> printf functionın adresinin tutulduğu yer..
[.rel.plt] + 0x8 --- >  0x08049a18 ----> multiply functionın adresinin tutulduğu yer..
[.rel.plt] + 0x10 -- >  0x08049a1c ----> ixir functionın adresinin tutulduğu yer...


yukarıya bakarsak stacke itilen değerlerin ne anlama geldiğini daha iyi anlamış oluruz..


bu şekilde uzunda olsa iki sectionın(.plt,.got.plt ) daha runtime sırasında(bu artık load-time değill runtime) dynamic linking de ne işe yaradığını öğrenmiş olduk.

sanırım dynamic linkleme aşaması ile ELF file sectionları arasındaki ilişkiyide anladık.artık geriye program headerdan söz etmek kaldı.zaten program headerda bu sectionların bir araya getirilmesiyle oluşan segmentlerden oluşmakta..

son incelemiş olduğumuz execute.out dosyamızı analiz edelim ve buradan program headerı gösterelim.

"readelf --program-header execute.out" diyerek program headerımızı görüyoruz.

program headerın akış içerisindeki yerinden bahsetmeye çalışalım.

normal olarak program header içerisindeki LOAD segmentlerini işletim sistemi memorye yüklüyor(load_elf_binary) ve ayrıca INTERP segmentine bakıyor eğer bu mevcutsa  o zaman bu durumda memorye dynamic linkerıda yüklüyor.(load_elf_interp)  INTERP segmenti içerisinde dynamic linkerın pathi mevcut.bizde buraya bakınca şunu görüyoruz ---> /lib/ld-linux.so.2

daha sonra kontrolü dynamic linkerın kendisine bırakıyor fakat bırakmadan önce dynamic linkera runtime da lazım olucak olan bir takım bilgileri barındıran auxiliary vektörünüde stack üzerinden gönderiyor.


bu vektörün içeriğinden biraz bahsetelim.

AT_PHDR --- > program için program headerın adresi
AT_PHENT -- > progam headerdaki her bir kayıtın uzunluğu
AT_PHNUM -- > ve kayıt sayısı

bu 3 yardımcı bilgi ile memorye yüklenilen programın segmentlerini dinamik linkera tanımlıyor.

ve ayrıca bu vektör ile şu bilgiler de stacke geçiriliyor..

AT_ENTRY --- >
programın başlangıc adresi, dynamic linker iş bitince nereye jumplaması gerektiğini buradan öğreniyor.

AT_BASE  --- >  dynamic linkerın yüklenildiği adresi belirtiyor.

daha önce de dediğimiz gibi dynamic linkerın buradaki ilk görevi programın ihtiyaç duyduğu libraryleri bulup yüklemek.

burada yine gördüğümüz DYNAMIC segmenti aslında dynamic sectionın kendisi.dynamic linker bu segment içerisinde STRTAB adında bir pointerı buluyor ve bu  pointer dosyanın string tablenı işaret ediyor.

string table da tahmin edebileceğiniz gibi libraryleri ve onlara ait functionları belirten stringler.

daha sonra dinamik linker işi bitirince programımıza kontrolü bırakıyor.

buraya kadar kısaca ELF Headerı,Section Headerı ve Program Headerı anlatmış/anlamış olduk.


eğer buradaki bilgilerin yeterli gelmediği kanısındaysanız yapacağınız tek şey araştırmak/bulmak ve pratik yapmak..

nınızay unos...---> little endian/big endian ?

Hiç yorum yok:

Yorum Gönder