Portable Executable(PE) File Format (I)

 

Portable Executable File Format Nedir?

Portable Executable (kısaca PE), Windows’ un çalıştırılabilir dosya formatıdır.

Portable executable “.acm, .ax, .cpl, .drv, .efi, .mui, .ocx, .scr, .sys, .tsp” ve hepimizin gördüğü .dll ve .exe uzantılarını kapsar.

Burada tarihi ve neden geliştirildiği hakkında bahsetmeyeceğim tabii ki, gereksiz yere yazıyı uzatmış olurum. Google’ da ufak bir araştırma ile tarihi ve neden geliştirildiği hakkında kaynaklara ulaşabilirsiniz.

Baştan uyarmam gerek, bu yazı bayağı uzayabilir. Karmaşık geldiği yerlerde dinlenip kahve içebilirsiniz(Benim tercihim). Araştırma yapmaktan çekinmeyin, ne kadar çok kaynaktan öğrenmeye kalkarsanız hem daha fazlasını öğrenirsiniz hemde yanlış öğrenme ihtimalini azaltırsınız.

O halde başlıyoruz.

P.E. Dosya Formatı Neden Önemlidir?

Öncelikle Zararlı Yazılım Analizi ve Reverse Engineering ile ilgilenen kişilerin kesinlikle bu dosya yapısını çok iyi bir şekilde bilmesi gerekiyor. Neden önemli olduğu konusuna gelecek olursak, PE dosya formatındaki başlıklar o dosya ile ilgili kritik bilgiler içermektedir. Örneğin PE dosyasının yükleneceği makine türü, eğer çalıştırılabilen bir dosya ise programın entry point adresi, PE dosyasının oluşturulduğu tarih gibi önemli olabilecek bilgiler içermektedir.

Relative Virtual Address(RVA)?

Kolları sıvamadan önce RVA’ nın ne olduğu ve ne işe yaradığını anlayalım ilk önce.

İşletim sistemi PE formatını belleğe yüklerken belleğin direk başından itibaren yüklemek zorunda değildir. PE formatının yükleme adresi kendi içinde bulunmaktadır.

Relative Virtual Address(RVA), dosya belleğe yüklendikten sonra dosya içerisindeki belli bir bölümün yükleme adresinden ne kadar uzakta olacağını belirtir.

Bu bize ne gibi bir yarar sağlar diye düşünüyor olabilirsiniz. Örneğin dosyanın içerisindeki belli bir bölgenin Relative Virtual Address’ ini biliyorsak, PE dosyası belleğe yüklendikten sonra o belirli bölge yükleme adresinden RVA kadar uzakta olacaktır. Böylece işletim sistemi PE dosya formatını belleğe yüklediğinde belirli bölgenin nerede olduğunu bulabiliriz.

Kolları Sıvama Zamanı Geldi!

İlk önce PE file format nasıl gözüküyor ufak bir bakış atalım.

Gördüğünüz gibi PE file format başlıklardan(headers) ve bölümlerden(sections) lardan oluşuyor.

İlk gördüğümüz “DOS Header” dan şimdi bahsetmek istiyorum. Çok basit bir işlevi olduğundan dolayı ileride bahsetmek zorunda kalmamak için.

DOS Header(DOS Başlığı), aslında uyumluluk sağlamak için bulunuyor. Bu bölüm ile birlikte ufak bir DOS programı var. Programın yaptığı şey ise, eğer bu PE dosyasını DOS sisteminde çalıştırmaya kalkarsanız ekrana bu PE dosyasını DOS ortamında çalıştırılamayacağınızı söyleyen bir hata mesajı verip programı sonlandırmak. Dediğim gibi bu header sadece uyumluluk sağlamak için PE dosya formatına ekleniyor, yani bu kısımla işimiz yok.

Şimdi sıra asıl başlıkları incelemeye geldi…

IMAGE FILE HEADER

Bu başlık PE dosyasına ait önemli birkaç bilgiyi tutuyor. Bu header dosyası IMAGE_FILE_HEADER yapısıyla temsil edilmiştir. Yapıya biraz göz atalım.

typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Buradaki “WORD” ve “DWORD” kafanızı karıştırmasın. WORD 16 bit (2 byte) işaretsiz tamsayı, DWORD 32 bit(4 byte) işaretsiz tamsayı yerini tutuyor. Şimdi bu yapının elemanlarını açıklayalım.

WORD Machine: Bu alan PE dosyasının yükleneceği makinanın türünü belirtir. Aşağıda sıklıkla kullanılan birkaç değeri görebilirsiniz.

  • 0x0000 = Herhangi bir makine türü için
  • 0x1c40 = ARMv7 (or daha yukarı)
  • 0xebc0 = EFI byte code
  • 0x8664 = x64 (64 bit PE formatı için)
  • 0x14c0 = Intel 386 ya da bununla uyumlu daha ileri model (32 bit PE formatı için)

