Hüseyin Altunkaynak Kişisel Blog

Malware Analizi - 05

Malware Analizi

Malware Analizi serisinin ilk bölümüne ulaşmak için: Malware Analizi - 01

x86 Mimarisi

Registerlar

Yazmaçlar fiziksel olarak işlemcinin içinde bulunan hafıza alanlarıdır. Bu türden hafıza alanları işlem hızı en yüksek olan hafıza alanlarıdır. Ancak hızın yüksekliğinin yanında bu alanların boyutları da aynı oranda düşüktür. x86 mimarisinde en fazla 32 Bitlik registerlar bulunmaktadır. x86 denmesinin nedeni ise 8086 ve 80386 gibi sonu 86 ile biten işlemcilerde 32 Bit kullanılmasından dolayıdır.

Genel Amaçlı Registerlar

  • EAX (Accumulator Register) -> İşlemci tarafından matematiksel işlemler için öncelikli olarak kullanılan registerdır.
  • EBX (Base Pointer) -> Bu register, data segment içinde bir alanı göstermek veya hesaplamalarda extra alan olarak kullanılmaktadır.
  • ECX (Count Register) -> ECX registeri döngüsel işlemlerinde sıklıkla kullanılmaktadır.
  • EDX (Data Register) -> Genellikle büyük hesaplamalar yapılırken gereken extra alan için kullanılır. EAX registeri çarpma gibi bir matematiksel işlemde gerekli olan extra yeri EDX registerinden temin eder.
  • ESI (Source Index) -> ESI Registeri, genellikle string yada döngü işlemleri sırasında okuma yapılacak yerin adresini gösterir.
  • EDI (Destination Index) -> EDI Registeri, genellikle string yada döngü işlemleri sırasında yazma yapılacak yerin adresini gösterir.
  • EBP (Base Pointer) -> EBP Registeri stack yapısı içinde kullanılan yerel değişlenlere erişmek için referans olarak kullanılır.
  • ESP (Stack Printer) -> ESP Registerinin kullanma amacı stack alanının en üst noktasının bilgisini tutar.

Segment Registerları

CS, DS, SS, ES, FS, GS -> Amacı sanal hafıza içerisindeki spesifik segment alanlarını korumaktır. Segment kaydedicilerinin hepsi 16 bitliktir.

  • CS → Code Segment
  • DS → Data Segment
  • SS → Stack Segment
  • ES → Extra Segment
  • FS, GS → 80386 ve sonrası CPU’larda ilk 4 registerın yetmediği zaman kullanılır.

FLAG Registerı

FLAG Registerı, işlemci üzerinde gerçekleştirilen matematiksel işlemlerin durumlarını korumak için kullanılmaktadır. Farklı işlemciler bu registeri kullanırken farklı anlamlarda işlemler gerçekleştirebilirler.

flags

Instruction Pointer

Instruction Pointer (IP) çalıştırılacak olan bir sonraki makina kodunun sanal memory adresini tutan yazmaçtır. Instruction pointer, sanal hafızadaki kod segment alanında bulunan kodları sırasıyla işler. JMP yada CALL gibi hafıza üzerinde sıçrama yapan bir komuta gelene kadar bu sıralı işlemini sürdürür. 32 Bit sistemlerde Instruction Pointer EIP, 64 bit sistemlerde ise RIP olarak adladırılır.

Instruction Set

Makine komutları genellikle üç kategori altında incelenebilir; veri transferi, aritmetik/lojik ve akış-kontrolü. x86 mimarisinde çok kullanılan komutlara değineceğiz.

Veri Transferi

  • mov -> Move
    • Register değeri, hafıza alanındaki değer veya sabit bir değeri hafıza alanına ya da registera yazan bir instructiondır.
    • mov ebx, eax -> eax registerındaki değeri ebx registerına atar.
  • push -> Push on Stack
    • Register değeri, hafıza alanındaki değer ya da sabit bir değeri stackin en üstüne atar.
    • push edi -> EDI registerındaki değere stackin en üstüne atar.
  • pop -> Pop from Stack
    • Stackin en üstündeki değeri registerın içine ya da hafıza alanına atar.
    • pop edi -> Stackin en üstündeki değeri EDI registerına atar.
  • lea -> Load Effective Address
    • Hafıza alanındaki değerden ziyade hesaplanan hafıza adresi belirtilen registera yüklenir.
    • lea (EBX, ESI, 8), EDI -> EBX + 8 * ESI sonucunu ESI registerına atar.

