x86 ve Tersine Mühendislik

Daha önceki yazımda x86’da bulunan yazmaçlara değinmiştim. Bu yazımda da sıklıkla kullanacağımız yazmaçlarla ile birlikte assemble’dan başlayarak herhangi bir yazılımı (şimdilik yazılım bizim için ufak tek exe’lik programlar olacak) adam nasıl yazmış ne kastetmiş, programın ne yapmasını istemiş gibi yorumlarda bulunabileceğiz.Öncelikle 32bitlik assemblynin temel verilerine bakalım: bit, byte, word ve dword kavramlarına bakalım;

BIT →1, 0

BYTE →8xBIT

WORD →16BIT(2BYTE)

DWORD →32BIT(4BYTE)

Günümüzde 32 bit yaygın olmasına rağmen 16bitlik dönemlerden bize miras kalan word ozamanlar için işlemcinin konuştuğu tam bir kelime manasına geliyordu. 32 bit çıktıktan sonra buna da double word anlamında dword demiş mühendis abilerimiz. 64 bit için de quadword diyorlar orası ayrı mevzu.

Öceki yazıda genel işlevli yazmaçlardan bahsetmiştik. Hatta önemine binaen tekrar hatırlatarım; ESP ev EBP yazmaçlarının aritmetik ve amacı dışındaki işlemler için kullanmaktan şiddtele kaçının demiştik. Eğer kullanacaksanız da bunları önce kaydedin işiniz bittiğinde kayıt ettiğiniz son değeri tekrar alın yoksa uygulama kendinden geçebilir ve çökebilir.

x86 mimarisinde tüm işlemler ve yazmaçlar aynı boyutta değildir(ah MIPS ah..).  Ayrıca x86’da her yazmaç aynı yerde değildir, bazı yazmaçlar aslında, aşağıdaki şekilde de görebileceğiniz gibi, diğer yazmaçların belirli blokları olarak atanmıştır. Burada E extended ifadesi için (16 bit dünyasından 32 bite geçtiğimizi hatırlatayım) AL ve AH yazmaçlarındaki L ve H ise Low(alt) ve High(üst) ifadelerini temsil etmektedir.

x86registers

 

Segment Yazmaçları

Segment yazmaçları kod bloklarının binary dizininde bölmeler yapmak için kullanılır. Örneğin 0x60 bir instruction ya da bir veri alanına denk gelebilir. CPU segment yazmaçları sayesinde bunu ayırtedebilmektedir.

Flag (Bayrak) Yazmaçları

Bayrak denilen şey aslında flag yazmacı içerisindeki bitlerdir. Bunlar 1 veya 0 olması durumunda göre işlemlerin sonuçlarını değiştirir. Örneğin “signed” bayrağı set edilmiş ise (1 ise) hexadecimal FF değeri ondalık -1 değerini ifade eder yok eğer 0 ise 255 değerini temsil eder. Bu alanlar bir işlem yapıldığında set edilir veya temizlenir. En çok karşımıza çıkacak olanlar şunlardır;

Z – zero flag: Yapılan son işlemin sonucu 0 ise set edilir.

S – signed flag: Kullanılan ya da sonuç olarak üretilen bir değer negatif değerler alabilir mi alamaz mı onu belirler

O – overflow flag: Yapılan işlem sonucu bir değer taşması var mı(en önemli bit alanı,son bit, 0’dan F ye veya F’den 0 a değişti mi) bilgisini tutar.

C – carry flag: Son işlem eğer en önemli biti değiştirmişse set edilir.

 Segment ve offsetler

Her program nihayetinde derlenir ve çalışmak üzere hafızaya yüklenerek işlemciye gideceği sırayı bekler. İşte bu çalışmaya hazır hale gelmiş programlar üzerinde belirli alanlar bulunur. Bir işlemci her farklı programın nereden başlayacağını neresini nasıl kullanacağını bilemez bunun için bu programlar derlendikten sonra hepsi belirli bir şablona oturtulur. Bu bahsettiğimiz şablonda ise 4 tane segment dediğimiz alanlar mevcuttur. Bunlar;

.text  .data  .stack  .heap

