Yazı arşivi

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.

Enes Fırat5 dk. okuma
İç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 bit64 bitAlt parçalarAnalizde sık görülen rol
EAXRAXAX, AH, ALDönüş değeri ve genel amaçlı işlemler
EBXRBXBX, BH, BLGenel amaçlı veri veya taban adresi
ECXRCXCX, CH, CLSayaç; Windows x64’te ilk argüman
EDXRDXDX, DH, DLVeri; Windows x64’te ikinci argüman
ESIRSISI, SILKaynak adresi
EDIRDIDI, DILHedef adresi
EBPRBPBP, BPLKlasik stack frame tabanı
ESPRSPSP, SPLStack’in tepesi
EIPRIP—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.

FlagAnlamıSık görülen dallanma
ZFSonuç sıfırJZ, JNZ
CFİşaretsiz taşma veya ödünçJA, JB
SFSonuç negatifİşaretli karşılaştırmalar
OFİşaretli taşmaJG, JL ile birlikte
Assembly
cmp eax, 10
jg  greater_signed
ja  above_unsigned

test eax, eax
jz   eax_is_zero

Windows’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.

Assembly
; 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ölgeTipik izinİçerikTersine mühendislik ipucu
.textR-XMakine komutlarıNormal koşulda yazılabilir olmamalıdır
.rdataR—Sabit metinler ve import verileriString referansları için iyi başlangıçtır
.dataRW-Global durum ve yapılandırmaÇalışma sırasında değişebilir
HeapRW-Dinamik olarak ayrılan veriÇözülen veya açılan veri burada görülebilir
StackRW-Dönüş adresi, argüman ve yerel değişkenlerFonksiyon 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 adres

Stack’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ürBoyutÖrnek
BYTE8 bitAL
WORD16 bitAX
DWORD32 bitEAX
QWORD64 bitRAX
Python
import struct

raw = bytes.fromhex("78 56 34 12")
print(hex(struct.unpack("<I", raw)[0]))  # 0x12345678
print(hex(struct.unpack(">I", raw)[0]))  # 0x78563412

Değ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.

KomutAnlamı
mov eax, ebxEBX 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], eaxEAX değerini hesaplanan adrese yazar
Assembly
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.

Assembly
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
AdreslemeOlası 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.

Etiketler

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