Yazı arşivi

Assembly Temelleri

Temel x86/x64 komutları, çağrı konvansiyonları, stack frame ve kontrol akışını tersine mühendislik bakışıyla okuma rehberi.

Enes Fırat4 dk. okuma
İç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ı

KomutDavranışAnalizdeki karşılığı
MOV dst, srcVeri taşırAtama veya bellek okuma/yazma
LEA dst, [adres]Adres hesaplarPointer veya hızlı aritmetik
PUSH / POPStack’e yazar veya stack’ten okurArgüman ve register saklama
CALLDönüş adresini kaydedip hedefe giderFonksiyon çağrısı
RETStack’teki dönüş adresine giderFonksiyon sonu
CMP / TESTFlag’leri güncellerKoşullu dallanma hazırlığı
JMP / JccAkışı başka adrese taşırDöngü, koşul veya switch
Assembly
mov eax, [ebp+8]
cmp eax, 1337h
jne fail
mov eax, 1
ret

fail:
xor eax, eax
ret

Burada 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

Assembly
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ırak

LEA 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ükJGJA
Büyük veya eşitJGEJAE
KüçükJLJB
Küçük veya eşitJLEJBE
Assembly
movzx eax, byte [rdi]
sub   eax, 41h
cmp   eax, 19h
ja    not_uppercase

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

Assembly
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
ret

Bu düzen her zaman görülmez. Derleyici optimizasyonu frame pointer’ı kaldırabilir:

Assembly
sub rsp, 28h
mov [rsp+20h], rcx
call helper
add rsp, 28h
ret

Bö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ümanlarDönüşÖnemli ayrıntı
Windows x86 cdeclStack, sağdan solaEAXStack’i çağıran temizler
Windows x86 stdcallStack, sağdan solaEAXStack’i çağrılan temizler
Windows x64RCX, RDX, R8, R9RAX32 bayt shadow space
Linux x64 System VRDI, RSI, RDX, RCX, R8, R9RAXShadow space yoktur
Assembly
; 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, 20h

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

Assembly
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:

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

Assembly
; Program sayacına yakın bir adresi elde eden klasik kalıp
call next
next:
pop eax

Bu 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ı

  1. Fonksiyonun girişinde kullanılan argüman register’larını ve stack değişimini belirleyin.
  2. CALL hedeflerini mümkün olduğunca isimlendirin.
  3. CMP veya TEST ile hemen sonraki dallanmayı birlikte okuyun.
  4. Geriye giden dallarda sayaç ve çıkış koşulunu bulun.
  5. Dönüşten önce RAX/EAX değ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.

Etiketler

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