Aritmetik / Lojik

  • add -> Addition
    • Kullandığı iki operanddan birincisi hedeftir, ikincisi kaynaktır. Hedefte memory olmadığı sürece ikinci operanddan gelen veri ile birinci operanddaki veriyi toplar ve birinci operandın içine yazar.
    • add EAX, EDX -> EDX’ten aldığı veriyi EAX ile toplayıp EAX’ın içine yazar.
  • sub -> Substraction
    • Çalışma şekli add komutu ile aynıdır.
  • inc, dec -> Increment, Decrement
    • Register veya memory alanındaki değeri 1 artırmak ya da 1 azaltmak için kullanılır.
    • inc EAX -> EAX = EAX + 1
  • imul -> Multiplication
    • İki operandlı ve üç operandlı kullanımı vardır. İki operandlı kullanımda iki operandın içeriğinin çarpımı birinci operanda kaydedilir. Üç operandlı kullanımda son iki operandın çarpımı ilk operanda yazılır.
    • imul EAX, [EBX] -> EBX hafıza alanındaki değer ile EAX çarpılıp EAX’a yazılır.
    • imul EAX, [EBX], 25 -> EAX = [EBX] * 25
  • idiv -> Division
    • EDX:EAX -> EDX’in en anlamlı 4 Byte’ı ile EAX’ın en anlamsız 4 Byte’ının birleşiminden oluşan değeri verilen register veya memorydeki değere böler. Bölüm EAX’ta saklanır, kalan ise EDX’te saklanır.
    • idiv EBX
  • and, or, xor -> Bitwise logical and, or, xor
    • İki operand arasında lojik işlemler yapılarak sonuç ilk operanda yazılır.
    • and eax, eax -> EAX registerını sıfırlamak için kullanılır.
  • not -> Bitwise logical not
    • Verilen operandın negatifini alır.
    • not EAX -> EAX = 00000000 -> 11111111
  • shl, shr -> Shift Left and Right
    • Aldığı iki operanddan birincisi kaydırılacak değer olurken ikincisi ne kadar kaydıracağına dair bilgi veren operanddır.
    • shl EAX, 1 -> EAX registerı içindeki değeri 1 Bit sola kaydır.

Akış-Kontrolü

Programın normal akışı EIP registerı sayesinde olur. EIP registerına direkt müdahale mümkün olmasa da akış-kontrolleri ile manipüle etmek mümkündür. Atlamak istediğimiz yerde begin: şeklinde bir etiket olması gerekir.

  • jmp -> Jump
    • Hiç bir kontrol yapmadan direk olarak verilen etikete dallanır.
    • jmp label -> labet etiketine koşulsuz dallan.
  • jcondition -> Conditional Jump
    • Adından da anlaşılacağı üzere dallanma, bir koşula bağlıdır. Bu koşul çoğu zaman bir önceki işlemin sonucuna bağlıdır.
    • je label (jump when equal)
    • jne label (jump when not equal)
    • jz label (jump when last result was zero)
    • jg label (jump when greater than)
    • jge label (jump when greater than or equal to)
    • jl label (jump when less than)
    • jle label (jump when less than or equal to)
  • cmp -> Compare
    • İki operandın karşılaştırılmasını sağlar. Sonuçları bayraklara işler. Kendisinden sonra gelecek olan jcondition’ların olmazsa olmazıdır.
    • cmp 10, EBX
    • jeq loop -> EBX registerının içeriği 10 olduğu takdirde loopa dallanacak.
  • call, ret -> Subroutine Call and Return
    • CALL label -> label etiketine dallanmayı sağlar ama programın kaldığı yeri stackte tutup geri dönmek için RET instructionı kullanılır.

Stack Alanının Kullanımı

  • Stack yüksek adresten düşük adreslere doğru büyür.
  • Stackin çalışması LIFO (Last In First Out) prensibine göre çalışır. Alan tüketimi bu şekilde olur, fakat herhangi bir anda stackin iznimiz olan herhangi bir yerine yazabilir ve okuyabiliriz.
  • Stackin en üst konumunu her zaman ESP registerı tutar. Stackte değişiklik yapmak için PUSH ve POP fonksiyonlarının haricinde ESP değerinin ADD ve SUB instructionları yardımıyla artırılıp azaltılması mümkündür. Programda ASLR etkin ise her yüklemede farklı adresten başlanır.
  • CALL instructionı çağrılmadan önce gerekli parametreler sağdan sola işlenecek şekilde stacke PUSH edilir. Ardından fonksiyon çağrısı yapılarak parametreler stackten okunarak fonksiyona geçirilir.
  • CALL instructionı çağrıldıktan sonra stackin en üstüne RETURN ifadesi yazılır. RET fonksiyonu bu değerden yola çıkarak programın son kaldığı yeri EIP registerına yazar. Bu şekilde program kaldığı yerden devam edebilir.
  • Bir önceki fonksiyonun frame pointerı -> Herhangi bir fonksiyona dallandıktan sonra ilk iş bir önceki fonksiyonun frame pointerını stacke yazmaktır. Bu sayede geri dönmeden önce bu değer stackten geri okuyarak önceki fonksiyondaki frame pointerı tekrar kullanabileceğiz.

