Bilgi Güvenliğinin Türkçesi

Kategoriler

14 Temmuz 2023 Cuma

Tersine mühendislik yöntemiyle malware analiz ve cracking


Software Reverse Engineering (Reversing), tersine mühendislik yaklaşımıyla bir uygulamanın yapıtaşlarını ortaya çıkarmaya yarar. Bu yaklaşım bir programı illegal yolla kullanmak (Cracking) için kullanılabileceği gibi şüpheli bir programın analiz edilip zararlı olup olmadığını veya etkilerinin ne olduğunun araştırılması içinde kullanılır. Bu yazıda Reversing işlemiyle zararlı bir yazılımın yapıtaşları, davranışları ve sisteme nasıl etki edeceği incelenecektir. Örnek uygulama olarak içine zararlı kod enjekte edilmiş ve AV lere yakalanmayan putty programı reversing yöntemiyle incelenecek ve içindeki zararlı kod parçası analiz edilecek.


Tersine mühendislik, bir aygıtın, objenin veya sistemin; yapısının, işlevinin veya çalışmasının, çıkarımcı bir akıl yürütme analiziyle keşfedilmesi işlemidir. Makine veya mekanik alet, elektronik komponent, yazılım programı gibi parçalarına ayrılması ve çalışma prensiplerinin detaylı şekilde analizini içerir. [1] Bu yazıda bir yazılımın tersine mühendisliği (Software Reverse Engineering) anlatılacak ve bu tanımı Reversing olarak kısaltacağız.

Reversing işleminde mantık; Sabit olan sistem üzerinde gerçekleşen farklı yazılımların gösterdiği farklılıkları anlamaktır. İşletim sistemi sabittir değişkenlik göstermez hep aynı işlemleri yaparak ilerler ancak işletim sistemi üzerinde çalışan uygulamalar farklılık gösterir. Eğer biz işletim sisteminin çalışma yapısını çok iyi bilirsek uygulamaların oluşturduğu farklılıkları çok daha rahat gözlemleyebiliriz. Bu yüzden uygulamaların analizi için sisteminin çalışma mantığını oluşturan CPU, RAM, Register gibi yapıları, komut işletme adımlarını ve bu adımlar sırasında bu yapıların konuşurken kullandığı dil olan Assembly yapısına değineceğiz.



İntel CPU’larda Komut işleme diyagramı:




Komut işleme diyagramındaki adımları biraz daha yakından tanıyalım;

1: Komutu Getir (Fetch):

    - Bellekten bir sonraki komutu getir ve yazmaça koy.

    - Program sayacını (program counter – PC) bir sonraki komutu gösterecek şekilde güncelle.

2: Komutu çözümle (Decode):

    - Getirilen komutun tipini ve işlevini belirle.

3: İşlenenleri getir (fetch operands):

    - Komut bellekteki bir sözlüğü kullanıyorsa sözcüğün nereden getirileceğini belirle, sözcüğü getir ve CPU yazmacına (register) koy.

4: Komutu işlet (Execute):

    - Yazmaçlardaki işlenenler üzerinde komutu işlet ve sonucu yazmaça koy.

5: Sonucu sakla (Store output):

    - Çıktı bir bellek işleneniyse belleğe yaz.

6: Bir sonraki komutu işletmek için 1. Adıma git




Fetch the code, program çalıştırıldığında belleğin belirli alanlarına programla ilgili veriler yüklenir, Bilgisayarın mimarisine göre farklı olacak şekilde programda yapılan herhangi bir işlem bellekte programla ilgili bu kısımların aktif olmasını sağlar, burada yapılan işlemle ilgili bölümün aktif olması işlemi “fetch the code” işlemidir.

PC, yazmaç, bellek gibi bir yapıdır, küçük ve çok hızlıdır. İçinde komutların adresleri bulunur. PC birimi, sırada hangi komut işletilecekse o komutla ilgili adresi taşır ve sıradaki komutun getirilmesini sağlar ve decode işlemiyle komut çözülür.

Fetch operands işlemiyle sıradaki komutun nereden alınacağı belirlenir ve oradan alıp CPU nun içindeki ilgili yazmaça (register) yazılır. Daha sonra yazmaçtaki bu komutlar çalıştırılır. Bu işlem sonucunda çıktılar belleğe veya tekrar işleme alınacaksa yazmaça gönderilir.

ALU (Aritmetic Logical Unit), iki sayının toplamı, mantıksal karşılaştırılması gibi işlemler burada yapılır. Veriler işletilmeden önce yazmaçlara yazılır, buradan ALU ya gelen veriler burada işleme tabi tutulur. Çıkan sonuç tekrar işlem görecekse yazmaça veya belleğe kaydedilir.