Program kodları text alanı içersinde tutulur. Uygulamanın heryerinde kullanılan veriler  data alanında, stack ise daha önce bahsettiğimiz yığındır. Bir çok şeyin yanında yerel değişkenler, fonksiyon parametreleri gibi sürekli değişip (giren-çıkan) elemanları barındırırç Heap ise uygulamanın nezaman fazla hafıza alanına ihtiyaç olursa(örneğin dizi işlemlerinde) başvuracağı alandır.

Stack (Yığıt)

Yığıt yapısı üst üste konulan kitaplar gibi düşünülebilir. Ancak hafıza da yüksek hafıza alanından düşük hafıza alanına doğru sıralanır. Daha önce bahsettiğimiz ESP ve EBP yazmaçları yığıt üzerinde fazlaca kullanılır. Örneğin bir eleman eklendiğinde ESP nin gösterdiği adres değeri azalır.

EBP’nin bir base (taban) işaretçisi oldupunu söylemiştik. Peki neye göre taban? Her program thread’lerden oluşur ve her thread kendi yığıtına sahiptir. Bu yığıt içindeyse thread’deki her fonksiyonun bir alanı vardır ve bunlar kendi stack frame denilen alanlara sahiptir. Bir fonksyion çağrıldığı zaman hemen stack üzerindeki yerini belirlemek için EBP alanına adresini yazar. ESP ise herzaman yığıtın en tepesini gösterir.

Heap (Yığın)

Heap tüm threadler ve işlemleri için tek bir yedek alandır yani her threadin stack gib ayrı bir yığını olamaz. Linked list şeklinde bir veri yapısına sahiptir. Yani her eleman sadece kendinden önceki ve sonraki alandan haberdardır. Kullanımı bittikten sonra dereferans ya da “free”edilerek yeni işlemler için yer açılır.

Instructions (Komutlar)

intel komutlar 1 Byte ile 14 Byte arasında değişen uzunluklara sahiptir.Opcode yani işlemin kodu alanı her işlem içinde mutlaka bulunan alandır ve diğer alanlarla birleşerek daha karışık ve gelişmiş komutlar üretilir. Disassembler kullanacağımız için buradaki bit desenleri ile fazla işimiz olmayacak.

Bazı komutlar bir operatörden oluşurken bazıları 2(ör. add eax, ebx)  ya da 3(ör. imul eax, edx, 64) operatörden oluşabilir. dword ptr [eax] şeklinde operatör bulunan işlemler hafıza alanında [xxx] şeklinde bir offset ile gösterilen yerde 4Bytle’lık bir alana referans göstermektedir.

Dikkat edilmeliki hafızadaki byte’lar ters sıradadır, çünkü intel “Little Endian” sunumu kullanır yani en önemli bit en soldaki bit’tir.

Arithmetic operations – ADD,SUB, MUL, IMUL, DIV, IDIV

ADD işleminin sentaksı:
add hedef, kaynak şeklindedir.

hedef kendi değer üzerine kaynaktaki değeri de alarak yeni bir değere sahip olur. Hedef ve kaynak EAX gib bir yazmaç, hafıza alanı referansı [ESP] gibi köşeli parantezler içerisinde belirtilir. Kaynak aynı zamanda doğrudan bir sayı olabilir ancak ikisi birden hafıza alanı olamaz.

add eax, ebx ; hedef ve kaynak yazmaç iken
add [esp], eax ; hedef yığıtın tepesi kaynak yazmaç
add eax, [esp] ; hedef yazmaç, kaynak yığıtın ilk elemanı
add eax, 4 ; kaynak sayı hedef yazmaç

SUB işlemi de aynı ADD işlemi mantığıyla çalışır. Ancak çarpma ve bölme işlemleri biraz farklıdır.

DIV/IDIV işleminin sentaksı:
div bölen şeklindedir. Evet bölünen burda yazmaz çünkü bölünen herzaman aynı yerdedir yani EAX yazmacında.. Sonuç ise yine EAX yazmacına kaydedilir geriye kalan değer ise EDX yazmacına yazılır. IDIV işlemi ise DIV ile aynıdır ancak işaretli bölme yapar yani negatif sayıları negatif olarak değerlendirir. Aşağıdaki işlemlere bir göz atalım

mov eax, 45 ; Bölüneni yani 45 değerini EAX yazmacına taşı
mov ecx, 6 ; Bölecek sayıyı ECX yazmacına taşı
div ecx ; EAX deki 45 değerini ECX’teki 6 değerine böl kalan 3 ise otomatikman EDX üzerindedir. Ayrıca EAX’in yeni değeri 7’dir.

