Neler yeni

Yazılım Forum

Tüm özelliklerimize erişmek için şimdi bize katılın. Kayıt olduktan ve giriş yaptıktan sonra konu oluşturabilecek, mevcut konulara yanıt gönderebilecek, itibar kazanabilecek, özel mesajlaşmaya erişebilecek ve çok daha fazlasını yapabileceksiniz! Bu hizmetlerimiz ise tamamen ücretsiz ve kurallara uyulduğu sürece sınırsızdır, o zaman ne bekliyorsunuz? Hadi, sizde aramıza katılın!

ELF Dosyalarını Kapsamlı Bir Şekilde Çözümleme: Temelden İleri Seviyeye Bir Rehber

ELF (Executable and Linkable Format), Unix benzeri işletim sistemlerinde (Linux, FreeBSD, Solaris vb.) yürütülebilir dosyaları, paylaşımlı kütüphaneleri ve çekirdek dosyalarını temsil etmek için standart bir dosya formatıdır. Bu format, sistemin bir programı belleğe nasıl yükleyeceğini, bağlayıcıların kütüphaneleri nasıl çözdüğünü ve hata ayıklayıcıların sembol bilgilerine nasıl eriştiğini tanımlar. ELF dosyalarını anlamak, sistem programlama, tersine mühendislik, malware analizi ve sistem güvenliği alanlarında çalışan herkes için vazgeçilmez bir beceridir.

Bu rehberde, ELF dosya yapısının derinliklerine inerek, çeşitli bölümlerini, başlıklarını ve dinamik davranışlarını kapsamlı bir şekilde inceleyeceğiz. Ayrıca, bu dosyaları çözümlemek için kullanılan temel araçları ve teknikleri adım adım ele alacağız. Amacımız, ELF dosyalarının iç işleyişini anlamanıza ve bu bilgiyi pratik analiz senaryolarında uygulamanıza yardımcı olmaktır.

1. ELF Dosya Yapısının Temelleri

Bir ELF dosyası genel olarak üç ana bölümden oluşur. Bu bölümler, dosyanın bağlama (linking) ve yükleme (loading) aşamalarında nasıl işleneceğini belirleyen kritik meta verileri barındırır:
  • ELF Başlığı (ELF Header): Dosyanın en başında yer alan ve dosyanın temel özelliklerini (türü, hedef mimarisi, giriş noktası, başlık tablolarının konumu vb.) içeren birincil veri yapısıdır.
  • Program Başlık Tablosu (Program Header Table - PHT): Yürütülebilir dosyalar için dosyanın belleğe nasıl yükleneceğini ve hangi bellek segmentlerinin (örneğin, kod segmenti, veri segmenti) oluşturulacağını tanımlar. Her giriş, bir segmenti ve onun bellek özelliklerini temsil eder.
  • Bölüm Başlık Tablosu (Section Header Table - SHT): Bağlanabilir dosyalar (obj, .so) için dosyanın mantıksal bölümlerini (kod, veri, sembol tabloları, hata ayıklama bilgileri vb.) tanımlar. Her giriş, bir bölümü ve onun dosya içindeki konumunu, boyutunu ve türünü belirtir.

Bağlayıcılar (linker) genellikle Bölüm Başlık Tablosu'nu kullanarak nesne dosyalarını birleştirir ve sembolleri çözerken, işletim sistemi çekirdeği bir programı belleğe yüklerken Program Başlık Tablosu'nu esas alır. Bu ayrım, ELF'in hem bağlama hem de yürütme esnekliğini sağlar.

ELF spesifikasyonu, dosya formatının modülerliğini ve genişletilebilirliğini vurgular. Bu sayede, farklı işlemci mimarileri ve işletim sistemi varyantları, aynı temel ELF yapısını kullanarak kendi özel gereksinimlerini karşılayabilirler. Bu adaptasyon yeteneği, ELF'i Unix benzeri sistemlerde fiili bir standart haline getirmiştir.

2. ELF Başlığı (ELF Header) Detaylı İnceleme