Bu şekilde döngüsel bir işlem gerçekleşir.

X86 Assembly temel komutlar


Sırasıyla ilk önce komut, yazmaç ve parametre yazılır, her işlemciye ait mimari farklıdır, işlemci bir dökümantasyon sağlar. 

intel işlemciye ait assembly program yapısını daha iyi anlamak için aşağıdaki örnekleri inceleyebiliriz.

Örnek:

mov eax,100h ;EAX=100h → aex a 100h ata

add eax,400h ;EAX=500h → eax a 400h ekle

sub eax,200h ;EAX=300h → eax tan 200h çıkar


Aşağıda reversing sırasında işimize yarayacak komutlar kısa örnekle açıklanmıştır.

1- MOV: 
Bu komutla bir yazmaçtan bir yazmaca kopyalama yapılabileceği gibi bir yazmaca sabit bir değer de yazılabilir.

Örnek:

mov ah,5

mov al,bh

Birincisinde “ah” yazmacına 5 yazılır. İkincisinde ise “bh” yazmacındaki veri “al” yazmacına kopyalanır. Mov komutuyla yalnızca eşit kapasitedeki yazmaçlar arasında kopyalama yapılabilir. Yani sonu x ile bitenler arasında veya sonu h veya l ile bitenler arasında kopyalama yapılabilir.

2- INC: 
Bir yazmaçtaki değeri bir artırır.

Örnek:

inc al

Burada al'nin önceki değerini 5 sayarsak artık al'nin değeri 6 olur.

3- MUL: 
Belirtilen yazmaçtaki değerle al yazmacındaki değeri çarpıp sonucu ax yazmacına koyar.

Örnek:

mul cl

Burada al yazmacındaki değerle cl yazmacındaki değer çarpılıp sonuç ax yazmacına konur.

4- ADD: 
Herhangi bir yazmaçtaki değerle herhangi sabit bir değer veya yazmaçtaki değeri toplayıp sonuç ilk belirtilen yazmaca koyulur.

Örnek:

add al,1

add al,cl

Birincisinde al ile 1 toplanıp sonuç al'ye atanır. Yani sonuçta al bir artırılmış olur. İkincisinde ise al ile cl toplanıp sonuç al'ye atanır. Yani sonuçta al'ye cl eklenmiş olur. Add komutu argüman olarak yalnızca eşit kapasitedeki yazmaçları alabilir. Örneğin aşağıdaki komut hatalıdır.

add ax,cl

5- SUB: 
Herhangi bir yazmaçtan sabit bir değeri ya da başka bir yazmaçtaki değeri çıkarır.

Örnek:

sub al,10

sub ax,cx

Birincisinde al yazmacındaki değer 10 eksilir. İkincisinde de ax yazmacındaki değer cx'teki değer kadar eksilir. Sub komutu da tıpkı add gibi eşit kapasiteli yazmaçlarla işlem yapar.

6- LOOP: 
Komutun argümanındaki ofset adresi ile loop komutunun bulunduğu satır arasında cx'in değeri kadar gidip gelinir. Yani bir döngü kurulmuş olur.

Örnek:

14F7:100 mov cx,5

14F7:103 inc al

14F7:105 add dl,al

14F7:107 loop 103

14F7:109 int 20

Burada 5 kere 103. ofset ile 107. ofset arasında gidip gelinir. Bütün yazmaçların ilk değerlerini 0 sayarsak sonuçta al'nin değeri 5, dl'nin değeri de f olur. Burada şunu gözden kaçırmamanızı öneriyorum: loop komutunun kapsama alanına giren komutlar henüz loop komutunun bulunduğu satıra gelinmeden doğal olarak bir kez çalıştırılır. Bu çalıştırılma bu bahsettiğimiz gidip gelmeye dâhildir. Öbür türlü söz konusu satırlar toplam 6 kere işletilmiş olurdu.

7- CMP: 
İki yazmaçtaki değeri ya da bir yazmaçtaki değerle sabit bir değeri kıyaslar. Tek başına bir işe yaramaz.

Örnek:

14F7:100 cmp al,bl

14F7:102 cmp bl,5

14F7:104 cmp 5,bl

14F7:106 cmp ax,bl

Bu komutlardan ilk ikisi çalışacak. Son ikisiyse çalışmayacak. Çünkü üçüncüsünde ilk argüman sabit bir değer. Sonuncusunda da farklı boyutlardaki yazmaçlar kıyaslanmış.

8- JG, JL, JE, JGE, JLE: 
Kendisinden hemen önce gelen cmp komutuna göre programın akışını belirli bir ofsete yönlendirirler.

Örnek:

14F7:100 cmp al,bl