MUL/IMUL işleminin sentaksı:
mul çarpan
mul hedef, çarpan, çarpılan
mul hedef, çarpan
mul/imul (unsigned/signed) işlemleri EAXdeki değeri çarpan ile çarpar veya iki değeri çarpıp sonucu hedef yazmaca yazar.

Bit İşlemleri – AND, OR, XOR, NOT

AND sentaksı: and hedef, kaynak
OR sentaksı: or hedef, kaynak
XOR sentaksı: xor hedef, kaynak
NOT sentaksı: not eax

Bu işlemler hedef ve kaynaktaki değerleri bit bit değerlendirir ve sonucu hedefe yazarlar. Burada önemli bir nüans var, sadece XOR işlemi ile aynı iki değer işleme tabi tutulduğunda sonuç mutlaka 0 olur. Bir çok derleyici 0 değerini elde etmek ve bundan emin olmak için için bir yazmacı yine kendisiyle XOR işlemine taabi tutar çünkü bu işlem MOV ile aynı yazmaca 0 atamaktan daha hızlıdır. NOT ise gelen ifadeyi tersler ve aynı yerine koyar.

Dallanma – JMP, JE, JLE, JNZ, JZ, JBE, JGE

JMP/JE/JLE vb. işlemlerin sentaksı:
jmp adres şeklindedir.

Assembly de dallanma işlemi yani bir koşula göre farklı yerlere gitme işlemi jump ve bayraklar ile gerçekleşir. Jump komutu gerçekleşen işlemin sonucuna göre (aslında x86 için set edilen bayrağa göre) EIP yazmacını kod bloğunun başka bir alanına yönlendirir.

ADD ve SUB komutu tüm Z, S, O, C bayraklarını ve bazı diğerlerini set edebilir.
AND komutu sonuca göre herzaman O,C bayraklarını temizlerken Z, S bayraklarını st eder. Hangi bayrakların set edildiğine göre dallanma işlemi gerçekleşir veya gerçekleşmez.

Not: Jump işleminden önce çoğu zaman XOR veya CMP(=compare) komutu görürsünüz. CMP dallanma öncesi tüm durum bayraklarını set eden ve jump işleminin işini kolaylaştıran en ideal ve hızlı bir komuttur. Sentaksı cmp hedef, kaynak şeklindedir.

Jump işlemi için bazı komutlar aynı diyebilirsiniz örneğin JLE ve JBE komutları her ikisi de “less” ve “below” olarak adlanmış olmasına rağmen yüksek seviyeli bir dilde if (x <= y)
{ burayı çalıştır } şeklinde gösterilebilir. Ancak JLE işaretli sayıları karşılaştırma işleminde JBE ise işaretsiz sayıları karşılaştırmada kullanılır.

Veri Taşıma – MOV, MOVS, MOVSB, MOVSW, MOVZX, MOVSX, LEA

MOV, sentax: mov hedef, kaynak
MOVSB, sentax: movzx hedef, kaynak
MOVZX, sentax: movzx hedef, kaynak

MOV bir kaynaktan diğerine değer taşıma işlemini yapar. hem hedef hem kaynak yazmaç olabilri veya birisi yazmaç birisi hafıza olabilir ancak her ikisi de hafıza olamaz. MOV işleminin MOVS, MOVSB, MOVSW, MOVSD gibi bir çok formatı vardır. Bunlar byte, word veya dword taşıdıklarını gösterir. X harfine sahip olanlar bir içerdiği değişkenin tamamını dolduramadığı durumda geriye kalan bitlerini ne yapacağını belirler, örneğin C’de char’dan integer’a tip dönüşümü yapılma işlemi gibi ;

char a = ‘h’;
int b;
b = (int)a;

int char’dan büyük olacağı için a değişkenin tuttuğu değer hepsini dolduramaz ve movsx yaptı iseniz geri kalan tüm bitleri en soldaki bit ile doldurur, movzx yaptı iseniz hepsini 0 ile doldurur.

MOVSX) DEST ← Signextend[SRC]
MOVZX) DEST ← Zeroextend[SRC]

LEA işlemi (=Load Effective Address) bir adresten bir yazmaca değer taşımak için kullanılır ve sentaksı;