ELF Başlığı, dosyanın en başında 64 baytlık (64-bit sistemlerde) sabit bir yapıdır ve dosya hakkında kritik yapısal bilgiler içerir. Bu başlık, bir ELF dosyasının temel kimliğini ve nasıl yorumlanması gerektiğini belirler. İşte bazı önemli alanlar ve anlamları:

  • e_ident:[/ Bu 16 baytlık dizi, ELF'in “sihirli sayı”sını (`\x7fELF`) içerir ve dosyanın ELF formatında olduğunu doğrular. Ayrıca, dosyanın byte sıralamasını (little-endian/big-endian), ELF sınıfını (32-bit/64-bit), ELF sürümünü ve işletim sistemi/ABI bilgilerini de barındırır. Bu alan, dosya türünü hızlıca tespit etmek için ilk bakılan yerdir.
    [*] e_type:[/ Dosya türünü belirtir. Olası değerler: `ET_NONE` (bilinmeyen), `ET_REL` (yeniden konumlandırılabilir dosya/obj), `ET_EXEC` (yürütülebilir dosya), `ET_DYN` (paylaşımlı nesne/kütüphane), `ET_CORE` (çekirdek dosyası).
    [*] e_machine:[/ Hedef işlemci mimarisini tanımlar (örneğin, `EM_X86_64` için x64, `EM_ARM` için ARM). Bu, doğru işlemci tarafından yürütülebilirliği garanti eder.
    [*] e_version:[/ ELF spesifikasyon sürümünü gösterir. Genellikle `EV_CURRENT` (1) kullanılır.
    [*] e_entry:[/ Yürütülebilir bir dosya için, programın yürütülmeye başlayacağı sanal bellek adresini (giriş noktası) belirtir. İşletim sistemi, programı bu adresten başlatır.
    [*] e_phoff / e_shoff:[/ Program Başlık Tablosu'nun ve Bölüm Başlık Tablosu'nun dosya içindeki ofsetlerini (bayt cinsinden başlangıç konumlarını) gösterir. Bu ofsetler, tablolara doğrudan erişim sağlar.
    [*] e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx:[/ Çeşitli başlık ve tablo boyutlarını ve sayısını belirtir. Özellikle `e_shstrndx`, bölüm adlarını içeren dizgi tablosunun indeksini işaret eder.


Bu başlığı incelemek için `readelf -h` komutunu kullanırız. Örneğin:

Kod:
$ readelf -h my_executable
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4010a0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          11680 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

Yukarıdaki çıktı, dosyanın bir 64-bit yürütülebilir dosya olduğunu, x86-64 mimarisi için derlendiğini ve giriş noktasının `0x4010a0` olduğunu gösterir. Ayrıca, Program Başlık Tablosu'nun dosya içinde 64 bayt ofsetten başladığını ve 13 girişi olduğunu da belirtir.

3. Program Başlık Tablosu (Program Header Table - PHT)

PHT, işletim sisteminin bir programı belleğe nasıl yükleyeceğini açıklayan bir dizi segment tanımlayıcısından oluşur. Her segment, dosyanın bir kısmının bellekte nasıl temsil edileceğini belirtir. İşletim sistemi, bu tablodaki bilgileri kullanarak programın sanal bellek düzenini oluşturur. Yaygın segment türleri ve anlamları şunlardır:

  • LOAD:[/ Belleğe yüklenmesi gereken kod veya veri bölümlerini içeren en yaygın segment türüdür. Genellikle bir `LOAD` segmenti okuma/yürütme izinlerine sahip kodu (`.text`), diğeri okuma/yazma izinlerine sahip veriyi (`.data`, `.bss`) barındırır. Bu segmentler, dosya içeriğinin belirli bir bellek adres aralığına nasıl eşleneceğini tanımlar.
    [*] PHDR:[/ Program Başlık Tablosu'nun kendisini tanımlayan bir segmenttir. Bu, programın kendi başlıklarına çalışma zamanında erişebilmesi için önemlidir.
    [*] INTERP:[/ Dinamik bağlayıcının yolunu (örneğin, Linux'ta `/lib64/ld-linux-x86-64.so.2`) belirten segmenttir. İşletim sistemi, programı çalıştırmadan önce bu yorumlayıcıyı yükler.
    [*] DYNAMIC:[/ Dinamik bağlama için gerekli bilgileri (paylaşımlı kütüphane bağımlılıkları, yeniden konumlandırma girdileri vb.) içeren segmenttir. Dinamik bağlayıcı bu bilgilere erişir.
    [*] NOTE:[/ Çeşitli sistem notlarını veya özel bilgileri (örneğin, GNU Build ID) içeren segmenttir. Genellikle işletim sistemine veya hata ayıklayıcılara yönelik ekstra veriler barındırır.
    [*] TLS (Thread-Local Storage): İş parçacığı yerel depolama alanlarının tanımlarını içerir.


PHT'yi `readelf -l` komutuyla detaylı bir şekilde inceleyebiliriz:

Kod:
$ readelf -l my_executable

Elf file type is EXEC (Executable file)
Entry point 0x4010a0
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz             Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R      0x8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000df4 0x0000000000000df4 R E    0x1000
  LOAD           0x0000000000000df8 0x0000000000400df8 0x0000000000400df8 0x000000000000021c 0x0000000000401014 RW     0x1000
  DYNAMIC        0x0000000000000e00 0x0000000000400e00 0x0000000000400e00 0x00000000000001d0 0x00000000000001d0 RW     0x8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R      0x4
  ... (diğer segmentler)

Yukarıdaki çıktıda `LOAD` segmentlerinin `R E` (Read, Execute) ve `RW` (Read, Write) izinlerine sahip olduğunu ve bunların dosya içindeki başlangıç ofsetlerini, sanal adreslerini (`VirtAddr`) ve bellek boyutlarını (`MemSiz`) gösterdiğini görebiliriz. Bu, sistemin kodu ve veriyi sanal bellek alanına nasıl yerleştirdiğini ortaya koyar.

4. Bölüm Başlık Tablosu (Section Header Table - SHT)

SHT, bir ELF dosyasının mantıksal yapısını oluşturan bölümler hakkında bilgi sağlar. Bu bölümler, dosyanın çeşitli türdeki içeriğini düzenler ve bağlama aşamasında linker tarafından kullanılır. SHT, özellikle hata ayıklayıcılar ve analiz araçları için büyük önem taşır. Yaygın bölümler ve işlevleri şunlardır:

  • .text:[/ Programın yürütülebilir makine kodunu barındırır. Bu bölüm genellikle okuma ve yürütme izinlerine sahiptir.
    [*] .rodata:[/ Salt okunur verileri (örneğin, sabit dizgiler, sabit sayılar) içerir. Bu bölüme yazma izni verilmez, bu da güvenlik ve veri bütünlüğü açısından önemlidir.
    [*] .data:[/ Başlatılmış global ve statik değişkenleri barındırır. Bu verilere programın çalışma zamanında okunur ve yazılır.
    [*] .bss:[/ Başlatılmamış global ve statik değişkenleri barındırır. Bu bölüm dosya içinde yer kaplamaz, ancak program yüklendiğinde işletim sistemi tarafından bellekte sıfırlarla başlatılır.
    [*] .symtab:[/ Sembol tablosu. Programdaki fonksiyonlar, değişkenler ve diğer referanslar hakkında bilgi (adı, değeri, boyutu, türü vb.) içerir. Hem global hem de yerel sembolleri barındırabilir.
    [*] .strtab:[/ Dizgi tablosu. Sembol adlarını, bölüm adlarını ve diğer dizgileri tutan bir veri havuzudur. Sembol ve bölüm tabloları, bu tablodaki ofsetlere başvurarak isimlere erişir.
    [*] .interp:[/ Dinamik bağlayıcının yolunu içeren bölüm, PHT'deki INTERP segmentine karşılık gelir.
    [*] .debug_*, .debug_info, .debug_line, vb.: Hata ayıklama bilgileri bulunur (isteğe bağlı). Bu bölümler, kaynak koduyla yürütülebilir kod arasındaki eşleşmeyi sağlar ve hata ayıklayıcıların daha yüksek seviyeli dil yapılarını anlamasına yardımcı olur.
    [*] .init / .fini:[/ Program başlatılırken veya sonlandırılırken yürütülen kod parçacıklarını içerir.


Bölümleri `readelf -S` komutuyla listeleyebiliriz:

Kod:
$ readelf -S my_executable
There are 30 section headers, starting at offset 0x2dc0:

Section Headers:
  [Nr] Name              Type             Address           Offset     Size     EntSize  Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000 00000000 00000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238 0000001c 00000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254 00000020 00000000   A       0     0     4
  [ 3] .note.gnu.build-id NOTE             0000000000400274  00000274 00000024 00000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298 0000001c 00000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8 000000a8 00000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400360  00000360 0000007e 00000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004003de  000003de 0000000e 00000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004003ec  000003ec 00000020 00000000   A       6     1     8
  [ 9] .rela.dyn         RELA             000000000040040c  0000040c 00000048 00000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400454  00000454 00000030 00000018  AI       5     0     8
  [11] .init             PROGBITS         0000000000400484  00000484 00000017 00000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004004a0  000004a0 00000030 00000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004004d0  000004d0 00000008 00000008  AX       0     0     8
  [14] .text             PROGBITS         00000000004004e0  000004e0 00000130 00000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000400610  00000610 00000009 00000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000400620  00000620 00000017 00000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000400638  00000638 0000003c 00000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400678  00000678 000000f0 00000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000400df8  00000df8 00000008 00000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000400e00  00000e00 00000008 00000008  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000400e08  00000e08 000001d0 00000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000400fd8  00000fd8 00000008 00000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000400fe0  00000fe0 00000030 00000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000401010  00001010 00000010 00000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000401020  00001020 00000008 00000000  WA       0     0     8
  [26] .comment          PROGBITS         0000000000000000  00001020 0000001a 00000001           0     0     1
  [27] .symtab           SYMTAB           0000000000000000  00001040 00000720 00000018          28    47     8
  [28] .strtab           STRTAB           0000000000000000  00001760 0000021c 00000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  0000197c 0000011f 00000000           0     0     1

Bu çıktı, dosyadaki tüm bölümleri, bunların türlerini, sanal adreslerini, dosya ofsetlerini ve boyutlarını listeler. `.text` bölümünün yürütülebilir kodu içerdiğini (`AX` bayrağı), `.data` ve `.bss` bölümlerinin yazılabilir veriyi (`WA` bayrağı) barındırdığını gözlemleyebiliriz. `.bss` bölümünün `NOBITS` türünde olması, dosya içinde yer kaplamadığını ancak bellek ayrılacağını gösterir.

5. Sembol ve Dizgi Tabloları

ELF dosyaları, programın içerdiği fonksiyonlar, değişkenler ve diğer referanslar hakkında bilgi sağlayan sembol tablolarını içerir. Bu tablolar, özellikle bağlama ve hata ayıklama süreçlerinde hayati öneme sahiptir. İki ana sembol tablosu türü vardır:

  • .symtab (Symbol Table): Derleme birimindeki (örneğin, bir C dosyası) tüm global, yerel ve tanımsız sembolleri içerir. Bu semboller arasında fonksiyonlar, global değişkenler, statik değişkenler ve bazen hata ayıklama sembolleri de bulunur. Yürütülebilir dosyalarda, genellikle `strip` komutuyla kaldırılırlar, ancak geliştirme ve hata ayıklama sırasında çok değerlidirler.
  • .dynsym (Dynamic Symbol Table): Yalnızca dinamik olarak bağlı sembolleri içerir. Örneğin, bir programın paylaşımlı kütüphanelerden içe aktardığı veya dışa aktardığı fonksiyonlar ve değişkenler burada listelenir. Bu tablo, dinamik bağlayıcının çalışma zamanında sembolleri çözmesi için gereklidir.
  • .strtab (String Table): Sembol adlarını, bölüm adlarını ve diğer dizgileri tutan bir veri havuzudur. Sembol ve bölüm tabloları, bu dizgi tablolarındaki ofsetlere başvurarak sembol ve bölüm adlarına erişir. Bu sayede, sembol adları tekrar etse bile dosyada yalnızca bir kez saklanır ve yer kazancı sağlanır.

Sembolleri `readelf -s` komutuyla görüntüleyebiliriz:

Kod:
$ readelf -s my_executable

Symbol table '.symtab' contains 75 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crt1.o
     2: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     3: 00000000004004e0     0 FUNC    LOCAL  DEFAULT   14 _start
     ...
    30: 00000000004004e0    20 FUNC    GLOBAL DEFAULT   14 main
    31: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
    32: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
    ...

Bu çıktı, `main` fonksiyonunun bir global sembol olduğunu (`GLOBAL` bağlama tipi) ve `.text` bölümünde (`Ndx 14`) yer aldığını gösterir. `puts` ve `__libc_start_main` gibi fonksiyonların ise `GLIBC_2.2.5` kütüphanesinden dinamik olarak bağlandığını (`UND` - undefined, `WEAK` - zayıf bağlama) görüyoruz. Sembol çözünürlüğü, programın harici kaynaklara nasıl eriştiğini anlamak için hayati bir adımdır.

6. Dinamik Bağlantı ve Yeniden Konumlandırma

Modern işletim sistemlerinde programlar genellikle paylaşımlı kütüphaneler (`.so` dosyaları) kullanır. Bu yaklaşım, kod tekrarını azaltır, bellek kullanımını optimize eder ve programların güncel kütüphanelerden faydalanmasını sağlar. Dinamik bağlayıcı (`ld-linux.so` gibi), bir program başladığında gerekli kütüphaneleri bulur ve programın sanal adres alanına eşler. Bu süreç, dinamik bağlama olarak adlandırılır.

  • PLT (Procedure Linkage Table): Prosedür Bağlantı Tablosu, yürütülebilir dosya içindeki harici fonksiyon çağrılarını dinamik bağlayıcıya yönlendiren bir mekanizmadır. Bir harici fonksiyon ilk kez çağrıldığında, PLT aracılığıyla dinamik bağlayıcıya yönlendirilir ve gerçek fonksiyon adresi bulunur. Sonraki çağrılar için bu adres GOT'a kaydedilir.
  • GOT (Global Offset Table): Global Ofset Tablosu, harici sembollerin (fonksiyonlar ve değişkenler) gerçek bellek adreslerini tutan bir tablodur. Dinamik bağlayıcı, bir sembolün adresini çözümlediğinde, bu adresi GOT'a yazar. Böylece, programın sonraki referansları doğrudan doğru adrese gider.
  • Yeniden Konumlandırma (Relocation): Programın veya paylaşımlı kütüphanelerin yüklendiği gerçek adresler, derleme zamanında bilinemez. Yeniden konumlandırma, bu sembol referanslarının ve bellek adreslerinin, program çalışma zamanında veya yükleme zamanında gerçek adreslerle güncellenmesi işlemidir. `.rela.dyn` ve `.rela.plt` gibi bölümler, yeniden konumlandırma girdilerini içerir ve bağlayıcıya hangi adreslerin nasıl güncelleneceğini söyler.

Bir ELF dosyasının bağlı olduğu paylaşımlı kütüphaneleri görmek için `ldd` komutunu kullanırız:

Kod:
$ ldd my_executable
	linux-vdso.so.1 (0x00007ffe4776e000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3c4c9b3000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f3c4cbaf000)

Bu çıktı, `my_executable` adlı programın `libc.so.6` (C standart kütüphanesi) ve `ld-linux-x86-64.so.2` (dinamik bağlayıcı) gibi kütüphanelere bağımlı olduğunu ve bunların bellekteki başlangıç adreslerini gösterir.

Yeniden konumlandırma girdilerini `readelf -r` komutuyla inceleyebiliriz:

Kod:
$ readelf -r my_executable

Relocation section '.rela.dyn' at offset 0x40c contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000401018  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000401028  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000401020  000300000006 R_X86_64_GLOB_DAT 0000000000000000 puts@GLIBC_2.2.5 + 0

Relocation section '.rela.plt' at offset 0x454 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000400490  000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000400498  000300000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

Bu çıktılar, `__libc_start_main` ve `puts` gibi fonksiyonların çalışma zamanında nasıl çözüldüğünü ve hangi bellek ofsetlerinin dinamik bağlayıcı tarafından güncellenmesi gerektiğini detaylı bir şekilde gösterir. `R_X86_64_JUMP_SLOT` türündeki yeniden konumlandırmalar genellikle PLT/GOT mekanizmasıyla ilişkilidir.

7. ELF Dosyalarını Çözümleme Araçları

ELF dosyalarını derinlemesine incelemek ve anlamak için birçok güçlü ve yaygın araç mevcuttur. Bu araçlar, farklı analiz ihtiyaçlarına göre özelleşmiştir:

  • readelf:[/ GNU Binutils paketinin temel bir parçasıdır ve ELF dosyalarını incelemek için en kapsamlı ve temel araçlardan biridir. ELF başlığını, bölüm ve program başlık tablolarını, sembol tablolarını, dinamik etiketleri, yeniden konumlandırma girdilerini ve çok daha fazlasını detaylı olarak görüntülemek için kullanılır. Yukarıdaki örneklerde bolca kullandığımız bu araç, düşük seviyeli ELF yapısını anlamak için vazgeçilmezdir.
    [*] objdump:[/ Yine Binutils'in bir parçası olan `objdump`, nesne dosyalarını incelemek, makine kodunu sökmek (disassembly) ve belirli bölüm içeriklerini (örneğin `.text`, `.data`) görüntülemek için kullanılır. Özellikle tersine mühendislik bağlamında kod akışını ve fonksiyonel mantığı anlamak için kritik bir araçtır.
    Kod:
    $ objdump -d my_executable
    
    my_executable:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    00000000004004e0 <_start>:
      4004e0:       31 ed                   xor    %ebp,%ebp
      4004e2:       49 89 d1                mov    %rdx,%r9
      4004e5:       5e                      pop    %rsi
      4004e6:       48 89 e2                mov    %rsp,%rdx
      4004e9:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
      4004ed:       50                      push   %rax
      4004ee:       54                      push   %rsp
      4004ef:       4c 8d 05 c2 00 00 00    lea    0xc2(%rip),%r8        # 4005b8 <__libc_csu_fini>
      4004f6:       48 8d 0d c3 00 00 00    lea    0xc3(%rip),%rcx        # 4005c0 <__libc_csu_init>
      4004fd:       48 8d 3d b6 00 00 00    lea    0xb6(%rip),%rdi        # 4005bc <main>
      400504:       ff 15 2e 00 00 00       call   *0x2e(%rip)        # 400538 <__libc_start_main@GLIBC_2.2.5>
      40050a:       f4                      hlt    
    
    000000000040050b <main>:
      40050b:       55                      push   %rbp
      40050c:       48 89 e5                mov    %rsp,%rbp
      40050f:       48 8d 3d d4 00 00 00    lea    0xd4(%rip),%rdi        # 4005e8 <_IO_stdin_used+0x8>
      400516:       e8 c5 ff ff ff          call   4004e0 <_start+0x0>
      40051b:       b8 00 00 00 00          mov    $0x0,%eax
      400520:       5d                      pop    %rbp
      400521:       c3                      ret
    [*] nm:[/ Nesne dosyalarındaki sembolleri listeler. Özellikle hangi sembollerin dışarıya aktarıldığını (`T` - Text, `D` - Data) veya içeriye aktarıldığını (`U` - Undefined) görmek için kullanışlıdır. Bir programın dış dünyayla olan arayüzünü hızlıca anlamak için idealdir.
    Kod:
    $ nm my_executable
    00000000004005b8 T __libc_csu_fini
    00000000004005c0 T __libc_csu_init
                     U __libc_start_main@@GLIBC_2.2.5
    000000000040050b T main
                     U puts@@GLIBC_2.2.5
    00000000004004e0 T _start
    [*] ldd:[/ Bir yürütülebilir dosyanın veya paylaşımlı kütüphanenin hangi paylaşımlı kütüphanelere bağımlı olduğunu gösterir. Dinamik bağlama sürecini anlamak için ilk adımlardan biridir.
    Kod:
    $ ldd my_executable
    	linux-vdso.so.1 (0x00007ffe4776e000)
    	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3c4c9b3000)
    	/lib64/ld-linux-x86-64.so.2 (0x00007f3c4cbaf000)
    [*] strace:[/ Bir programın yaptığı sistem çağrılarını izler. ELF analiziyle doğrudan ilgili olmasa da, bir programın çalışma zamanı davranışını, dosya erişimlerini, ağ bağlantılarını ve diğer çekirdek etkileşimlerini anlamak için hayati bir araçtır. Özellikle şüpheli ikili dosyaları analiz ederken veya hataları ayıklarken çok değerlidir.
    Kod:
    $ strace ./my_executable
    execve("./my_executable", ["./my_executable"], 0x7ffd7a04dd28 /* 64 vars */) = 0
    brk(NULL)                               = 0x5558d11c1000
    arch_prctl(ARCH_SET_FS, 0x5558d11c1880) = 0
    set_tid_address(0x5558d11c1b50)         = 13744
    set_robust_list(0x5558d11c1b60, 24)     = 0
    rseq(0x5558d11c2120, 0x20000, 0, 0x5558d11c2120) = 0
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9a2f64a000
    ...
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
    write(1, "Hello, ELF!\n", 14Hello, ELF!
    )         = 14
    exit_group(0)                           = ?
    +++ exited with 0 +++
    [*] GDB (GNU Debugger): Çalışan bir programı adım adım izlemek, bellek içeriğini incelemek, kayıtların durumunu kontrol etmek ve kırılma noktaları (breakpoints) ayarlamak için kullanılır. Özellikle dinamik analizde, hata ayıklamada ve programın çalışma zamanı davranışını anlamada kritik bir araçtır.
    [*] Hex Editörler (xxd, hexdump): ELF dosyasının ham baytlarını görüntülemek için kullanılır. Bu araçlar, dosyanın düşük seviyede nasıl organize edildiğini ve belirli veri yapılarının (örneğin, başlıklar veya tablolar) dosya içindeki gerçek konumlarını anlamak için faydalıdır.
    [*] İleri Seviye Tersine Mühendislik Araçları (IDA Pro, Ghidra, Radare2): Özellikle karmaşık ELF dosyalarını, tescilli yazılımları veya zararlı yazılımları analiz etmek için tasarlanmış kapsamlı araçlardır. Grafikli arayüzler, güçlü kod dekompilasyonu (makine kodunu daha okunabilir C benzeri bir dile çevirme) ve gelişmiş analiz yetenekleri sunarlar. Bu araçlar, büyük ikili dosyaların içindeki mantığı anlamayı kolaylaştırır. Daha fazla bilgi için Ghidra projesini veya IDA Pro'yu inceleyebilirsiniz.


8. Pratik Uygulama ve Örnek Senaryo

Basit bir C programı yazarak ELF çözümlemesini somutlaştıralım ve çeşitli araçlarla inceleyelim:

Kod:
// hello.c
#include <stdio.h>

int main() {
    printf("Hello, ELF!\n");
    return 0;
}

Bu kodu derleyelim ve yürütülebilir bir dosya oluşturalım:
Kod:
$ gcc hello.c -o hello

Şimdi `hello` yürütülebilir dosyasını yukarıda bahsettiğimiz araçlarla adım adım inceleyelim:

  • Dosya Türünü Belirleme: `file` komutu, dosyanın temel özelliklerini hızlıca gösterir.
    Kod:
    $ file hello
    hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=..., not stripped
    Yukarıdaki çıktı, `hello` dosyasının 64-bit bir ELF yürütülebilir dosyası olduğunu, x86-64 mimarisi için derlendiğini ve dinamik olarak `/lib64/ld-linux-x86-64.so.2` yorumlayıcısı ile bağlandığını açıkça belirtiyor. Ayrıca, dosyanın `strip` edilmediğini (yani sembol bilgilerinin hala içeride olduğunu) de gösteriyor, bu da hata ayıklama için önemlidir.
  • ELF Başlığını İnceleme: `readelf -h` ile giriş noktasını bulabiliriz.
    Kod:
    $ readelf -h hello | grep 'Entry point address'
      Entry point address:               0x4010a0
    Programın giriş noktası `0x4010a0` adresindedir, bu adres programın yürütülmeye başlayacağı yerdir.
  • Dinamik Bağlantıları Görüntüleme: `ldd` ile kütüphane bağımlılıklarını kontrol edelim.
    Kod:
    $ ldd hello
    	linux-vdso.so.1 (0x00007ffcc856c000)
    	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbf86de8000)
    	/lib64/ld-linux-x86-64.so.2 (0x00007fbf86ff5000)
    `libc.so.6` kütüphanesine bağımlı olduğunu görüyoruz. `printf` fonksiyonu gibi standart C kütüphanesi fonksiyonları bu kütüphanede yer almaktadır.
  • Sembol Tablosunu Kontrol Etme: `nm` veya `readelf -s` kullanarak program içindeki sembolleri görebiliriz.
    Kod:
    $ nm hello | grep ' main'
    0000000000401149 T main
    $ nm hello | grep ' printf'
                     U printf@@GLIBC_2.2.5
    Bu çıktılar, `main` fonksiyonunun yürütülebilir dosya içinde tanımlandığını (`T` - text segment) ve `0x401149` adresinde başladığını, `printf` fonksiyonunun ise `GLIBC_2.2.5` sürümünden dinamik olarak çağrılan tanımsız bir sembol (`U` - undefined) olduğunu gösterir. Bu, `printf`'in adresinin çalışma zamanında dinamik bağlayıcı tarafından çözüleceği anlamına gelir.

9. Sonuç

ELF dosyalarını çözümlemek, modern işletim sistemlerinin nasıl çalıştığını ve uygulamaların düşük seviyede nasıl etkileşim kurduğunu anlamanın temelidir. Bu rehberde, ELF başlığından program ve bölüm başlık tablolarına, sembol ve dizgi tablolarından dinamik bağlama mekanizmalarına kadar ELF yapısının birçok önemli yönünü ele aldık. Ayrıca, `readelf`, `objdump`, `nm` ve `ldd` gibi güçlü Binutils araçlarını kullanarak pratik örnekler sunduk.

ELF analizi, güvenlik araştırmacılarının zararlı yazılımları incelemesi, sistem programcılarının performansı optimize etmesi ve yazılım geliştiricilerinin derlenmiş kodun davranışını anlaması için kritik bir beceridir. Bu bilgi, bir programın bellekte nasıl düzenlendiğini, harici kütüphanelerle nasıl etkileşim kurduğunu ve nihayetinde CPU üzerinde nasıl yürütüldüğünü daha derinlemesine kavramanıza yardımcı olacaktır. Daha fazla bilgi ve derinlemesine inceleme için, ELF spesifikasyonuna başvurmanız şiddetle tavsiye edilir.

Bu kapsamlı rehberin, ELF dünyasına adım atmanız için sağlam bir temel oluşturduğunu umuyoruz. Unutmayın ki pratik yapmak ve farklı türdeki ELF dosyalarını (örneğin, paylaşımlı kütüphaneler, statik derlenmiş dosyalar) incelemek, bu alandaki uzmanlığınızı pekiştirmenin ve daha karmaşık analiz görevlerine hazırlanmanın en iyi yoludur.
 
shape1
shape2
shape3
shape4
shape5
shape6
Üst

Bu web sitenin performansı Hazal Host tarafından sağlanmaktadır.

YazilimForum.com.tr internet sitesi, 5651 sayılı Kanun’un 2. maddesinin 1. fıkrasının (m) bendi ve aynı Kanun’un 5. maddesi kapsamında Yer Sağlayıcı konumundadır. Sitede yer alan içerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır.

YazilimForum.com.tr, kullanıcılar tarafından paylaşılan içeriklerin doğruluğunu, güncelliğini veya hukuka uygunluğunu garanti etmez ve içeriklerin kontrolü veya araştırılması ile yükümlü değildir. Kullanıcılar, paylaştıkları içeriklerden tamamen kendileri sorumludur.

Hukuka aykırı içerikleri fark ettiğinizde lütfen bize bildirin: lydexcoding@gmail.com

Sitemiz, kullanıcıların paylaştığı içerik ve bilgileri 6698 sayılı KVKK kapsamında işlemektedir. Kullanıcılar, kişisel verileriyle ilgili haklarını KVKK Politikası sayfasından inceleyebilir.

Sitede yer alan reklamlar veya üçüncü taraf bağlantılar için YazilimForum.com.tr herhangi bir sorumluluk kabul etmez.

Sitemizi kullanarak Forum Kuralları’nı kabul etmiş sayılırsınız.

DMCA.com Protection Status Copyrighted.com Registered & Protected