WORD NumberOfSections: Bu alanda section table (bölüm tablosunun) eleman sayısı bulunur.

DWORD TimeDateStamp:  Bu alanda PE dosyasının yaratıldığı tarih bilgisi bulunmakta.

DWORD PointerToSymbolTable: Çalıştırılabilir dosyalar için bu kısım önemsizdir ve 0(sıfır) bulunur. Bu kısım COFF formatı için önemlidir. COFF dosyalarının sembol tablosunun dosya offseti bulunur.

DWORD NumberOfSymbols: Çalıştırılabilir dosyalar için bu kısım önemsizdir ve 0(sıfır) bulunur. COFF dosyaları için bu alanda sembol tablosundaki eleman sayısı bulunur.

WORD  SizeOfOptionalHeader:  Bu kısımda IMAGE_FILE_HEADER dan sonra gelen IMAGE_OPTIONAL_HEADER başlığının byte cinsinden değeri bulunur.

WORD Characteristics: Bu alanda PE dosyasına ilişkin bazı özellikler bitsel olarak kodlanmıştır. Bazı önemli bitler ve karşılıkları aşağıda belirtilmiştir.

  • 0x0001  = Bu bitin anlamı, relocation bilgisi eklenmemiştir. Yani dosya sadece belirtilen adrese yüklenebilir aksi taktirde dosya yüklenemez.
  • 0x0002 = Bu bitin anlamı, dosya çalıştırılabilir bir dosyadır.
  • 0x0100 = Dosyanın çalıştırılacağı makina 32 bit işlemcili bir makinadır.
  • 0x2000 = Dosya bir dll dosyasıdır.

Gördüğünüz gibi bir dosyanın exe ve ya dll olmasını sadece bu kısım belirliyor.

IMAGE OPTIONAL HEADER

İsmine kesinlikle aldanmayın, sanıldığının aksine bu başlık isteğe bağlı kesinlikle değildir. Tam tersine ilk stack boyutu, programın entry pointini, işletim sisteminin versiyonu gibi çok önemli bilgileri barındırır. Bu kısmın 32bit ve 64bit versiyonları var fakat sadece boyut olarak farklılıklar barındırıyor. Yani endişelenmeye gerek yok.

Bu header dosyası içerisinde IMAGE_OPTIONAL_HEADER yapısıyla temsil edilmiştir. Yapıya biraz göz atalım.

typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
USHORT Magic;
UCHAR MajorLinkerVersion;
UCHAR MinorLinkerVersion;
ULONG SizeOfCode;
ULONG SizeOfInitializedData;
ULONG SizeOfUninitializedData;
ULONG AddressOfEntryPoint;
ULONG BaseOfCode;
ULONG BaseOfData;
//
// NT additional fields.
//
ULONG ImageBase;
ULONG SectionAlignment;
ULONG FileAlignment;
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
USHORT MajorImageVersion;
USHORT MinorImageVersion;
USHORT MajorSubsystemVersion;
USHORT MinorSubsystemVersion;
ULONG Reserved1;
ULONG SizeOfImage;
ULONG SizeOfHeaders;
ULONG CheckSum;
USHORT Subsystem;
USHORT DllCharacteristics;
ULONG SizeOfStackReserve;
ULONG SizeOfStackCommit;
ULONG SizeOfHeapReserve;
ULONG SizeOfHeapCommit;
ULONG LoaderFlags;
ULONG NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

Gördüğünüz gibi yapı “standart fields” ve “nt additionals field” ile iki kısma bölünmüştür.

Standart Fields genellikle Unix executable dosyaları, Common Object File FormatCOFF için kullanılır. Fakat sadece Unix executable dosyaları kullanmaz bu kısmı, Windows’ ta farklı amaçlarla kullanır.

Magic: Burada 0x010b ve 0x20b değerleri bulunabilir. 32bit ise 0x010b, 64bit ise 0x020b değeri bulunur.

MajorLinkerVersion & MinorLinkerVersion: Bu kısımda linker ın majör ve minör versiyon bilgileri bulunur.

SizeOfCode: Executable kodun boyutunu tutar.

SizeOfInitializedData: İlk değer atanmış verilerin toplam boyutunu tutar.(Genelde .data kısmında bulunurlar.)

SizeOfUninitializedData: İlk değer atanmamış verilerin toplam boyutunu tutar.(.bss kısmında bulunurlar.)

AddressOfEntryPoint: Bu kısım executable dosyaların başlangıç noktasının RVA sını, dll dosyalarının ise DllMain fonksiyonunun RVA sını tutar. Genellikle .text bölümünde bir yer belirtir.