lea eax, dword ptr[ecx+edx] ; ecx ve edx deki değerleri topla ve o adresteki 32bitlik veriyi eax’e yükle

şeklindedir.

Döngüler – LOOP, REP

Jump kullanılarak gayet şık döngüler oluşturulabilmesine rağmen x86 işi bir adım  daha ileri götürerek LOOP ve REP komutunu getirmiş.

mov ecx, 5 ; ECX sayaç yazmacıydı hatırlayın
_proc:
dec ecx ; ecx’i 1 azalt
loop _proc ; tekrar _proc ile işaretlenmiş alana döner.

REP işlemi de LOOP işlemine benzer ancak daha çok stringler için tasarlanmıştır. Bu da IA-32 assembly dilini neredeyse bir yüksek seviyeli dil sınıfına çıkarır 🙂

mov esi, str1 ; karşılaştırılacak 1. string ESI yazmacında
mov edi, str2 ; karşılaştırılacak 2. string EDI yazmacında
mov, ecx, 10h ; 10h = 16 (byte) kadar geriye say
rep cmps ; ESI ve EDI yazmaçlarını ecx’deki sayı kadar byte ilerleyerek karşılaştır.

Eğer karşılaştırma işleminde eşitlik bozulursa işlem durur ve bir bayrak set edilerek işlemden çıkılır.

Yığıt Yönetimi – POP, PUSH

pop hedef
push değer/yazmaç

pop yığıtın en tepesinden bir değer ya da adressi hedef yazmaca yükler ayrıca ESP değerini de artırır. PUSH ise bir değeri yığıtın en tepesine ekler ve ESP değerini yeni tepe noktasına işaret edecek şekilde düşürür.

Fonksiyonlar – CALL, RET

Burada biraz Uyanın!!!

Evet bu kısım biraz zor gelebilir. CALL jump işlemine benzer, jump EIP’ye bir adres yükler programın çalışması oradan itibaren devam eder. Ancak CALL o anki EIP değerini yığıt üzerine, kendisini çağıran fonksiyonun artık sonuna geldiğinde, sonradan yeniden aynı yere koumak üzere kaydeder. Jump işleminin ise böyle bir kaydı yoktur. Sistemde call _function işlemi yürütüldüğünde;

  1. EIP call komutu aracılığıyla yığıta kaydedilir.
  2. EBP yığıta kaydedilir.
  3. EBP ESP ile aynı yere getirilir.(Aynı adres alanını gösterir.)
  4. ESP yerel değişkenler, parametreler ve diğer değerleri kapsayacak alan açacak kadar azaltılır.
  5. EIP’ye _function fonksiyonun adresi atanır

1 numaralı işlemi call komutu gerçekleştirdiğine göre 2 ve 3 numaralı komut ne aracılığıyla gerçekleşir peki? İşte bu nıoktada assembly ve binary arasında bir ayrım devreye giriyor. Assembly ilgili mimarinin komut setinden ibarettir(x86, ARM, PowerPC, MIPS, RISC…). Ancak binary programı; daha önceden belirlenmiş bir mimari, işletim sistemi üzerinde çalışmak üzere belirli bir derleyici tarafından derlenmiştir. Örneğin Windows binary dosyaları PE formatında linux ise Elf formatındadır. Ayrıca farklı derleyiciler aynı kod için farklı binary dizinleri çıkartabilir. Yukarıda 2. ve 3. adımlar için belirli bir komut yoktur. Ancak binary dosyalarına ayrı ayrı push ve move komutları ile aşağıdaki gibi yazılmışlardır ki buna “function prolog” da denir

push ebp
mov ebp, esp
sub esp, 1Ah ; 1A rastgele bir değerdir

Çağırılan fonksiyon işini bitirdiğinde, çağıran fonksiyonun EBP değeri yığıttan pop edilir. RET komutu ESP değerini artırarak (yığıtı küçülterek) çağırılan fonksiyonun yığıt alanını kaldırır ve eski EIP değerini yine yığıttan pop ile yükler ki program fonksiyon çağırılmadan önceki yerinden devam etsin. Peki fonksiyon bir geri dönüş değerine sahipse bu değer ne oluyor? Bu değer fonksiyon çıkmadan önce EAX yazmacına yazılıyor.

Interrupts (Kesmeler),ve Debugger tuzakları – INT, trap flag

