Yazı arşivi

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.

Enes Fırat5 dk. okuma
İç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
    └── .reloc

Adı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.

C
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.

AlanAnalizdeki anlamı
MachineHedef işlemci mimarisi
NumberOfSectionsSection tablosundaki kayıt sayısı
TimeDateStampDerleme zamanı olduğu iddia edilen değer
CharacteristicsEXE, 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:

AlanGörevi
Magic0x10B PE32, 0x20B PE32+
AddressOfEntryPointYürütmenin başladığı RVA
ImageBaseTercih edilen yükleme adresi
SectionAlignmentBellekteki section hizalaması
FileAlignmentDiskteki section hizalaması
SizeOfImageBellekteki görüntünün toplam boyutu
SubsystemGUI, 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.

SectionNormal içerikİncelenecek durum
.textÇalıştırılabilir kodAynı anda yazılabilir ve çalıştırılabilir olması
.rdataSabitler ve import verisiBeklenmedik yüksek entropi
.dataGlobal değişkenlerBüyük şifreli yapılandırma alanları
.rsrcİkon, manifest ve kaynaklarBüyük veya gömülü ikili veri
.relocRelocation 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.

Python
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.

Python
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)))  # 0x900

Bu 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ÖrneklerOlası davranış
BellekVirtualAlloc, VirtualProtectDinamik kod veya unpack işlemi
Süreç erişimiOpenProcess, WriteProcessMemoryBaşka bir sürece erişim
RegistryRegSetValueEx, RegCreateKeyExYapılandırma veya kalıcılık
AğInternetOpen, connect, sendAğ iletişimi
KriptoCryptDecrypt, BCryptEncryptVeri çö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ı

  1. İmzaları, mimariyi ve section sayısını doğrulayın.
  2. Entry point’in hangi section içinde olduğunu bulun.
  3. Section izinlerini, boyutlarını ve entropisini karşılaştırın.
  4. Importları tek tek değil, davranış grupları halinde inceleyin.
  5. Resource ve TLS kayıtlarını kontrol edin.
  6. 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.

Etiketler

Bu yazı CC BY 4.0 ile lisanslanmıştır.