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