14F7:102 jl 10e

Burada eğer al'deki değer bl'den küçükse akış 10e ofsetine yönledirilecektir. Değilse program normal şekilde akışına devam edecektir. Tahmin edebileceğiniz gibi jl küçüktür anlamına geliyor. Mantığı bununla tamamen aynı olan farklı komutlar da vardır. Bunlar

jg → büyüktür.

jl → küçüktür.

je → eşittir.

jge → büyük eşittir.

jle → küçük eşittir.

9- JMP: 
Belirli bir ofsete atlamaya yarar.

Örnek:

14F7:100 jmp 110

Burada programın akışı 110. ofsete yönlendirilir.

Küçük bir kodun

10- CALL: Komutu ilgili bölümden çağırır




GDB ile örnek uygulama




Yalnızca iki değişkenden oluşan örnek bir kodun assembly dilinde nasıl görüntüleneceğini inceleyelim. Komut işleme diyagramını yukardaki kod üzerinden düşünecek olursak; program iki değişkeni içeriyor, bu iki değişken bellekte farklı hücrelere yerleşecek. Fetch ile ilgili bölümden değer çağırılacak ve pc ile counter miktarı arttırılacak, Decode işlemi ile komut çözümlenecek ve ALU ya gönderilecek, aynı anda yazmaçlar bellekteki ilgili bölümü okuyacak ve ALU ya iletecek. ALU biriminde toplanan değerler execute edilerek komut işletilecek ve bir çıktı oluşacak. Daha sonra bu çıktı belleğe yada tekrar işlenecekse yazmaç bölümüne yazılacak. Tüm bu işlemlerin mantığını anlarsak programın debug işlemini daha rahat okuyabiliriz.



Programı derledikten sonra GDB programında disassembe main komutuyla kodun main kısmının bellekte adreslendiği bölümler ve bu bölümler için yapılan mov, sub, call gibi işlemleri görüyoruz. Burada dikkat etmek gereken 11. Satırda callc parametresiyle printf komutu bellekten çağırılıyor, buradan anlıyoruz ki değişkenler bu satırdan sonra ekrana yazılıyor.

Debug işlemi yapılırken break point koyarsak program bu satıra kadar çalışır ve durur böylece buraya kadar ki alınan parametreleri daha rahat inceleyebiliriz. Örnek olarak yazdığımız programcıkta break main komutuyla main kısmına break koyuyoruz, program buraya kadar çalışıyor ve duruyor ve bize var1 değerini gösteriyor.

Print var1 dediğimizde bize “0” değerini gösteriyor, buradan var1 değerinin henüz yazmaça yazılmadığını anlıyoruz, next dediğimizde var2 değerini gösteriyor, bu aşamada print var1 dediğimizde bize var1 değeri olarak 3434 rakamını veriyor, böylece var1 değerinin yazmaça yazıldığını anlıyoruz, next ile ilerlediğimizde program tamamlanıyor ve istediğimiz değerleri gösteriyor.



Örneğin, print &var1 komutuyla birinci değişkenimizin hangi adreste olduğunu görebiliyoruz. Bu komut modifiye etmek yada görüntülemek isteyeceğimiz kısmın bellekteki tam yerini bulmak için bize yardımcı olacaktır.

OLLYDBG ile dinamik malware analiz örnek uygulama:

Öncelikle msfvenom programıyla putty programının içine belirlediğimiz ip adresine reverse channel açacak bir kodu yerleştiriyoruz ve putty programını yeni haliyle derliyoruz.



Oluşturduğumuz putty programını Ollydbg ile açıp şüpheli kodların bellekte hangi bölümde adreslendiğini bulmaya çalışıyoruz, ilk başta samanlıkta iğne aramak gibi olsada doğru ipuçları yakaladıktan sonra doğru aramalar yaparak sonuca ulaşabiliriz.

Netstat ile bağlantıları izlediğimizde 192.168.93.128 ip sine bağlantı kurulduğunu görüyoruz, ancak ip adresiyle ilgili bölüm gözükmüyor, bunun yerine programın sitringlerini log dosyasına yazdıktan sonra, log dosyasında zararlı bir koda ait bir ipucu bulmaya çalışıyoruz.



Username isimli bir string bulduk, bu bölümün nerde adreslendiğini ollydbg programıyla bulmak gerekiyor.



Username stringinin bulunduğu kısımda breakpoint koyma, sırayla çalıştırma gibi işlemler yaparak bizim için gerekli olan analizleri yapabiliriz. Bu sayede virüsün nelere etki ettiğini anlayıp önlemlerimizi alabiliriz.





Kaynaklar:

Hiç yorum yok:

Yorum Gönder