PE Dosya Formatı
Windows PE dosyalarının header, section, import, export ve adres dönüşümlerini statik analiz bakışıyla açıklayan temel rehber.
İçindekiler
Windows’taki .exe, .dll ve .sys gibi ikili dosyalar Portable Executable, kısaca PE biçimini kullanır. PE yapısını okumak; bir dosyanın mimarisini, başlangıç noktasını, ihtiyaç duyduğu API’leri ve bellekte nasıl yerleşeceğini programı çalıştırmadan anlamayı sağlar.
PE Dosyasının İskeleti
Bir PE dosyası DOS header ile başlar, NT header ve section tablosuyla devam eder. Gerçek kod ve veriler section’larda bulunur.
PE dosyası
├── DOS Header
│ ├── e_magic: MZ
│ └── e_lfanew: NT Header ofseti
├── DOS Stub
├── PE imzası: PE\0\0
├── COFF / File Header
├── Optional Header
├── Section Table
└── Section'lar
├── .text
├── .rdata
├── .data
├── .rsrc
└── .relocAdında “optional” geçmesine rağmen Optional Header, çalıştırılabilir bir PE dosyasında temel çalışma bilgilerini taşır. Entry point, image base, hizalama değerleri ve data directory burada yer alır.
DOS ve NT Header Doğrulaması
DOS header’ın ilk iki baytı MZ imzasıdır. 0x3C ofsetindeki e_lfanew alanı NT header’ın dosya içindeki konumunu gösterir. Bu adresteki değer PE\0\0 olmalıdır.
if (*(uint16_t *)base != 0x5A4D)
return BAD_FORMAT;
uint32_t pe_offset = *(uint32_t *)(base + 0x3C);
if (*(uint32_t *)(base + pe_offset) != 0x00004550)
return BAD_FORMAT;Bellekte MZ görmek tek başına dosyanın güvenilir olduğunu göstermez. Yine de gömülü veya belleğe açılmış başka bir PE görüntüsünü ararken yararlı bir başlangıç işaretidir.
File Header ve Optional Header
File Header hedef mimariyi, section sayısını ve bazı dosya özelliklerini taşır. Sık görülen Machine değerleri x86 için 0x014C, x64 için 0x8664’tür.
| Alan | Analizdeki anlamı |
|---|---|
Machine | Hedef işlemci mimarisi |
NumberOfSections | Section tablosundaki kayıt sayısı |
TimeDateStamp | Derleme zamanı olduğu iddia edilen değer |
Characteristics | EXE, DLL ve diğer dosya bayrakları |
Timestamp kolayca değiştirilebilir. Olağandışı bir tarih ipucudur fakat tek başına kanıt değildir.
Optional Header içinde öncelikle şu alanlara bakılır:
| Alan | Görevi |
|---|---|
Magic | 0x10B PE32, 0x20B PE32+ |
AddressOfEntryPoint | Yürütmenin başladığı RVA |
ImageBase | Tercih edilen yükleme adresi |
SectionAlignment | Bellekteki section hizalaması |
FileAlignment | Diskteki section hizalaması |
SizeOfImage | Bellekteki görüntünün toplam boyutu |
Subsystem | GUI, konsol veya başka çalışma ortamı |
DllCharacteristics içindeki ASLR ve DEP/NX gibi güvenlik bayrakları da önemlidir. Bayrağın bulunmaması mutlaka kötü niyetli davranış anlamına gelmez; dosyanın yaşı ve derleyici zinciriyle birlikte değerlendirilmelidir.
Section Tablosunu Okumak
Section tablosu diskteki ham verinin bellekte nereye ve hangi izinlerle yerleşeceğini açıklar.
| Section | Normal içerik | İncelenecek durum |
|---|---|---|
.text | Çalıştırılabilir kod | Aynı anda yazılabilir ve çalıştırılabilir olması |
.rdata | Sabitler ve import verisi | Beklenmedik yüksek entropi |
.data | Global değişkenler | Büyük şifreli yapılandırma alanları |
.rsrc | İkon, manifest ve kaynaklar | Büyük veya gömülü ikili veri |
.reloc | Relocation kayıtları | ASLR ile birlikte değerlendirilir |
UPX benzeri packer section adları, çok yüksek entropi, olağandışı entry point ve küçük ham boyuta karşılık büyük virtual size dikkat gerektirir. Bunlar tek başına kesin hüküm değil, sonraki analizi yönlendiren göstergelerdir.
import pefile
pe = pefile.PE("sample.exe")
print(hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint))
for section in pe.sections:
name = section.Name.rstrip(b"\x00").decode(errors="ignore")
print(
name,
hex(section.VirtualAddress),
hex(section.PointerToRawData),
f"entropy={section.get_entropy():.2f}",
)RVA, VA ve File Offset
PE analizinde aynı konum üç farklı adres biçimiyle karşımıza çıkar:
- VA, dosya belleğe yüklendikten sonraki tam adrestir.
- RVA, bu adresin image base’e göre uzaklığıdır.
- File offset, verinin diskteki ham dosya içindeki konumudur.
VA = ImageBase + RVA
RVA = VA - ImageBase
FileOffset = RVA - Section.VirtualAddress
+ Section.PointerToRawDataÖrneğin RVA 0x1500, section RVA’sı 0x1000 ve ham veri başlangıcı 0x400 ise file offset 0x900 olur.
def rva_to_file_offset(rva, section_rva, raw_pointer):
return rva - section_rva + raw_pointer
print(hex(rva_to_file_offset(0x1500, 0x1000, 0x400))) # 0x900Bu dönüşümü yalnızca hedef RVA’nın gerçekten ilgili section’ın sanal aralığında bulunduğunu doğruladıktan sonra yapmak gerekir.
Import ve Export Tabloları
Import tablosu dosyanın hangi DLL ve fonksiyonlara ihtiyaç duyduğunu gösterir. Loader, program yüklenirken Import Address Table içindeki kayıtları gerçek fonksiyon adresleriyle doldurur.
| API grubu | Örnekler | Olası davranış |
|---|---|---|
| Bellek | VirtualAlloc, VirtualProtect | Dinamik kod veya unpack işlemi |
| Süreç erişimi | OpenProcess, WriteProcessMemory | Başka bir sürece erişim |
| Registry | RegSetValueEx, RegCreateKeyEx | Yapılandırma veya kalıcılık |
| Ağ | InternetOpen, connect, send | Ağ iletişimi |
| Kripto | CryptDecrypt, BCryptEncrypt | Veri çözme veya şifreleme |
Bir API adı tek başına davranışı kanıtlamaz. VirtualAlloc sıradan uygulamalarda da kullanılır. Doğru yorum için fonksiyonun argümanlarına, çağrı sırasına ve çevresindeki kontrol akışına bakılmalıdır.
Export tablosu ise özellikle DLL’lerin dışarı sunduğu fonksiyonları listeler. Fonksiyonlar adla veya ordinal numarasıyla dışa aktarılabilir; yalnızca ordinal kullanılması analizi zorlaştırabilir.
Resource, Relocation ve TLS
.rsrc içinde ikon, sürüm bilgisi, manifest ve uygulamaya ait ikili kaynaklar bulunabilir. Büyük ve yüksek entropili bir kaynak gömülü veri ihtimalini artırır, ancak meşru sıkıştırılmış medya da aynı görüntüyü verebilir.
Relocation tablosu, görüntü tercih edilen image base dışında bir adrese yüklendiğinde düzeltilecek konumları içerir. TLS callback’leri ise ana entry point’ten önce çalışabilir. Bu nedenle başlangıç akışını incelerken yalnızca AddressOfEntryPoint değerine odaklanmak yeterli değildir.
İlk Statik Analiz Sırası
- İmzaları, mimariyi ve section sayısını doğrulayın.
- Entry point’in hangi section içinde olduğunu bulun.
- Section izinlerini, boyutlarını ve entropisini karşılaştırın.
- Importları tek tek değil, davranış grupları halinde inceleyin.
- Resource ve TLS kayıtlarını kontrol edin.
- Bulguları string referansları ve disassembly akışıyla doğrulayın.
Bu sıra bir dosyanın kötü amaçlı olup olmadığına tek başına karar vermez; fakat ayrıntılı analize nereden başlanacağını sistemli hale getirir.