3. İÇ İÇE DÖNGÜLER
• İç içe döngülerin analizini kolaylaştırmak için renklendirme ve
gruplama yöntemlerinden faydalanabiliriz.
• Gruplama ve Analiz Metodu Önerisi
Kalın mavi çizgiyi bul (döngü çizgisi)
Kalın çizginin ok başından geriye doğru takip et
o Eğer bunu yaparken bir başka kalın mavi çizgiye
rastlarsan önce iç döngüyü grupla
Döngüyü grupla
Tüm döngüler gruplandıktan sonra fonksiyonun başından
itibaren analiz etmeye başla
o Pseudo Code ile tespitleri not et
o Tekrar grupla ve analize devam et
ÖN BİLGİ
4. VERİ YAPILARI [STRUCTURES]
• Assembly kodu içinde belli bir adresin offset'ine erişim
gözlendiğinde bu bir veri yapısı [structure] kullanımına işaret
olabilir.
• Bu durumda okunabilirliği artırmak için IDA'nın veri yapıları
imkanından faydalanabiliriz.
• İç içe döngüler ile birlikte bu konuyla ilgili uygulama örneğini
gerçekleştireceğiz.
ÖN BİLGİ
5. VERİ YAPILARI [STRUCTURES]
ÖN BİLGİ Veri yapılarının
anlamlandırılmasıyla ilgili
aşağıdaki kod parçasının
Assembly'ye dönüşümü ve veri
yapısını IDA'da modelledikten
sonra bunu koda yansıtma
örneğini görebilirsiniz.
6. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
#include <stdio.h>
typedef struct veriYapisi
{
int rakam;
int faktoriyel;
struct veriYapisi *sonraki;
} bilesen;
bilesen bilesen1 = { 5, 0, NULL }; bilesen bilesen2 = { 7, 0, &bilesen1 }; bilesen bilesen3 = { 3, 0,
&bilesen2 };
bilesen bilesen4 = { 4, 0, &bilesen3 }; bilesen bilesen5 = { 2, 0, &bilesen4 }; bilesen bilesen6 = { 8, 0,
&bilesen5 };
int main()
{
int i,j;
bilesen *cursor = &bilesen6;
int rakam;
for (i = 1; i<=6; i++)
{
cursor->faktoriyel = 1;
for (j = 1; j <= cursor->rakam; j++)
{
cursor-
>faktoriyel = cursor->faktoriyel * j;
}
cursor = cursor->sonraki;
}
cursor = &bilesen6;
for (i = 1; i<=6; i++)
{
printf("%d faktoriyel = %dn", cursor-
>rakam, cursor->faktoriyel);
cursor = cursor->sonraki;
}
getchar();
return 0;
}
Örnek olarak inceleyeceğimiz kodumuzda içinde [2] INTEGER ve bir [STRUCT
POINTER] barındıran bir veri yapısını kullanacağız.
Bu veri yapılarından oluşmuş bir dizi veriyi [GLOBAL] değişken olarak tanımladık
ve [OFFSET]'leri bir zincir liste oluşturacak biçimde ayarladık.
7. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Bir [CURSOR] kullanarak zincir liste
[LINKED LIST] üzerinden gezerken
[faktoriyel] alanlarına [rakam]
verisinin faktoriyelini hesaplayarak
yazıyoruz.
8. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Derlenmiş kodumuzu
IDA ile açalım.
9. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Kodu sadeleştirmek için döngüleri
gruplama yoluna gideceğiz.
Bunun için daha önce de belirlediğimiz
gibi önce iç içe döngülerde en iç
döngülere yönlenmemiz lazım.
Genel yaklaşım olarak fonksiyonun
sonuna en yakın döngüden
gruplamaya başlayabiliriz. Kodun
sonundaki döngüyü oluşturan 3 bloğu
[CTRL] tuşunu basılı tutarak seçelim.
10. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Seçtiğimiz blokları aynı renge boyamak için
sağ klikleyerek [Set node color] seçeneğini
seçelim.
11. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Seçtiğimiz blokları renklendirelim.
12. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Blokları gruplayıp tek bir kutu
şeklinde ifade etmek için sağ
klikleyip [Group nodes] seçeneğini
seçelim.
13. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Grubumuza [DONGU_1] adını
verebiliriz.
14. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
[DONGU_1] grubunun toplanmış
hali soldaki gibi olacaktır.
15. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Kalan bölümdeki kodları
incelediğimizde iç içe 2 döngü
olduğunu görebiliriz. Bunlardan
içte olanını oluşturan blokları
seçip renklendirelim.
16. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Yeni grubumuza [DONGU_2] adını
verebiliriz.
17. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Bu grubu da toplayarak kodu
sadeleştirmeye devam edelim.
18. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Son olarak dıştaki döngüyü
oluşturan blokları seçelim ve
renklendirelim.
19. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Son grubumuzu da [DONGU_3]
olarak adlandırıp toplayalım.
Görüleceği gibi kod analiz için
oldukça sadeleşti, şimdi sıra kod
bloklarının oluşturduğu grupları
adım adım açarak
anlamlandırmakta.
20. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Demo uygulamamızı diğer örneklerde olduğu gibi detaylı olarak
incelemeyeceğiz. Ancak [var_C] değişkenine atanan offset adresine çift
tıklayarak bu alanda bulunan verilere göz atalım.
21. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
IDA için bu [.data] section'ında bulunan bu alanın özel bir anlamı yok, o nedenle
BYTE BYTE görüntülüyor veriyi.
22. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Diyelimki detaylı bir analiz yaptık ve bu veri yapısının [3] adet DWORD'den
oluştuğu kanaatine vardık. Bu durumda veri alanları üzerinde [d] tuşuna [3] defa
basarak veriyi [DWORD – dd] formatına çevirebiliriz.
23. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Oluşturduğumuz ilk veri yapısı bloğunun son alanındaki offset bilgisinden
faydalanarak zincir listeyi oluşturan diğer veri yapısı alanlarını da ortaya
çıkarabiliriz.
24. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Detaylı kod analizi ve veri yapılarını inceleyerek ortaya çıkardığımız bilgileri koda
yansıtabilmek ve kodun okunabilirliğini artırabilmek için önce IDA'ya bir veri
yapısı tanımı yapmamız gerekir.
25. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
[INSERT] tuşuna basarak yeni oluşturacağımız veri yapısının
adını [VERI_YAPISI] verelim.
26. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
[d] tuşuna basarak veri yapısını oluşturan alanları
tanımlayalım ve [d] tuşuna 2 defa daha basarak bu veri
alanlarının büyüklüklerini [DWORD]'e dönüştürelim.
27. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Kodu detaylı olarak
incelediğimizi, [var_C] lokal
değişkeninde bulunan veri
yapısı adresinin önce [EAX]
register'ına aktarıldığını ve
bunun [+4] byte offset'inde de
tespit ettiğimiz bir alanın var
olduğunu bildiğimizi
varsayalım.
28. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
[4] rakamını tıkladıktan
sonra sağ klikleyerek
context menü'yü açalım.
Burada tanımladığımız
veri yapısını seçerek
kodun anlaşılabilirliğini
artıralım.
29. ÖN BİLGİ İÇ İÇE DÖNGÜLER VE
VERİ YAPILARI
DEMO
Kod yanda görüldüğü gibi
bir veri yapısının içindeki
bir bileşene referans
verildiğini belirtecek hale
gelerek anlaşılırlığı
artacaktır.
31. 6. AŞAMA Bu fonksiyon diğerlerine nazaran
oldukça kompleks ve çok sayıda
döngü içeriyor.
Bu döngülerin bazıları da iç içe
[nested] döngüler gibi görünüyor.
NOT: IDA'nın döngüleri daha belirgin
hale getirmek için KOYU MAVİ
çizgiler kullandığından daha önce
bahsetmiştik.
32. 6. AŞAMA
Karmaşık bir kodla karşı karşıya olduğumuzda, ki iç içe döngüler içeren bir kod
karmaşık olabilir, belli kod bölümlerini renklendirerek diğer bölümlerden
ayrıştırmak isteyebiliriz.
İç içe döngüleri daha anlaşılabilir hale getirmek için yapılabilecek bir diğer
uygulama döngüleri oluşturan kod bölümlerini gruplamak ve bu grupları
kapatmak olabilir
33. 6. AŞAMA
İç içe döngüleri kapatmaya en içteki döngülerden başlamalıyız. Bir genelleme
yapamayız ancak en içteki döngülerin sıklıkla bulundukları bölüm fonksiyondan
çıkış noktasına yakın olan alanlardır.
Öyleyse [RETN] instruction'ına yakın olan döngüye bir göz atabilir ve bu kod
alanlarını renklendirebiliriz.
34. 6. AŞAMA
En içteki döngüyü oluşturan kod bloklarını
renklendirelim.
Renklendirmeye [CMP] instruction'ını barındıran
test bölümünden başlayıp sayacın artırıldığı kod
bölümüne kadar devam edelim.
35. 6. AŞAMA
Döngümüze test bloğundan önce gelen "initialization" bloğunu da ekleyelim. Bu
kod karmaşasında bu bölümü bulmak zor olsa da kod akış kollarını takip ederek
ve sayaç olarak kullanılan lokal değişken veya register'ı highlight ederek bu bloğu
da bulabiliriz.
36. 6. AŞAMA
Döngü kod bloklarını gruplamadan önce [CTRL] tuşuna basılı tutarak blokları
seçelim. Blokların başlık bölümlerinin griye dönmesi seçme işleminin başarılı
olduğunu gösterir.
37. 6. AŞAMA
Seçilen tüm kod bloklarını gruplamadan önce [SAĞ KLİK]leyerek context
menü'nün açılmasını sağlayalım ve [Group nodes] seçeneğini seçelim.
38. 6. AŞAMA
Oluşturacağımız gruba bir isim vermemiz
gerekiyor. Döngüler üzerinde çalıştığımız için
bu grubu [DONGU_1] olarak isimlendirebiliriz.
39. 6. AŞAMA
Bu kod bloğunu grupladığımızda kodun
anlaşılabilirliğine katkıda bulunmuş olduk.
40. 6. AŞAMA İç içe döngüleri basitleştirmek için içten dışa doğru
bir yaklaşımın doğru olacağını söylemiştik. Şimdi
[DONGU_1]'in bir dışındaki döngüyü gruplamaya
çalışalım.
Renklendirmeyi kullanmak blokları ayrıştırmak için
duyduğumuz ihtiyaca bağlı, ancak biz tüm döngüleri
netleştirmek için boyamaya devam edebiliriz.
41. 6. AŞAMA
Yine [CTRL] tuşunu basılı tutarken grubun parçası
olan kutuları seçebiliriz ve sağ klik ile gelen Context
Menü'den faydalanarak bu kutuları [node]
gruplayabiliriz.
43. 6. AŞAMA
2. döngüyü de gruplandırdıktan sonra bir üst
döngünün daha olduğunu görebiliriz.
Ancak sol taraftaki kod bloklarına baktığımızda bu
bacakta da iç içe döngüler var gibi görünüyor.
Önce iç döngüleri gruplama prensibimiz gereği üst
seviyedeki döngüye geçmeden önce [DONGU_2]
seviyesindeki diğer alanları da sadeleştirmemizde
fayda var.
45. 6. AŞAMA Bu grubu da daha kolay ayrıştırmak için farklı
bir renge boyayalım.
Döngüleri gruplarken "initialization"
kutularını da dahil ettiğimize dikkat edin.
47. 6. AŞAMA
Döngüleri grupladıkça kodun nasıl daha kolay
anlaşılabilir hale geldiğine dikkat edin.
Elbette hatalı grupladığımız veya yanlış
yerden başladığımız durumlar olabilir, ancak
bu grupları tekrar oluşturma imkanımız var.
[DONGU_3]'ü grupladıktan sonra bu grubu
da içine alan bir başka döngü olduğunu
görebiliyoruz.
50. 6. AŞAMA Yeni bir iç döngüyü belirleyelim.
Döngüleri sunum sayfasında
incelemek daha zor, IDA
ekranında daha rahat biçimde
analizimizi gerçekleştirebiliriz.
56. 6. AŞAMA Tüm döngüleri gruplandırdıktan sonra kod çok
basit bir hale geldi.
Şimdi yeri geldikçe grupları açarak nasıl bir
algoritma barındırdıklarını incelemeye geçebiliriz.
58. 6. AŞAMA
Bu offset'e geldiğimizde anlamlı
bir bilgi göremiyoruz, en azından
bize anlam ifade edebilecek
ASCII bir veri barındırmıyor bu
alan.
59. 6. AŞAMA
Bu offset'in ne olduğunu
bilmesek de [var_C] lokal
değişkenine erişilen diğer
noktalarda bu değişkenin bir
adres bilgisi barındırdığını
belirtmemizde fayda var.
61. 6. AŞAMA
Bu bloktaki çağrılan fonksiyonlara göz attığımızda daha önceden analiz
ettiğimiz bir fonksiyona rastlıyoruz.
Fonksiyonun işlevini hatırlamak için tekrar [ALTI_INT_OKU] fonksiyonunu
ziyaret edelim.
62. 6. AŞAMA
Bu fonksiyon kendisine verilen
bir string'in içinden okuduğu 6
INTEGER değeri 2. parametre
olarak aldığı adres ve
sonrasındaki alanlara yazıyordu.
65. 6. AŞAMA
Fonksiyon parametresi ile verilen
değerin [ALTI_INT_OKU] fonksiyonu
çağrılmadan hemen önce atandığı
lokal değişkenin adını
[L_PRM_GIRDI] olarak
düzenleyebiliriz. Bu alan aynı
zamanda [ALTI_INT_OKU]
fonksiyonuna verilen 1. parametre.
66. 6. AŞAMA
[ALTI_INT_OKU] fonksiyonu ile ilgili
2 parametre aldığı ve 2.
parametrenin de okunan 6
INTEGER'ın yazılması için
kullanılacağını biliyoruz. Bu nedenle
stack'te bu adresin yazılacağı alana
[L_PTR_INT_GECICI] adını
verebiliriz.
67. 6. AŞAMA
Stack'te [ALTI_INT_OKU]
fonksiyonuna verilen adresin kendisi
ise [var_38] alanının adresi. Bu alanı
da [L_PTR_INT] olarak
adlandırabiliriz. Bu adres okunan
INTEGER değerlerinin arka arkaya
yazılacağı alanın başlangıç adresi.
69. 6. AŞAMA
Bu alanı anlamlandırmak için
adını [VAR_I] olarak
değiştirelim.
Burada sayacın [0] ile initialize
edildiğini, [5] ve daha altı
olduğu durumlarda (yani <6)
döngüde kalmaya devam
edildiğini görebiliyoruz.
70. 6. AŞAMA
Grubun en altındaki blokta
sayacımızın "1" artırıldığını
[INC] görebiliyoruz.
71. 6. AŞAMA
PSEUDO CODE (yani kod benzeri deyimlerle ifade etme) yaklaşımı ile analiz
ettiğimiz algoritmanın netleştirilmesi yöntemini daha önce de kullanmıştık.
Bu yönteme bu fonksiyon gibi çok karmaşık fonksiyonlarda daha da fazla
ihtiyacımız var. O nedenle algoritmanın analiz ettiğimiz bölümleri için bu
yöntemi kullanacağız.
PSEUDO CODE
int girdi[6]
for(VAR_I=0;VAR_I<6;VAR_I++)
{
...
}
72. 6. AŞAMA
Eklediğimiz yorum satırında da
görüldüğü gibi okunan
INTEGER'lar [0] veya daha
küçük olamazlar.
73. 6. AŞAMA
Hemen aşağıdaki blokta da
okunan INTEGER değerlerinin
[6]'dan büyük olamayacaklarını
görebiliyorz.
74. 6. AŞAMA
PSEUDO CODE
int girdi[6]
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
76. 6. AŞAMA
[DONGU_5]'in sayacı olarak
[var_60] lokal değişkeninin
kullanıldığını görüyoruz.
[var_60] önce "[VAR_I]+1"
ile initialize ediliyor. [6]'dan
küçük olduğu müddetçe de
iç döngüde kalmaya devam
ediyor uygulama.
77. 6. AŞAMA
Kodu daha okunur hale
getirebilmek için [var_60]
değişkeninin adını [VAR_J]
olarak değiştirelim.
78. 6. AŞAMA
Daha önce de bahsettiğimiz [VAR_J] değişkeninin bir
üst döngünün sayacı olan [VAR_I] sayacına [1]
eklenerek initialize edildiği kod bölümü.
79. 6. AŞAMA
Yine daha önce bahsettiğimiz [VAR_J] değişkeninin [5]'in
üzerine çıkması halinde döngüden çıktığı kod bölümü.
80. 6. AŞAMA
Dıştaki döngü [6] INTEGER değerinin üzerinden dönerken, içteki döngü de
incelenmekte olan INTEGER değerinden sonra gelen değerler üzerinden dönüyor.
Eğer bu değerler birbirleri ile aynı ise oyun sonlanıyor. Dolayısıyla girilen [6]
INTEGER'ın birbirlerinden farklı olmak zorunda olduğunu söyleyebiliriz.
87. 6. AŞAMA
Şimdi [DONGU_4]'ü
[Ungroup] ederek
incelemeye başlayabiliriz.
Derleyicimiz sayaç olarak
yine [VAR_I]'yı kullanmayı
uygun görmüş.
Bu döngüde de benzer
biçimde [VAR_I] [0] ile
initialize ediliyor , sayaç her
döngüde [1] artırılıyor ve
indeks [6]'dan küçük ise
döngüde kalmaya devam
ediliyor.
89. 6. AŞAMA
Bu haliyle döngünün son
bloğunu
anlamlandıramıyoruz,
[var_10] ve [var_58] lokal
değişkenlerinin ne için
kullanıldığına dair bir
fikrimiz yok.
O yüzden iç döngü olan
[DONGU_3] döngüsünü
incelemeye başlayabiliriz.
91. 6. AŞAMA
Tekrar hatırlamak için
[BILINMEYEN_OFFSET] lokal
değişkenine ilgili adresin ilk
atandığı fonksiyonun
başlarındaki kod bölümüne
göz atabiliriz.
92. 6. AŞAMA
[0x40304C] adresini tekrar ziyaret
edelim.
Daha önce de bu alanın byte'lara
bölünmüş halde pek bir anlam
ifade etmediğini
değerlendirmiştik.
Ancak aşağıya ve yukarıya doğru
offset adreslerine benzer veriler
olduğunu görebiliyoruz.
94. 6. AŞAMA
Kodun alt bölümlerinde [var_10] lokal değişkeninde bulunan adresin [+8]
offset'inde bulunan bir DWORD değerin [EAX] register'ına atandığını ve
bunun da tekrar [var_10] lokal değişkenine atandığını görüyoruz. Bu
durumda [BILINMEYEN_OFFSET] adresindeki verileri DWORD değerlerine
dönüştürmeyi düşünebiliriz.
95. 6. AŞAMA
Veri tipinin üzerine tıkladıktan
sonra [d] tuşuna [2] defa
basarak byte'ları DOUBLE
WORD'lere [dd]
dönüştürebiliriz.
Art arda 3 DWORD verisi
oluşturduğumuzda son
DWORD'ün bir başka offset'in
adresini içerdiğini
görebiliyoruz.
96. 6. AŞAMA
Şimdi aynı işlemi ortaya
çıkardığımız offset olan
[0x403040] adresine atlayarak
gerçekleştirelim.
97. 6. AŞAMA
Bu adresteki 3. DWORD değeri
de [0x403034] değerini
içeriyor. Bu da bir adrese
benziyor.
Aynı işlemi bu adreste de
gerçekleştirelim.
98. 6. AŞAMA
Aynı işlemi gerçekleştirdiğimiz
6. alanda son DWORD değeri
[0] olarak karşımıza çıkıyor.
Bu noktadan sonra tekrar aynı
işlemi uygulayamıyoruz.
99. 6. AŞAMA
Son incelemelerimizden sonra bir veri yapısıyla [STRUCTURE] karşı karşıya
olduğumuzu söyleyebiliriz.
IDA PRO, kodu daha anlaşılır hale getirebilmek için veri yapılarını ifade
edebileceğimiz bir imkan sağlıyor.
Bu imkanı kullanabilmek için [Structures] penceresine geçelim.
100. 6. AŞAMA
Yeni bir veri yapısı eklemek için [INSERT] tuşuna basalım.
101. 6. AŞAMA
Yeni bir veri yapımızın adını [VERI_YAPISI] olarak verelim.
103. 6. AŞAMA
Açıkçası veri yapısının alanlarını henüz net olarak anlayamadık. Ancak
her bir veri yapısının 3 adet [DWORD] içerdiğini düşünüyoruz.
Bir [DATA veri yapısı bileşeni oluşturmak için [D] tuşuna basalım.
104. 6. AŞAMA
[DATA] alanının büyüklüğünün [DWORD] olduğunu belirtmek için veri alanını [D]
tuşu ile oluşturduktan sonra 2 defa [d] tuşuna basalım ve bu alanı [dd] olarak
belirleyelim.
Daha sonra ne amaçla kullanıldığını henüz bilmediğimiz bu alana üzerine
tıkladıktan ve [N] tuşuna bastıktan sonra [ALAN_1] adını verelim.
105. 6. AŞAMA
Yeni veri alanlarını eklemek için ALTTAKİ [VERI_YAPISI] adına tıkladıktan sonra [D]
tuşuna basalım. Bu şekilde 3 [DWORD] alan tanımladıktan sonra son alana
[NEXT_OFFSET] adını verelim. Çünkü bu alanın bir offset adresi barındırdığından
eminiz ve son veri yapısının 3. DWORD alanında [0] yani [NULL] değerinin
bulunması da karşımızda bir zincir liste [LINKED LIST] olma ihtimalini güçlendirdi.
106. 6. AŞAMA
Bu offset'in veri yapısının bir alanı olduğunu belirtmek
için [8] rakamına tıkladıktan sonra sağ klikleyerek
context menü'sünü açalım ve
[VERI_YAPISI.NEST_OFFSET] seçeneğini tıklayalım.
107. 6. AŞAMA
Bu işlemden sonra kod daha
okunaklı hale gelecek ve
aktarılan verinin bir veri
yapısının bir bileşeni olduğunu
daha rahat anlayabileceğiz.
108. 6. AŞAMA
Bu noktada ilk offset'in bir
zincir listenin ilk zinciri
olduğunu varsayabiliriz.
110. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
111. 6. AŞAMA
[LISTE_BASI] adresi [var_10]
lokal değişkenine atandıktan
sonra her bir döngüde zincir
liste içindeki bir sonraki
bileşenin [NEXT_OFFSET]
adresinin [var_10] adresine
atandığını görebiliyoruz.
112. 6. AŞAMA
Yazılımlarda genellikle zincir
listenin üzerinde çalışılan
bileşeninin adresinin
tutulduğu değişkene [CURSOR]
adı verilir. Biz de [var_10] lokal
değişkenini bu şekilde
adlandırabiliriz.
113. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
114. 6. AŞAMA
Bu döngüde daha önceden [VAR_J] olarak adlandırdığımız sayacın kullanıldığını, önce [1] değeri ile
initialize edildiğini, her döngüde girdi olarak verilen INTEGER ARRAY'inin [VAR_I] indeksindeki değerin
[VAR_J] değeri ile karşılaştırıldığını , bu değer [VAR_J] değerini aşıncaya kadar [CURSOR]'ün bir sonraki
bileşene işaret edecek şekilde ilerletildiğini ve tabi ki [VAR_J] değerinin artırıldığını görebiliyoruz.
115. 6. AŞAMA
Burada [L_PTR_INT] lokal değişkeninin fonksiyonun başında okuduğumuz [6]
INTEGER değerinin yazıldığı INTEGER ARRAY'inin başlangıç adresini işaret ettiğini
tekrar hatırlayalım.
116. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
117. 6. AŞAMA
Veri yapılarına da bakarak bu karmaşık kod bölümünü anlamlandırmaya
çalıştığımızda bizim gireceğimiz INTEGER dizisindeki her bir rakam için zincir
listenin başından itibaren girilen rakam indeksine kadar ilerleneceği sonucuna
varabiliriz. Peki zincirin bu bileşenine gelindiğinde ne olarak, bunu analizimizin
devamında anlamayı ümit ediyoruz.
118. 6. AŞAMA
Evet bu bölümler oldukça karmaşık, ancak odaklanarak dikkatli incelediğimizde
yeni oluşturulan bir lokal değişken array'inin ilgili [VAR_I] indeksi için mevcut
[CURSOR]'ümüzün değerinin yazıldığını anlayabiliriz.
119. 6. AŞAMA
Bu nedenle [var_58] lokal değişkenine [OFFSET_ARRAY] adını verelim.
120. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
OFFSET_ARRAY[VAR_I]=CURSOR
121. 6. AŞAMA [DONGU_3]'ü analiz ettik ve
bu sırada bir üst döngü olan
[DONGU_4]'ün eksik kalan kod
bölümünü de anlamlandırmış
olduk.
Bu nedenle [DONGU_3] kod
grubunu tekrar toplayabiliriz.
123. 6. AŞAMA
[DONGU_2] grubunu açmak
için bu grubu seçerek sağ
klikleyip [Ungroup nodes]
seçeneğini tıklayalım.
124. 6. AŞAMA
[DONGU_2] grubunda
[OFFSET_ARRAY]'in başlangıç
adresindeki değer önce
[LISTE_BASI] alanına yazılıyor,
nihai olarak da [CURSOR]
değişkenine yazılıyor.
Yani [CURSOR]
[OFFSET_ARRAY]'in ilk
bileşenini taşıyacak biçimde
yenileniyor.
[LISTE_BASI] değişkeni ise artık
[OFFSET_ARRAY]'in ilk
bileşenini içerecek biçimde
güncelleniyor.
125. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
OFFSET_ARRAY[VAR_I]=CURSOR
LISTE_BASI=OFFSET_ARRAY[0]
CURSOR=OFFSET_ARRAY[0]
126. 6. AŞAMA
Bu döngüde [VAR_I] değişkeni
sayaç olarak kullanılıyor ve [1]
olarak initialize ediliyor.
Döngüden çıkış koşulu da
[VAR_I] değişkeninin [6]'dan
küçük olması (çünkü 5'ten
büyük olursa döngü devam
ediyor)
127. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
OFFSET_ARRAY[VAR_I]=CURSOR
LISTE_BASI=OFFSET_ARRAY[0]
CURSOR=OFFSET_ARRAY[0]
for(VAR_I=1;VAR_I<6;VAR_I++)
128. 6. AŞAMA Biraz karmaşık olan son kod
bloğunda önce [CURSOR]
pointer'ı [EDX] register'ına
yazılıyor.
Daha sonra
[OFFSET_ARRAY]'inin [1]'den
başlatılmış olan [VAR_I] sayacı
kadar içerideki bileşenin
değeri [CURSOR]'ın işaret ettiği
zincir liste bileşeninin [8] byte
offset'ine yazılıyor.
Bu offset'in ne olduğunu daha
önce analiz etmiş ve
[Structures] penceresinde
tanımlamıştık. Bu tanımlamayı
buraya da aktaralım.
129. 6. AŞAMA [8] rakamının üzerine tıklayıp
sağ klikleyerek bu veriyi
anlamlandırmak üzere
[VERI_YAPISI.NEXT_OFFSET]
seçeneğini klikleyelim.
130. 6. AŞAMA Bu anlamlandırmayı yaptıktan
ve dikkatle inceledikten sonra
bu bölümde gerçekleşen
olayın [CURSOR] ile işaret
edilen zincir liste bileşeninin
[NEXT_OFFSET] alanının
[OFFSET_ARRAY]'deki bir
sonraki zincir bileşen adresi ile
güncellenmesi olduğunu
görebiliriz.
131. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
OFFSET_ARRAY[VAR_I]=CURSOR
LISTE_BASI=OFFSET_ARRAY[0]
CURSOR=OFFSET_ARRAY[0]
for(VAR_I=1;VAR_I<6;VAR_I++)
CURSOR->NEXT_ITEM=OFFSET_ARRAY[VAR_I]
132. 6. AŞAMA Bu güncellemeden hemen
sonra da [CURSOR] işaret ettiği
zincir bileşeninin
[NEXT_OFFSET] adresi ile
güncelleniyor.
Bir başka alternatif de
[OFFSET_ARRAY]'den bu
adresin alınması olabilirdi.
133. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
OFFSET_ARRAY[VAR_I]=CURSOR
LISTE_BASI=OFFSET_ARRAY[0]
CURSOR=OFFSET_ARRAY[0]
for(VAR_I=1;VAR_I<6;VAR_I++)
CURSOR->NEXT_ITEM=OFFSET_ARRAY[VAR_I]
CURSOR=CURSOR->NEXT_ITEM
134. 6. AŞAMA
Bu döngüyü de incelememiz
de bittiğine göre bu grubu da
kapatabiliriz.
Şimdi sıra [DONGU_1]'de.
137. 6. AŞAMA
[8] offset'ini işaretlediğimizde
benzer bir işlemin aşağı
bölümde de yapıldığını daha
rahat görebiliyoruz.
138. 6. AŞAMA Bu kod bölümlerini daha
anlamlı hale getirmek için veri
yapısına referans verme
imkanımızı tekrar kullanıyoruz.
139. 6. AŞAMA [CURSOR]'ün işaret ettiği son
zincir bileşeninin
[NEXT_OFFSET] alanına [0],
yani pek çok uygulama
dilindeki ifadesiyle [NULL]
atandığını görüyoruz. Bu zincir
listelerdeki son bileşen için
uygulanan tipik bir yöntem.
140. 6. AŞAMA
PSEUDO CODE
int girdi[6]
struct {
int ALAN_1
int ALAN_2
int next_item
} item;
item LISTE_BASI = 0X40304C
for(VAR_I=0;VAR_I<6;VAR_I++)
if (girdi[VAR_I] <= 0 || girdi[VAR_I] > 6)
HATALI_DENEME()
for(VAR_J=VAR_I+1; VAR_J<6;VAR_J++)
if(girdi[VAR_I]==girdi[VAR_J])
HATALI_DENEME()
for(VAR_I=0;VAR_I<6;VAR_I++)
item CURSOR = LISTE_BASI
for(VAR_J=1; VAR_J<girdi[VAR_I];VAR_J++)
CURSOR=CURSOR->NEXT_ITEM
OFFSET_ARRAY[VAR_I]=CURSOR
LISTE_BASI=OFFSET_ARRAY[0]
CURSOR=OFFSET_ARRAY[0]
for(VAR_I=1;VAR_I<6;VAR_I++)
CURSOR->NEXT_ITEM=OFFSET_ARRAY[VAR_I]
CURSOR=CURSOR->NEXT_ITEM
CURSOR->NEXT_ITEM=NULL
141. 6. AŞAMA Sonraki adımda [LISTE_BASI]
değişkeni [CURSOR]'a atanıyor.
Yeni liste başımız bizim
gireceğimiz [6] rakamdan
ilkinin işaret ettiği bileşendi ve
biz yeni sıralamayı
[OFFSET_ARRAY] aracılığı ile
oluşturmuştuk.
143. 6. AŞAMA Bu döngüde sayaç olarak
[VAR_I] değişkeni kullanılıyor.
[0] ile initialize edilen [VAR_I]
değeri [4]'ü geçtiğinde içinde
bulunduğumuz [ASAMA_6]
fonksiyonunun sonuna
geliyoruz.
Döngünün en alt bloğunda da
[VAR_I] değişkeninin [1]
artırıldığını görebiliyoruz.
145. 6. AŞAMA Döngümüzün yaptığı asıl test
bu bölümde gerçekleşiyor,
çünkü eğer hata yapmış isek
uygulama hata alarak
sonlanıyor.
Bu bölümde yapılan şey
[CURSOR]'ın o anda işaret
ettiği bileşenin ilk DWORD'ü
ile bu bileşenin [NEXT_OFFSET]
alanı ile referans verdiği
sonraki bileşenin ilk
DWORD'ünün karşılaştırılması
işlemi.
Eğer MEVCUT bileşenin [1.]
DWORD'ünün değeri listedeki
SONRAKİ bileşenin [1.]
DWORD'ünden DAHA BÜYÜK
ise [HATALI_DENEME]
fonksiyonu çağrılıyor.
150. 6. AŞAMA
Zincir listemizi daha önceden analiz etmiş ve offset'leri
çözerek tüm bileşenleri belirlemiştik.
[DONGU_1]'de bileşenlerin ilk alanlarındaki değerler
birbirleriyle karşılaştırılıyordu.
151. 6. AŞAMA
İlk alanın değerlerini HEX gösterimden
ONDALIK gösterime geçirmek için
[ALAN_1]'in üzerine tıkladıktan sonra sağ
klikleyerek [Field
type][Number][Decimal] seçeneklerini
seçebiliriz.
Daha sonra bu veri yapısını tespit
ettiğimiz zincir liste ile ilişkilendireceğiz.
152. 6. AŞAMA
[ALAN_1]'in veri tipini [DECIMAL] olarak belirledikten sonra
[VERI_YAPISI]'nın görüntüsü yukarıdaki gibi olacaktır.
153. 6. AŞAMA
[VERI_YAPISI]'nın son DWORD'ünün de
bir başka [VERI_YAPISI]'na işaret eden bir
OFFSET olduğunu biliyoruz. Bu bilgiyi de
veri yapısı üzerine aktarmak için son alanı
tıklayıp sağ klikledikten sonra [Field
type][Offset][Offset (struct) ...]
seçeneğini seçiyoruz.
154. 6. AŞAMA Karşımıza çıkan menüde tanımlanmış
olan tek veri yapısı olan [VERI_YAPISI]
çıkacaktır. Bu veri yapısını seçerek
offset'in işaret ettiği [Structure] tipini de
IDA'ya öğretmiş olacağız.
156. ALT+Q
6. AŞAMA Veri alanlarını tanımladığımız veri yapıları
ile ilişkilendirmek için ilgili veri alanının
üzerinde tıkladıktan sonra [ALT+Q]
tuşlarına basabiliriz. Daha sonra karşımıza
çıkan [Structure] listesinden ilgili olanı
seçerek bu kalıbı veri alanı ile
ilişkilendirmiş oluruz.
157. 6. AŞAMA
Zincir listemizi oluşturan bileşenleri ilgili [Structure] ile ilişkilendirdikten sonra
veri tipleri de [Structure] üzerinde tanımladığımız gibi görüntülenmiş olacaktır.
İlk alanları [DECIMAL] olarak tanımladığımız için şimdi ONDALIK olarak
görülebileceklerdir.
158. 6. AŞAMA
Veri yapılarını ilk alanlarındaki değerlerine göre sıralarsak
[ASAMA_6] için girmemiz gereken dizi [2 4 1 6 3 5] olacaktır.
160. NE ÖĞRENDİK
Karmaşık kod bloklarını analiz etme hakkında
• Kod bloklarını renklendirme
• Kod bloklarını gruplama
• İç içe döngüleri en içten başlayarak inceleme yöntemi
Veri yapılarını anlamlandırma ve kod okunabilirliğini artırma
hakkında
• IDA'da structure tanımı yapma
• Tespit edebildiğimiz veri yapısı bileşenlerinin formatlarını
structure içinde tanımlama
• Structure bilgisini kod içine verilen referanslara aktarma
• Structure bilgisini veri blokları üzerine aktarma