INT sentaks; int num

Kesmeler CPU’ya o anda yapmakta olduğu işlemi (çalışan thread’i) durdurmasını söyler. Bu durum donanımdan, yazılımdan veya yetksisiz hafıza erişimi gibi bir hatadan kaynaklanıyor olabilir.  INT komutu işlemciye ulaştığında işlemler num operatörü ile belirtilen hata yakalayıcısına aktarılır. Her int komutu bu numaraya ihtiyaç duymaz örneğin;INT3.
Assembly seviyesinde bir uygulamayı debug ederkenbreakpoint konulan yerdeki komut hexadecimal değeri 0xCC olan int3 komutu ile değiştirilir. Bundan sonra ilgili thread kontrolü debugger’a geçer ve aynı zamanda trap bayrağı set edilir. Eğer trap bayrağı set edilmiş ise CPU tek bir komut çalıştıracak ve kontrolü debugger’a devredecektir.
Breakpointlerin de koşullu, donanımsal ve yazılımsal diye çeşitleri vardır, buradaki yazılımsal breakpointlerdir.

Çağrı Mekanizmaları

Daha önce CALL ve RET komutlarından bahsetmiştik ancak bunların çalışmaları stilleri bazen derleyicilere göre fark gösterebiliyor. Çağrı mekanizmaları çağıran ve çağrılan fonksiyonların işlemcide nasıl değerlendirileceğini belirler. Örneğin parametrelerin yığıta atılma sırası, dönüş değerleri ile yazmaçlar ve yığıtın temizlenmesini kimin üstleneceği gibi durumlar bu mekanizmalara göre farklılık gösterecektir.

cdecl: cdecl fonksiyonu (c declaration = c deklarasyonu) çoğu derleyicinin x86 için kullandığı ve C programlama dilinden türeyen bir fonksiyondur. stdcall fonksiyonundan farkı çağrılan değil çağıran fonksiyon yığıtı temizlemekten sorumludur. Aşağıdaki kod parçacaığını ele alalım;


int cdecl_func(int x, int y, int z);
int a, b, c, ret;
ret = cdecl_func(a, b, c);

kodunun assembly olara derlenmiş hali;

push c
push b
push a
call cdecl_func
add esp, 12
mov ret, eax

Burada parametreler sağdan sola doğru geçirilmekte ve cdecl_func fonksiyonunu çağıran fonksiyon yığıtı add esp,12(3 değişken * 4Byte) komutu ile temizlemekte.

stdcall:  cdecl ile tek fark dışında aynıdır. O da yukarıda gösterilen fonksiyon için düşünürsek yığıtı çağıran değil çağırılan fonksiyon temizler yani nasıl bulduysa öyle bırakır. Microsoft için varsayılan bir çağırma mekanizmasıdır. Return değeri EAX yazmacında saklanır.

pascal: tahmin edeceğiniz üzere pascal programlama dilinden türemiştir . stdcall fonksiyonundan farkı parametrelerin soldan sağa doğru sıralanmasıdır.

fastcall: Standartlaşmamış bir çağrıdır. Diğer çağrılar fonksiyon parametrelerini yığıtta saklarken bu çağrı öncelikle yazmaçlarda saklamak ister. Genellikle iki parametreyi doğrudan yazmaçlara (Microsoftta EDX ve ECX ) yazar. Bu daha az hafıza kaplamasını ve fonksiyonun bir nebze daha hızlanmasını sağlar(adından da anlaşılacağı gibi).

Çağrı mekanizmaları ile ilgili daha detaylı bilgi için wikipedia‘a bakabilirsiniz.

Assembly kodları derleyici ayarlarına veya türlerine göre hep aynı olmaz aşağıdaki kod bloğu için iki farklı derleyicinin sonuçlarına bakalım; GCC stack işaretçisini ayarlamakla uğraşmıyor mesela


int adder(int a, int b)
{
return a+b;
}
void main()
{
int x = 1;
int y = 2;
printf("the function returned the number %d\n", adder(x,y));
}

adder fonksiyonunun derlenmiş hali;

push ebp
mov ebp, esp
mov eax, [ebp+arg_0]
add eax,  [ebp+arg_4]
pop ebp
retn

 

Screenshot from 2016-03-01 09-32-23

 

 

Reklamlar

Bir Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Connecting to %s