BaseOfCode: Bu kısımda executable kodların olduğu bölümün(genellikle .text bölümü) RVA’sını tutar.

BaseOfData: İlk değeri atanmış verilerin bulunduğu (.data bölümü) bölümün RVA’sını tutar.

ImageBase: Linker, PE formatının yükleneceği adresi bu kısma yazar. Genel olarak 32 bit exe için bu kısımda 0x00400000 (4MB) bulunur. Tabiki linker ayarlarından bu kısım değiştirilebilir. Dediğimiz gibi bu kısımda genellikle  0x00400000 bulunuyor. Peki PE dosyalarında çoğunlukla aynı değer bulunuyor ise birden fazla dosyayı çalıştırmaya kalktığımızda nasıl karışmıyor? Bu sorunun cevabı: yer değiştirme(relocation) ile. Dosya içinde bulunan relocation bilgisi ile dosya farklı bir adresten itibaren yüklenir.

SectionAlignment: Buradaki değer bölümlerin yüklenirken nasıl bir hizalamaya uyacağını gösterir. Tipik olarak 0x00001000 (4KB) değeri bulunur. (Windowsta sayfa(page) boyutu 4KB’tır.)

FileAlignment: Bölümlerin diskte hangi değerin katlarına uygun şekilde sıralacağını tutar. Varsayılan olarak 512 bulunur.

MajorOperatingSystemVersion, MinorOperatingSystemVersion:  Bu kısımda PE dosyasının yükleneceği minimum işletim sisteminin versiyon bilgisi bulunur.

MajorImageVersion & MinorImageVersion: Bu kısımda yüklenecek PE dosyasının minör ve major versiyon numaraları bulunur.

MajorSubsystemVersion, MinorSubsystemVersion: Burada işletim sisteminde kullanılan minör ve major değerleri bulunur.

SizeOfImage: Dosyanın bölümlerinin toplam boyutunu tutar.

SizeOfHeaders: PE headerın ve section tablosunun boyutunu bulundurur.

CheckSum: CRC checksum için ayrılmıştır bu kısım fakat genellikle 0 bulunur.

Subsystem: Kullanıcının kullanacağı interface i belirtir.

  • 1(Native): Subsystem gerekli olmayan uygulamalar için(driver gibi)
  • 2(Windows_GUI)
  • 3(Windows_CUI)
  • 5(OS2_CUI)
  • 7(POSIX_CUI)

DllCharacteristics: Dll için belirli flag leri aktif eder. (MSDN’ e göre bu kısımda hep 0 bulunuyor.)

SizeOfStackReserve: Her program için stack bulunur(daha doğrusu her thread için), işte bu kısım stack için ayrılacak alanın büyüklüğünü belirliyor. Default olarak 0x100000 (1MB) dır.

SizeOfStackCommit: İlk thread için commit edilecek stack boyutu bulunur. Default olarak 0x1000 bytes (1 page).

SizeOfHeapReserve: Program için ayrılacak heap alanı boyutunu belirtir.

SizeOfHeapCommit: Başlangıçta commit edilecek heap miktarını belirtir. Default olarak 0x1000 bytes (1 page).

NumberOfRvaAndSizes: Dosya içerisindeki veri dizininde kaç adet girdi olduğunu belirtir.

DATA DIRECTORY(Veri Dizini)

Veri dizini, PE formatı için önemli olan tabloların yerlerini ve uzunluklarını tutar. Toplamda 8 byte büyüklüğünde; 4 byte RVA, 4 byte uzunluk bilgisi.

typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

Bu yapıdaki VirtualAddress tablonun RVA sını, Size ise büyüklüğünü tutuyor. Yani tablonun başlangıç adresi ve büyüklüğünü tutuyor bu yapı, tablonun kendisini değil.

Yazı çok uzun olduğundan 2 kısıma böleceğim. Diğer kısmı da tamamladığım zaman yayınlayacağım. Eğer yazıda herhangi bir yanlış varsa veya sormak istediğiniz bir kısım var ise lütfen yorumda belirtin, en kısa sürede düzelteceğim.

Aklıma bir şey geldikçe eklemeler veya düzeltmeler yapacağım. Yani son baktığınızla en son sürümü arasında ufak farklar olabilir.

Bu yazıda şimdilik benden bu kadar. Lütfen farklı kaynaklardan araştırma yapın, neyin ne olduğunu öğrendikten sonra kendiniz inceleyin. Bu şekilde pratik yaparak hafızanızda daha iyi şekilde kalmasını sağlarsınız. Zaten PE formatı hakkında 3. yazımda rastgele bir PE dosyasının incelemesini yazmak istiyorum ama ileride ne olur bilemiyorum.

EDIT: 2.yazıma buradan ulaşabilirsiniz.

Be First to Comment

Leave a Reply

Your email address will not be published.