ELF ve yükleme / rehber
"Dinamik bağlama: GOT, PLT ve sembol çözümleme"
GOT, PLT, relocation ve sembol çözümleme. Bir çağrının gerçek hedefini bulmak.
Bu sayfada
- 1. GOT ve PLT Çalışma Mekanizması
- Tembel Bağlama (Lazy Binding) Adımları:
- 2. readelf ile Relocation Tablolarını İncelemek
- Başlıca Relocation Tipleri (x86-64):
- 3. RELRO Güvenliği: Partial ve Full RELRO
- 4. PLTHook Mekanizması ve Modern Karşılıkları (Frida & eBPF)
- Modern İkizleri:
- 5. Sembol Çözümleme Sırası ve LD_PRELOAD
- İncelemeyi Derinleştir
- Kaynaklar
Linux üzerinde dinamik olarak bağlanan (dynamically linked) bir ELF dosyası, harici kütüphanelerdeki (örneğin libc.so.6 içindeki puts veya malloc) fonksiyonların adreslerini derleme anında bilemez. Çünkü paylaşımlı kütüphaneler her çalıştırmada ASLR nedeniyle belleğin farklı bir adresine yüklenebilir.
Bu sorunu çözmek için ELF mimarisi iki temel veri yapısı kullanır:
- GOT (Global Offset Table): Verilerin ve fonksiyonların nihai bellek adreslerini tutan işaretçi tablosu.
- PLT (Procedure Linkage Table): Fonksiyon çağrılarını dinamik yükleyici üzerinden ilk seferde çözümleyip (lazy binding) GOT'a yazan çalıştırılabilir atlama tablosu.
Tersine mühendislikte harici çağrıları izlemek, fonksiyonların kancalanmasını (hooking) anlamak ve bellek korumalarını (RELRO) kavramak için GOT PLT mekanizması anahtar roldedir.
1. GOT ve PLT Çalışma Mekanizması#
Aşağıdaki şema, ilk kez çağrılan bir puts("Merhaba") fonksiyonunun PLT ve GOT arasındaki adım adım yolculuğunu gösterir:
Kullanıcı Kodu PLT Bölümü (.plt) GOT Bölümü (.got.plt)
+-----------------------+ +----------------------------+ +----------------------------+
| call puts@plt | ------> | puts@plt: | ------> | GOT[puts] |
+-----------------------+ | jmp *GOT[puts] | | (İlk başta PLT'nin |
| push $reloc_offset | <------ | ikinci satırına döner!) |
| jmp PLT0 | +----------------------------+
+----------------------------+ |
| |
v v
+----------------------------+ +----------------------------+
| PLT0 (Ortak Çözücü): | ------> | _dl_runtime_resolve() |
| push *GOT[1] (link_map) | | (libc.so içinde puts'u |
| jmp *GOT[2] (dl_resolve)| | bulur ve GOT[puts]'a |
+----------------------------+ | gerçek adresi yazar) |
+----------------------------+
|
v
puts() asıl adresi çalışır.
Tembel Bağlama (Lazy Binding) Adımları:#
- İlk Çağrı:
call puts@pltişletilir. Kontrolputs@pltadresine geçer. - İlk Atlama:
jmp *GOT[puts]çalışır. Ancak ilk andaGOT[puts]hücresi henüzputsadresini tutmaz;puts@plt'nin hemen altındakipush $reloc_offsetsatırını gösterir! - Çözücüye Geçiş: Fonksiyonun relocation ofseti yığına atılır ve
jmp PLT0ile dinamik yükleyicinin ortak çözücüsüne atlanır. - Çözümleme ve Güncelleme: Dinamik yükleyicinin
_dl_runtime_resolve()fonksiyonu sembol tablosunu tarar,libc.soiçindekiputssanal adresini bulur veGOT[puts]hücresine kalıcı olarak yazar. - İkinci Çağrı: Program daha sonra tekrar
puts@pltçağırdığında,jmp *GOT[puts]doğrudan çözümlenmiş adrese atlar; yükleyici bir daha devreye girmez.
2. readelf ile Relocation Tablolarını İncelemek#
Bir ELF dosyasının dış bağımlılıklarını ve relocation kayıtlarını readelf -r ile görebilirsiniz:
readelf -r ./ornek
Örnek çıktı:
Relocation section '.rela.dyn' at offset 0x5a0 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000003df8 000200000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main + 0
000000003e00 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x600 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000004018 000400000007 R_X86_64_JUMP_SLOT 0000000000000000 puts + 0
000000004020 000500000007 R_X86_64_JUMP_SLOT 0000000000000000 exit + 0
Başlıca Relocation Tipleri (x86-64):#
R_X86_64_JUMP_SLOT: PLT tarafından çözülecek fonksiyon adresleri için GOT girdisi.R_X86_64_GLOB_DAT: Kütüphanelerden çekilen global değişkenlerin adresleri için GOT girdisi.R_X86_64_RELATIVE: ASLR/PIE nedeniyle taban adrese eklenmesi gereken göreceli adresler.R_X86_64_IRELATIVE: GNU IFUNC (Indirect Function) tarafından çalışma anında çözülecek fonksiyonlar (örneğin işlemcinin AVX2 desteğine göre farklımemcpyseçilmesi).
3. RELRO Güvenliği: Partial ve Full RELRO#
GOT tablosu doğası gereği yazılabilir (writable) olduğunda, bellek güvenliği açıklarında saldırganların en çok hedef aldığı alandır (GOT Overwrite saldırısı). Bu riski önlemek için RELRO (Relocation Read-Only) koruması geliştirilmiştir.
| RELRO Seviyesi | Derleme Seçeneği | Lazy Binding? | .got İzni | .got.plt İzni | Güvenlik Durumu |
|---|---|---|---|---|---|
| No RELRO | -Wl,-z,norelro |
Var | RW | RW | Tamamen savunmasız |
| Partial RELRO | -Wl,-z,relro |
Var | R (Salt-okunur) | RW (Yazılabilir) | .got.plt hâlâ ezilebilir |
| Full RELRO | -Wl,-z,relro,-z,now |
Yok (BIND_NOW) | R (Salt-okunur) | Yok (Tümü .got içinde) | Tam Koruma |
Full RELRO ile derlenen bir programda (gcc -Wl,-z,relro,-z,now):
- Dinamik yükleyici, program başlarken tüm sembolleri anında çözer (Tembel bağlama iptal edilir).
- Çözümleme bittikten hemen sonra
mprotect()çağrısı yapılarak tüm GOT tablosu salt-okunur (PROT_READ) yapılır. - Artık hiçbir zararlı kod çalışma anında GOT girdilerini değiştiremez.
4. PLTHook Mekanizması ve Modern Karşılıkları (Frida & eBPF)#
Tarihsel PLTHook aracı, GOT/PLT mekanizmasını en saf C koduyla manipüle eden klasik bir örnektir. Yaptığı işlem temelde şudur:
- Bellekteki
PT_DYNAMICsegmentini tarayarakDT_JMPREL(relocation girdileri) veDT_STRTABdizgilerini bulur. - Hedef fonksiyonun adıyla (
puts) eşleşenR_X86_64_JUMP_SLOTgirdisinin GOT ofsetini tespit eder. mprotect()ile o bellek sayfasını geçici olarakPROT_READ | PROT_WRITEyapar.- GOT hücresindeki orijinal fonksiyon göstericisini kendi kanca fonksiyonuyla değiştirir.
Modern İkizleri:#
- Kullanıcı Alanında: Frida Interceptor:
PLTHook mantığını modern bir JavaScript API arkasında otomatikleştirir:javascript
Interceptor.attach(Module.findExportByName(null, "puts"), { onEnter: function (args) { console.log("[+] puts: " + Memory.readUtf8String(args[0])); } }); - Çekirdek Düzeyinde: eBPF uprobes:
Sürecin belleğine hiç yazmadan, doğrudan çekirdek seviyesinde fonksiyon adresine kanca takar. Program
Full RELROile korunsa veyamprotectengellense dahi eBPF çağrıyı görünmez biçimde izler:bashsudo bpftrace -e 'uprobe:/usr/lib/libc.so.6:puts { printf("puts: %s\n", str(arg0)); }'
5. Sembol Çözümleme Sırası ve LD_PRELOAD#
Dinamik yükleyici bir sembolü ararken belirli bir arama kapsamı (lookup scope) uygular:
- Ana İkili: Programın kendisi.
LD_PRELOAD: Kullanıcı tarafından önceden yüklenen kütüphaneler.DT_RPATH/DT_RUNPATH: İkili dosyaya gömülü kütüphane arama yolları.LD_LIBRARY_PATH: Ortam değişkenindeki yollar./etc/ld.so.cache: Sistem kütüphane önbelleği.- Varsayılan Yollar:
/lib64,/usr/lib64.
Bu öncelik sırası nedeniyle LD_PRELOAD ile araya girilerek sistem çağrıları ve C fonksiyonları kolayca kancalanabilir (interposition).
İncelemeyi Derinleştir#
Sembol seçiminin LD_PRELOAD ile çalışma anında nasıl değiştiğini bizzat denemek için Sembol seçimi laboratuvarı uygulamasına geçin. Sembolleri ve hata ayıklama bilgilerini tamamen silinmiş ikililerde analiz yöntemlerini öğrenmek için Stripped ELF rehberini inceleyin.