Assembly Temelleri
Temel x86/x64 komutları, çağrı konvansiyonları, stack frame ve kontrol akışını tersine mühendislik bakışıyla okuma rehberi.
İçindekiler
Tersine mühendislikte amaç çoğu zaman Assembly yazmak değil, derleyicinin ürettiği kodu anlamlandırmaktır. Tek tek komutları ezberlemek yerine veri hareketini, fonksiyon sınırlarını ve kontrol akışını takip etmek daha hızlı sonuç verir.
Temel Komut Kalıpları
| Komut | Davranış | Analizdeki karşılığı |
|---|---|---|
MOV dst, src | Veri taşır | Atama veya bellek okuma/yazma |
LEA dst, [adres] | Adres hesaplar | Pointer veya hızlı aritmetik |
PUSH / POP | Stack’e yazar veya stack’ten okur | Argüman ve register saklama |
CALL | Dönüş adresini kaydedip hedefe gider | Fonksiyon çağrısı |
RET | Stack’teki dönüş adresine gider | Fonksiyon sonu |
CMP / TEST | Flag’leri günceller | Koşullu dallanma hazırlığı |
JMP / Jcc | Akışı başka adrese taşır | Döngü, koşul veya switch |
mov eax, [ebp+8]
cmp eax, 1337h
jne fail
mov eax, 1
ret
fail:
xor eax, eax
retBurada CMP herhangi bir sonucu saklamaz. Yalnızca flag’leri değiştirir; JNE de ZF değerine bakarak başarısızlık dalına geçer.
Veri Taşıma ve Aritmetik
mov eax, [ebx+8] ; bellekteki alanı oku
lea ecx, [ebx+8] ; alanın adresini hesapla
movzx edx, byte [esi] ; bir baytı sıfır uzantılı taşı
add eax, 10
sub eax, edx
xor ecx, ecx ; ECX = 0
shl eax, 2 ; 4 ile çarp
and eax, 0FFh ; yalnızca alt baytı bırakLEA adı bellek yüklemesini çağrıştırsa da veriyi okumaz. Derleyiciler adres hesabının yanında lea eax, [ecx+ecx*4] gibi ifadelerle çarpma işlemini de gerçekleştirebilir.
İşaretli ve İşaretsiz Dallanma
Aynı bit dizisi işaretli veya işaretsiz sayı olarak yorumlanabilir. Bu ayrım kullanılan dallanma komutunda görünür.
| Koşul | İşaretli | İşaretsiz |
|---|---|---|
| Büyük | JG | JA |
| Büyük veya eşit | JGE | JAE |
| Küçük | JL | JB |
| Küçük veya eşit | JLE | JBE |
movzx eax, byte [rdi]
sub eax, 41h
cmp eax, 19h
ja not_uppercaseBu kalıp karakterin A ile Z aralığında olup olmadığını işaretsiz karşılaştırmayla denetler.
Stack Frame ve Fonksiyon Sınırları
Klasik 32 bit bir fonksiyon girişte önceki frame pointer’ı saklar, yerel değişkenler için alan ayırır ve çıkışta bu işlemleri geri alır.
push ebp
mov ebp, esp
sub esp, 20h
push esi
mov eax, [ebp+8]
mov [ebp-4], eax
pop esi
mov esp, ebp
pop ebp
retBu düzen her zaman görülmez. Derleyici optimizasyonu frame pointer’ı kaldırabilir:
sub rsp, 28h
mov [rsp+20h], rcx
call helper
add rsp, 28h
retBöyle bir fonksiyonda yerel değişkenleri RSP değişimleri üzerinden izlemek gerekir. Tek bir ofset, stack pointer değiştikçe farklı bir konumu gösterebilir.
Çağrı Konvansiyonları
Çağrı konvansiyonu argümanların nereye yazıldığını, dönüş değerini ve stack temizliğini belirler. Yanlış konvansiyon seçimi, decompiler’ın parametreleri de yanlış yorumlamasına neden olur.
| Platform | İlk argümanlar | Dönüş | Önemli ayrıntı |
|---|---|---|---|
Windows x86 cdecl | Stack, sağdan sola | EAX | Stack’i çağıran temizler |
Windows x86 stdcall | Stack, sağdan sola | EAX | Stack’i çağrılan temizler |
| Windows x64 | RCX, RDX, R8, R9 | RAX | 32 bayt shadow space |
| Linux x64 System V | RDI, RSI, RDX, RCX, R8, R9 | RAX | Shadow space yoktur |
; Windows x64: MessageBoxA(NULL, text, title, 0)
xor rcx, rcx
lea rdx, [rel text]
lea r8, [rel title]
xor r9d, r9d
sub rsp, 20h
call MessageBoxA
add rsp, 20hKontrol Akışını C Mantığına Çevirmek
Disassembly okurken satır satır ilerlemek yerine basic block’ları gruplayın. Bir karşılaştırma, koşullu dal ve iki hedef blok çoğu zaman bir if/else yapısını temsil eder.
cmp byte [rbp-1], 42h
jne bad_path
call good
jmp done
bad_path:
call bad
done:Benzer biçimde geriye doğru atlayan bir dal çoğunlukla döngüdür:
loop_start:
cmp byte [rdi], 0
je loop_end
inc rdi
jmp loop_start
loop_end:Switch yapıları ise sabit sayıda hedef içeren jump table biçiminde derlenebilir. Dolaylı jmp [table+rax*8] benzeri bir komut gördüğünüzde önce index sınırını kontrol eden karşılaştırmayı arayın.
CALL ve RET
CALL, sıradaki komutun adresini stack’e yazar ve hedef fonksiyona gider. RET ise stack’in tepesindeki adresi alarak yürütmeyi oradan sürdürür. Normal fonksiyon akışı, position-independent code ve bazı gizleme teknikleri bu davranıştan yararlanır.
; Program sayacına yakın bir adresi elde eden klasik kalıp
call next
next:
pop eaxBu kalıbı gördüğünüzde otomatik olarak zararlı davranış sonucu çıkarmayın. Kodun neden kendi adresine ihtiyaç duyduğunu çevresindeki kullanımlarla doğrulayın.
Pratik Okuma Sırası
- Fonksiyonun girişinde kullanılan argüman register’larını ve stack değişimini belirleyin.
CALLhedeflerini mümkün olduğunca isimlendirin.CMPveyaTESTile hemen sonraki dallanmayı birlikte okuyun.- Geriye giden dallarda sayaç ve çıkış koşulunu bulun.
- Dönüşten önce
RAX/EAXdeğerinin nasıl üretildiğini kontrol edin.
Bu sıra, decompiler çıktısına körü körüne güvenmeden Assembly ile üst seviye mantık arasında bağ kurmayı kolaylaştırır.