x86 - Uygulama

x86 mimarisi ile alakalı öğrendiğimiz yeni şeyleri uygulamak ve dinamik analize devam edebilmek için aşağıda kodları bulunan küçük bir uygulama üzerinden incelemelere devam edeceğiz. Kodları Dev-C++ üzerinde derledim. Bazı assembly kodları farklılık gösterebilir. Temelde aynı işlemleri yapan fonksiyonlar olup işlemci düzeyinde iyileştirmelere sahip olabilir. Programımızı inno.exe adıyla derleyip linkliyoruz.

inno00

inno.exe dosyamız oluştuktan sonra Immunity üzerinden analize başlamadan önce import edilen kütüphane ve fonksiyonlarla kafamızı ağrıtmadan programımızın main() fonksiyonuna gitmemiz gerekiyor. Bunun için IDA’yı açıp main() fonskiyonunun hafızaya alındıktan sonraki adresine ulaşıyoruz. ASLR desteği olmadığı için programımız main elemanını burada gözüken Virtual Adrese yerleştirecektir. Adresi aldığımıza göre devam edelim.

inno01

Immunity programını açtıktan hemen sonra programın aşağısında bulunan komut satırına b 401516 yazarak main fonksiyonun program hafızaya yüklendikten sonraki konumuna yazılımsal bir breakpoint koymuş olduk.

inno02

Koymuş olduğumuz breakpointin doğruluğunu test etmek amacıyla programın yukarısında bulunan konsoldan b‘ye basarak önceden koyulmuş olan breakpointleri inceliyoruz. Bunlardan ilki Immunity tarafından otomatik olarak koyuluyor. Bizim koymuş olduğumuz breakpoint PUSH EBP koduna karşılık geliyor. Bu ifade yeni bir fonskiyona dallandığımızda çalıştırılan ilk komutlardandır.

inno03

F9‘a iki kez bastıktan sonra koymuş olduğumuz breapointe geldik.

inno04

Şimdi kodları anlamak adına F8 ile adım adım ilerliyoruz. Önceki fonksiyondan kalan (EBP) Stack Base Pointer’ı Stack’e gönderiyoruz. Bu ve bundan sonraki bir kaç adım function prologue diye geçiyor. Program yeni bir CALL çağrısı yaptığında kaldığı yeri unutmamak için bu tarz bir işlem yapıyor.

inno05

Function prologue’un sonraki adımı da Stack Pointer’ın main fonksiyonunun Base Pointer’ı olarak atanması adımıdır.

inno06

Sonraki adımda Stack Pointer FFFFFFF0 ile AND işlemine sokularak Stackte 8 Byte’lık bir alan ayrılmıştır.

inno07

SUB ESP,20 komutu ile Stackte 32 Bytelık bir alan ayrılmıştır. Stackte ayrılan bu alan bizim programımızdan ziyade derleyicinin çalıştırdığı bir sonraki CALL çağrısı için ayrılmıştır.

inno08

CALL çağrısına dallanmadan geçmek için F8‘e basarak bir sonraki komuta geçiyoruz. MOV DWORD PTR SS:[ESP+1C],5 komutu, tanımlamış olduğumuz ilk x değişkenini Stacke yazmamızı sağlıyor. ESP+1C = 28FEBC‘ye denk geldiği için 28FEBC adresine 5 değerini hexadecimal olarak basıyoruz.

inno09

Ardından aynı işlemi y değişkeni için de yapıyoruz. Bu sefer ESP+18 = 28FEB8 adresine yani ilk değişkenden sonraki 5. Byte’tan itibaren yazıyoruz. Bu durum C’de bir integer değişkenin 4 Byte’lık alanda saklandığını gösteriyor. Bir önceki integerı kaydederken farketmemiş olsak da buradaki değerin hexadecimal olarak kaydedildiği aşikar.

inno10

ESP+18 konumundan 0000000A değerini alarak bunu EAX registerına yazdı. Bir sonraki CALL çağrısı için hazırık yapılıyor sanki.

inno11

EAX registerı içindeki değeri ESP+4 konumuna yazdı.

