CPU Mimarisi ve Bellek Modeli
x86 ve x64 register yapısı, bellek bölgeleri, endian düzeni, pointer ayrımı ve stack üzerinden fonksiyon okuma temelleri.
İçindekiler
Bir programı tersine mühendislik açısından okumaya başladığımızda kaynak koddaki değişkenleri değil; register’ları, adresleri ve bellek bölgelerini görürüz. Bu nedenle ilk hedef bütün komutları ezberlemek değil, işlemcinin veriyi nerede tuttuğunu ve nasıl taşıdığını anlamaktır.
Register’ları Anlamlandırmak
Register’lar işlemcinin en hızlı çalışma alanıdır. Aynı fiziksel register’ın farklı boyutlarına farklı adlarla erişilebilir. Örneğin RAX 64 bitlik register’ın tamamını, EAX alt 32 bitini, AX alt 16 bitini; AH ve AL ise birer baytlık parçalarını ifade eder.
| 32 bit | 64 bit | Alt parçalar | Analizde sık görülen rol |
|---|---|---|---|
| EAX | RAX | AX, AH, AL | Dönüş değeri ve genel amaçlı işlemler |
| EBX | RBX | BX, BH, BL | Genel amaçlı veri veya taban adresi |
| ECX | RCX | CX, CH, CL | Sayaç; Windows x64’te ilk argüman |
| EDX | RDX | DX, DH, DL | Veri; Windows x64’te ikinci argüman |
| ESI | RSI | SI, SIL | Kaynak adresi |
| EDI | RDI | DI, DIL | Hedef adresi |
| EBP | RBP | BP, BPL | Klasik stack frame tabanı |
| ESP | RSP | SP, SPL | Stack’in tepesi |
| EIP | RIP | — | Sıradaki komutun adresi |
x64 kodda EAX gibi 32 bitlik bir alt register’a yazmak, RAX’ın üst 32 bitini sıfırlar. Bu yüzden mov eax, 1 işleminden sonra RAX değeri de pratikte 1 olur.
Özel Register’lar ve Flag’ler
EFLAGS veya RFLAGS, karşılaştırma sonucunu ve işlem durumunu tutar. CMP ile TEST sonucu bir değişkene yazmaz; yalnızca bu bayrakları günceller. Ardından gelen koşullu dallanma komutu kararı buradan verir.
| Flag | Anlamı | Sık görülen dallanma |
|---|---|---|
| ZF | Sonuç sıfır | JZ, JNZ |
| CF | İşaretsiz taşma veya ödünç | JA, JB |
| SF | Sonuç negatif | İşaretli karşılaştırmalar |
| OF | İşaretli taşma | JG, JL ile birlikte |
cmp eax, 10
jg greater_signed
ja above_unsigned
test eax, eax
jz eax_is_zeroWindows’ta FS ve GS de analiz açısından önemlidir. 32 bit süreçlerde FS:[30h], 64 bit süreçlerde ise GS:[60h] üzerinden PEB adresine ulaşılması sık görülen bir kalıptır.
; 32 bit Windows
mov eax, fs:[30h]
movzx eax, byte [eax+2]
test eax, eax
jnz debugger_detected
; 64 bit Windows
mov rax, gs:[60h]Bellek Bölgeleri
Bir sürecin belleği tek parça değildir. Kod, sabit veriler, çalışma sırasında ayrılan alanlar ve fonksiyon çağrıları farklı bölgelerde tutulur.
| Bölge | Tipik izin | İçerik | Tersine mühendislik ipucu |
|---|---|---|---|
.text | R-X | Makine komutları | Normal koşulda yazılabilir olmamalıdır |
.rdata | R— | Sabit metinler ve import verileri | String referansları için iyi başlangıçtır |
.data | RW- | Global durum ve yapılandırma | Çalışma sırasında değişebilir |
| Heap | RW- | Dinamik olarak ayrılan veri | Çözülen veya açılan veri burada görülebilir |
| Stack | RW- | Dönüş adresi, argüman ve yerel değişkenler | Fonksiyon akışını izlemeyi sağlar |
Yüksek adres
┌────────────────────┐
│ Stack │ RSP/ESP burayı gösterir, aşağı doğru büyür
├────────────────────┤
│ Heap │ Dinamik ayırmalar burada tutulur
├────────────────────┤
│ .data / .rdata │ Global ve sabit veriler
├────────────────────┤
│ .text │ Çalıştırılabilir kod
└────────────────────┘
Düşük adresStack’te görülen her sayı bir pointer değildir. Bir değeri adres olarak yorumlamadan önce debugger’ın bellek haritasında geçerli ve erişilebilir bir bölgeye denk gelip gelmediğini kontrol etmek gerekir.
Endian ve Veri Boyutu
x86 ve x64 mimarileri little endian düzenini kullanır. Çok baytlı bir sayının en düşük anlamlı baytı bellekte önce yer alır. Örneğin 0x12345678, hex görünümünde 78 56 34 12 olarak saklanır.
| Tür | Boyut | Örnek |
|---|---|---|
| BYTE | 8 bit | AL |
| WORD | 16 bit | AX |
| DWORD | 32 bit | EAX |
| QWORD | 64 bit | RAX |
import struct
raw = bytes.fromhex("78 56 34 12")
print(hex(struct.unpack("<I", raw)[0])) # 0x12345678
print(hex(struct.unpack(">I", raw)[0])) # 0x78563412Değer, Pointer ve Adres
Bir register’ın içindeki sayının değer mi yoksa adres mi olduğunu komutun bağlamı belirler. Köşeli parantez kullanılması genellikle bellek erişimi yapıldığını gösterir.
| Komut | Anlamı |
|---|---|
mov eax, ebx | EBX değerini EAX’e kopyalar |
mov eax, [ebx] | EBX’in gösterdiği adresteki değeri okur |
lea eax, [ebx+4] | Adresi hesaplar, bellekten veri okumaz |
mov [ebx+4], eax | EAX değerini hesaplanan adrese yazar |
mov rbx, 0000000140003000h
mov eax, [rbx]
lea rcx, [rbx+20h]Bu örnekte ilk satır tek başına RBX’in pointer olduğunu kanıtlamaz. İkinci satırda register bir bellek adresi olarak kullanıldığı için yorum kesinleşir.
Stack Üzerinden Fonksiyon Okumak
Klasik 32 bit fonksiyonlarda pozitif EBP ofsetleri argümanlara, negatif ofsetler yerel değişkenlere işaret eder.
push ebp
mov ebp, esp
sub esp, 10h
mov eax, [ebp+8]
mov ecx, [ebp+0Ch]
add eax, ecx
mov [ebp-4], eax
mov esp, ebp
pop ebp
ret| Adresleme | Olası anlam |
|---|---|
[EBP+4] | Dönüş adresi |
[EBP+8] | İlk argüman |
[EBP+0Ch] | İkinci argüman |
[EBP-4] | Yerel değişken |
Optimize edilmiş kodda frame pointer kaldırılabilir. Böyle bir durumda RBP her zaman frame tabanı değildir; RSP değişimleri ve fonksiyonun çağrı konvansiyonu birlikte izlenmelidir.
Analiz İçin Kısa Bir Rutin
Bir fonksiyonu ilk kez açtığınızda önce girişte kullanılan register’ları ve stack değişimini not edin. Ardından CALL hedeflerini, CMP/TEST ile bunları izleyen dallanmaları ve dönüş değerinin yazıldığı register’ı işaretleyin. Bu küçük rutin, uzun bir disassembly görünümünü anlamlı bloklara ayırmayı kolaylaştırır.