inno12

Aynı işlemi 00000005 değeri için de tekrarlayıp kendisini ESP registerının gösterdiği adrese yazdı. Şimdiki çağrılan fonksiyonun aslında hangi fonskiyon olduğunu biliyoruz ama içine dallanıp neler olup bittiğini incelemek lazım.

inno13

Yapılan CALL çağrısına F7‘ye basarak dallandıktan sonra Stacke yazılan ifadeye dikkat edelim. Bize vermek istediği mesaj kabaca şu; “inno.00401500 fonksiyonuna dallandın. Geri dönerken gideceğin adres inno.00401548’dir.”. Değişkenlerimiz hazır. Topla() fonksiyonuna da dallandık. Şimdi fonksiyonun içinde geçen toplama işleminin nasıl olduğunu bi inceleyelim.

inno14

İlk önce kaldığımız yeri unutmamak adına EBP registerını Stacke gönderelim.

inno15

Stack Pointer’ı Base Pointer’a eşitleyelim ki fonksiyonun içinde istediğimiz işlemleri gerçekleştirebilelim.

inno16

Dev-C++ üzerinde hafıza yönetimi çok iyi olmadığı için gereğinden fazla hafıza alanı kullanmaktan çekinmiyor. Stackten 16 Byte’lık bir alan daha istiyor.

inno17

EBP+8 = 28FEA0 adresinden ilk değişkenimizi alıp EDX registerına atıyoruz.

inno18

İkinci değişkenimizi de alıp EAX registerına atıyoruz. Şimdi bunları toplamak lazım.

inno19

ADD EAX,EDX komutu ile EDX registerındaki değeri EAX registerı üzerine ekliyoruz.

inno20

EAX registerı üzerindeki toplam değerimizi EBP-4 = 28FE94 adresine yazıyoruz.

inno21

LEAVE komutu ile fonksiyonumuz için ayrılan 16 Byte’lık alanı boşalttık ve 28FE98 adresindeki değeri (yani önceki fonskiyonda kalmış olduğumuz Stack adresini) EBP registerına yazdık. Geri dönüş için gerekli olan Stackin en üstündeki yazıda görüldüğü gibi bir önceki fonskiyonun adresinin yazılı olduğu satır karşımızda duruyor. Artık geri dönmeye hazırız.

inno22

RETN instructionı ile stackteki son değer pop ediliyor. Yeni ESP registerı değerimiz 28FEA0.

inno23

EAX registerında tutulan toplam değerimiz ESP+14 adresine yazılıyor.

inno24

Hafıza yönetimi konusunda iyileştirme olmadığı için ESP+14 adresindeki veri EAX’a tekrar alınıyor. Printf fonksiyon çağrısına geldiğimizde Stackteki son iki değerin printf fonksiyonu için parametre olarak kullanılacağı bir gerçek. İlk parametremizi bu adımda ESP+4 = 28FEA4 adresine toplamı yazarak elde ediyoruz.

inno25

İkinci parametre olarak da “Toplam = %d” yi yüklemiş olduk.

inno26

Printf fonksiyonumuzu çağırmadan önce programa küçük bir göz atalım. Henüz program ekranında hiçbir şey yazmıyor.

inno27

Bir adım ilerledikten sonra ekrana “Toplam = 15” yazısı geliyor. Aynı anda Stacke bir başka string yazılıyor. Bu string bir sonraki system() fonksiyonu için parametre olarak kullanılacak.

inno28

system() fonksiyonu çağrısına geldiğimizde Stackteki stringin yanına command ifadesi geldi. Bu string system() fonksiyonunun bir parametresiymiş.

inno29

system() fonksiyonunun çağrısının yaptıktan sonra ekranda beliren string, bizden bir tuşa basmamızı istiyor. Herhangi bir tuşa basmadan ilerleyemiyoruz.

inno30

Fonksiyondan çıkmadan önce EAX’a sıfır atıyoruz. Bunun amacı main() fonksiyonunda geri dönüş değeri olarak sıfır göndermemiz. return 0 satırından bunu çok rahat anlayabiliriz.

inno31

LEAVE instructionı ile fonksiyonda kullanmak üzere ayırdığımız Stack alanını geri iade ediyoruz. 28FEC8 adresindeki değeri EBP registerına pop ediyoruz. Main() fonksiyonunda çıkmak için her şey hazır.

inno32

Main() fonksiyonundan çıktıktan sonra da EAX registerında ki değerimizi Stacke gönderip exit fonksiyonumuzu çağırıyoruz. Programımız bu şekilde sonlanmış oluyor.

inno33

all tags