3. ÖN BİLGİ
• Birinci aşama eğitmen eşliğinde gerçekleştirilecektir. Bu çalışma
sırasında IDA'nın temel özellikleri ve kullanımına aşina olmanın
yanı sıra tersine mühendisliğe uygulamalı bir giriş yapılacaktır.
• Birinci aşamaya kısa bir "dinamik analiz" ile başlayacağız.
• Daha sonra dinamik analizle tespit ettiğimiz "string" bilgilerini
kullanarak uygulama içinde bu string'lerin kullanıldığı
fonksiyonları inceleyecek ve hangi fonksiyona odaklanmamız
gerektiğini tahmin etmeye çalışacağız.
• Odaklanmamız gereken fonksiyonu tespit ettikten sonra da
algoritma, veri yapıları ve fonksiyondan çağrılan API
fonksiyonları ve diğer fonksiyonları inceleyerek fonksiyonun
amacını anlamaya çalışacağız.
• Karmaşık adımlarda fonksiyonu pseudo kodlayarak edindiğimiz
algoritma bilgisini kaydedebiliriz.
4. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
• IDA ismini Interactive Disassembler ifadesinden alır. Bir
çalıştırılabilir dosya IDA tarafından ilk analiz edildiğinde IDA
".idb" uzantılı ayrı bir dosya üzerinde bir veritabanı oluşturur.
• Bu noktadan itibaren disassembly üzerinde yapılan tüm işlemler
(rename etmeler, renklendirmeler, gruplamalar, veri tipi
dönüşümleri, v.d.) bu veritabanı üzerinde yapılır, çalıştırılabilir
dosya ile ilgisi yoktur. Bu nedenle analistler bir zararlı üzerinde
çalıştıklarında sadece IDA veritabanını paylaşarak çalışabilirler,
çalıştırılabilir dosyanın paylaşılması gerekmez.
• Tabi eğer analist isterse assembly kodu üzerinde yaptığı
değişikliği çalıştırılabilir dosyaya yamalayabilir.
5. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
• DİKKAT: IDA PRO'da "undo" (geri al) imkanı yoktur. Dolayısıyla
belli aralıklarla yedek almakta fayda vardır. Zira veri yapılarını
farklı formatlara dönüştürdüğümüzde (örneğin byte serisini
string gibi yorumlattığımızda, v.b.), fonksiyon, parametre,
değişken isimlerini rename ettiğimizde, veri alanlarını kod gibi
yorumlattığımızda bu işlemleri geri almak vakit kaybettirebilir.
6. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
IDA arayüzü hakkındaki temel bilgiler
• Işıklandırma (highlighting)
• IDA arayüzünde bir cursor'ü bir kelimenin üzerine
(tıklayarak) konumlandırdığınızda arayüzde aynı kelimenin
geçtiği diğer yerler de highlight edilir.
• Örneğin "call" instruction'ına tıkladığınızda diğer "call"
instruction'ları da highlight edilir. Ya da belli bir register'a
atanan bir değerin nerede tekrar kullanıldığını kolayca
görebilmek için bu register'ın adının üzerine tıklanabilir.
• Bu sayede assembly kodu içinde bizim o anki incelememizle
ilgili bölümler daha kolay ayrıştırılabilir.
7. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
IDA arayüzü hakkındaki temel bilgiler
• Işıklandırma (highlighting)
8. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
IDA arayüzü hakkındaki temel bilgiler
• Grafik Görüntü (Graph View) – Metin Görüntü (Text View)
• IDA'nın en önemli faydalarından birisi Graph View imkanıdır.
İki mod arasında "space" tuşu ile geçiş yapılabilir.
SPACE
9. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
IDA arayüzü hakkındaki temel bilgiler
• Grafik Görüntü (Graph View) – Metin Görüntü (Text View)
• Graph View bize fonksiyonun karmaşıklığı, ana dalları ve
döngüleri hakkında görsel ve dolayısıyla daha kolay
anlaşılabilir bilgi verir.
• IDA döngüleri "kalın mavi" çizgilerle ifade eder.
• Karar dalları (decision branches) eğer koşul doğru (true) ise
"yeşil", değilse "kırmızı" çizgilerle ifade edilir.
10. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
IDA arayüzü hakkındaki temel bilgiler
• Grafik Görüntü (Graph View) – Metin Görüntü (Text View)
Döngü
if (*EAX = 0) == TRUEif (*EAX = 0) != TRUE
11. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Yeniden isimlendirme (renaming)
• Assembly kodunu anlamlandırdıkça fonksiyon isimleri ve
değişkenler üzerine tıklanarak "N" tuşuna basıldığında anlamlı
isimler verebiliriz. Bu işlemi gerçekleştirdiğimizde söz konusu
fonksiyonlara veya değişkenlere referans verilen diğer tüm
noktalarda bizim yeni verdiğimiz isim görülür.
12. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Imports alt penceresi (subview)
• PE dosya formatında da değinildiği gibi uygulamanın kullandığı
işletim sistemi API'leri bize uygulamanın amaçları ile ilgili bilgi
verebilir.
13. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Strings alt penceresi (subview)
• Kesin bilgi vermese de dinamik analiz ile birleştirildiğinde
string'ler hangi fonksiyonlara odaklanmamız gerektiği hakkında
bize çok yardımcı olabilirler.
14. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Strings alt penceresi (subview)
• Bir string'in üzerine çift tıklayarak onun dosya içindeki yerine
ulaşabiliriz.
15. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Referanslar
• String'e IDA tarafından verilen ismin üzerine tıkladıktan sonra
"X" tuşuna basılırsa bu string'e referans verilen yerler görülebilir.
16. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Referanslar
• Referanslar'dan birisi seçilerek çift tıklandığında assembly kodu
içinde string'e referans verilen alana ulaşılır.
17. ÖN BİLGİ
IDA PRO HAKKINDA TEMEL BİLGİLER
Geri dönme (ESC tuşu)
• IDA içinde referanslar, fonksiyon isimleri, v.d. üzerine tıklanarak
yapılan geçişlerde geri dönülmek için ESC tuşuna basılabilir.
ESC
19. 1. AŞAMA
Dinamik analiz sırasında gözlemlediğimiz
string'lerden birisini Strings penceresinden
bularak çift tıklayalım.
20. 1. AŞAMA
IDA'nın C string'i olarak algıladığı bu veri
alanına verdiği isme ("aTersineMuhendi")
tıkladıktan sonra "X" tuşuna basarak bu alana
referans veren diğer noktaları izleyelim.
21. 1. AŞAMA
String'imize veri alanına nazaran yukarıdaki [up] object, yani kod tipinde [o]
bir alandan ve tam olarak da [_main] fonksiyonunun 0xBB offset adresinde
bulunan bir instruction'dan referans verildiğini görüyoruz.
Instruction'dan anlaşıldığı kadarıyla bu string'in adresi stack'te bir lokal
değişken alanına yazılıyor.
Şimdi çift tıklayarak bu kod bölümüne geçelim.
23. 1. AŞAMA Bu kod bölümünde biraz aşağıya doğru
indiğimizde aşamalarla ilgili yönlendirme
metinlerini görebiliyoruz. Bu fonksiyon bizim
ana dallanma noktamız olabilir. Fonksiyon
içinden çağrılan diğer fonksiyonları daha kolay
görebilmek için [call] instruction'larından
birisinin üzerine tıklayarak tüm [call]
instruction'larını highlight edebiliriz.
IDA bazı fonksiyonları [printf] olarak
adlandırmış, bazılarını ise [sub_] ile başlayan
ve yanında da RVA'e benzer bir adres ile
isimlendirmiş.
24. [printf] fonksiyonuna çift tıkladığımızda bir [stub] kod bölümüne atlıyoruz.
Bu kodda sadece [Import Address Table - IAT]'da bulunan [printf] API
fonksiyon adresine [JMP] instruction'ı bulunuyor. IDA'nın fonksiyon adı
olarak [printf] yazabilmesinin sebebi de bu.
1. AŞAMA
25. 1. AŞAMA
[ASAMA-1] string'i stack'e yazıldıktan hemen sonra [printf] fonksiyonu
çağrılıyor. Bundan sonra da [sub_401B19] fonksiyonu çağrılıyor.
26. 1. AŞAMA
[sub_401B19] fonksiyon adına
tıkladığımızda bu fonksiyonun her
[ASAMA] string'i yazıldıktan sonra
çağrıldığını görebiliyoruz.
27. 1. AŞAMA
[sub_401B19] fonksiyon adına çift
tıkladığımızda bu fonksiyona
ulaşabiliriz. Fonksiyonun içeriğindeki
metinlerden yola çıkarak kesin emin
olmasak da bu fonksiyonun okumayla
ilgili olduğu tahminini yapabiliriz.
28. 1. AŞAMA
Bu fonksiyonun adını tahminimizi yansıtacak biçimde [GIRDI_OKU] olarak
[RENAME] edelim. Eğer hatalıysak daha sonra daha detaylı bir inceleme
yaparak bu adı değiştirme imkanımız var.
30. Yaptığımız isim değişiklikleri, eklediğimiz yorumlar, kod
değişiklikleri v.d. bilgiler daha önce de belirttiğimiz gibi
IDA'nın oluşturduğu veritabanına yazılır.
1. AŞAMA
31. 1. AŞAMA
ESC tuşu ile [GIRDI_OKU] fonksiyonundan bu
fonksiyonu çağıran ana fonksiyona
döndüğümüzde fonksiyonun çağrıldığı her
noktada isminin [GIRDI_OKU] olarak
güncellendiğini görebiliriz.
32. 1. AŞAMA
[GIRDI_OKU] fonksiyonundan bir sonra çağrılan fonksiyon [sub_401470]
fonksiyonu. [Calling Convention]'lardan bahsederken [EAX] register'ının genellikle
fonksiyonun [RETURN] değerini içerdiğini belirtmiştik.
[GIRDI_OKU] fonksiyonu çağrıldıktan hemen sonra [EAX] register'ının değeri önce
[var_4] lokal değişkenine, oradan tekrar anlamsız bir biçimde [EAX] register'ına
atanıyor. Belli ki kullanılan derleyici bu konuda bir optimizasyon yapmamış veya
yapamamış. Daha sonra [EAX] register'ının değeri [var_18] değişkenine atanıyor
ki; bu değişken de [ESP]'nin işaret ettiği en uç noktada bulunan bir alanda yer
alıyor. X86'ya giriş bölümünde bir fonksiyona [PUSH] instruction'ı ile parametre
vermiştik. Burada ise derleyici yine ilginç bir farklılık göstererek stack'te daha
önceden ayırmış olduğu alanın son bölümünü parametre aktarmak için kullanıyor.
33. [sub_401470] fonksiyonuna
geçtiğimizde [asdf2009] string'inin
adresini [var_4] lokal değişkenine,
aldığı parametreyi ise [var_8] lokal
değişkenine atadığını görüyoruz.
Bu işlemlerden hemen sonra da
[sub_401492] fonksiyonu
çağrılıyor. Bu fonksiyonun çıktısının
[EAX register değerinin] [0] olup
olmadığı kontrol ediliyor. Eğer [0]
değilse [sub_401C1E] fonksiyonu
çağrılıyor. Sonuç [0] olduğu
durumda da fonksiyon normal bir
biçimde sonlanıyor.
1. AŞAMA
34. 1. AŞAMA
Sonucun [0] olmadığı durumda
çağrılan fonksiyona baktığımızda
son olarak [exit] fonksiyonunun
çağrıldığını görüyoruz.
35. 1. AŞAMA
C ve C++ API fonksiyonlarına MSDN'den veya diğer kaynaklardan göz atabiliriz.
Bu kaynaklarda "exit" fonksiyonu çağrıldığında [process]'in, yani uygulamamızın
kapatıldığını görebiliriz.
36. 1. AŞAMA
Bu fonksiyonu [HATALI_DENEME]
olarak isimlendirelim. Yaptığı iş
sadece [8] statü koduyla
uygulamayı sonlandırmak.
37. 1. AŞAMA
Çıkış yapan fonksiyonu [RENAME]
ettikten sonra kodu biraz daha
rahat okunur hale getirmiş olduk.
38. 1. AŞAMA
İlk adımda çağrılan fonksiyonun
adını da [ASAMA_1] olarak rename
edebiliriz.
39. 1. AŞAMA
[ESC] tuşuyla ana fonksiyona göndüğümüzde çağrılan
fonksiyon adının da güncellendiğini görebiliriz.
42. 1. AŞAMA
[ASAMA_1]
fonksiyonunda asıl
kontrolün yapıldığını
tahmin edebileceğimiz
bir başka fonksiyon daha
[sub_401A02] çağrılıyor.
Bu fonksiyon
çağrıldığında ise [ESP]
register'ının işaret ettiği
alanda [2] adet lokal
değişkenin tutulduğunu
görebiliyoruz.
43. 1. AŞAMA
Bu değişkenlerin de
adlarını biraz daha
anlamlı kılmak üzere
[var_4]'ü [L_ASDF1234],
[var_8]'i [L_GIRDI] olarak
adlandırabiliriz. Eğer
[sub_401A02] fonksiyonu
[2] adet parametre
alıyorsa bunlardan ilki
[L_GIRDI] (yani
kullanıcının girdiği veri),
ikincisi ise [L_ASDF1234],
yani [asdf2009] değeridir.
44. 1. AŞAMA
Tam olarak ne yaptığını
analiz edebilmek için
[sub_401A02] fonksiyon
adına çift tıklayarak bu
fonksiyona geçelim.
45. 1. AŞAMA
Bu fonksiyon önce 1.
parametreyi [arg_0], daha
sonra da [2.] parametreyi
[arg_1] [sub_4019D6]
fonksiyonuna parametre
olarak veriyor ve bu
fonksiyonu çağırıyor.
46. 1. AŞAMA Bu fonksiyona geçtiğimizde
kendisine verilen parametreyi
[EAX] register'ına atadığını
görüyoruz. [EAX] register'ının
geçtiği yerleri daha rahat
görebilmek için bu register'a
tıklayalım.
[var_8] lokal değişkeni
kendisine verilen parametre ile
initialize edilirken [var_4] [0]
değeri ile initialize ediliyor.
Aşağıdaki döngüde [var_4]
sayaç olarak kullanılıyor. [var_8]
lokal değişkeninin işaret ettiği
bellek alananındaki byte ise her
turda [0] ile karşılaştırılıyor.
Son turda [var_4] sayaç değeri
[EAX] register'ına atanarak geri
döndürülüyor.
47. 1. AŞAMA
Yaptığımız analize dayanarak bu
fonksiyonun kendisine parametre
olarak verilen bir C string'inin
uzunluğunu belirlediğini
söyleyebiliriz.
Bu nedenle bu fonksiyona
[MTN_UZUNLUK] adını verebiliriz.
48. 1. AŞAMA
[ESC] tuşu ile geri döndüğümüzde
bu fonksiyonun amacını anlamış
olduğumuzdan, fonksiyona
parametre olarak verilen 1. ve 2.
parametrelerin (yani C
string'lerinin) uzunluklarının
karşılaştırılması neticesinde eğer bu
string'lerin uzunlukları eşitse
sağdaki kod alanlarına geçildiğini
daha rahat görebiliyoruz.
Bunu da kod okunurluğunu
artırabilmek için [JUMP] edilen bu
alanın ismini [UZUNLUK_ESITSE]
ifadesiyle değiştirerek koda
aktarabiliriz.
49. 1. AŞAMA
[sub_401A02] fonksiyonu aldığı 2
parametreyi [var_8] ve [var_C]
lokal değişkenlerine atadıktan
sonra bunlar üzerinde bir inceleme
yapıyor.
Bu parametrelerin ne olduklarını
daha net görebilmek için [ESC]
tuşuna basarak [ASAMA_1]
fonksiyonuna geçebiliriz.
50. [ASAMA_1] fonksiyonuna geçtiğimizde [1.] parametrenin bizim girdiğimiz değer,
[2.] parametrenin ise [asdf2009] string'i olduğunu görebiliriz.
Uygulama derleyicisinin çağrılan fonksiyonlara parametre aktarma yönteminin
stack'te lokal değişkenler için ayrılan yerleri kullanmak olduğunu daha önceden
değerlendirmiştik.
1. AŞAMA
51. 1. AŞAMA
[sub_401A02] fonksiyonunun
içinde de parametre (argüman)
isimlerini bu bilgilere uygun
biçimde düzenleyebiliriz.
Buna göre [1.] parametreye
[TEST_GIRDISI], [2.] parametreye
[MTN_ASDF2009] adını verebiliriz.
53. Kodun kalan bölümünde lokal
değişkenler üzerinden işlem
yapıldığı için lokal değişkenleri
[var_8] için [L_TEST_GIRDISI] ve
[var_C] için [L_MTN_ASDF2009]
olarak düzenleyelim.
1. AŞAMA
54. Fonksiyonun içinde test girdisinin her bir byte'ını kontrol eden bölümde eğer
okunan byte [0] ise Jump edilecek adresi [GIRDI_SONUNA_GELDIK] olarak
adlandırabiliriz.
1. AŞAMA
55. Girdinin son byte'ına gelinmemesi
durumunda test girdisi ve
[asdf2009] string'lerinin her bir
byte'ının karşılaştırıldığı kod
bölümüne geliyoruz.
Bu karşılaştırmanın sonucunun
eşitlik olmaması durumunda
atlanan kod adresini de
[GIRDI_FARKLI] olarak
isimlendirebiliriz.
1. AŞAMA
56. Karakterlerin aynı olması halinde test girdisi ve [asdf2009] string'i
içinde işaret edilen byte'ların adresleri [EAX] register'ına atanır.
1. AŞAMA
57. Test girdisi ve [asdf2009] string'i içinde işaret edilen byte'ların
adresleri [INC] instruction'ı ile [1] artırılır ve döngünün bir sonraki
turuna geçilir.
1. AŞAMA
58. [sub_401A02] fonksiyonuyla ilgili
analizimiz sonucunda bu
fonksiyonun adını
[MTN_KARSILASTIR] olarak
düzenleyebiliriz. Eğer karşılaştırılan
metin'ler aynı ise bu fonksiyon [0]
değerini, farklı ise [1] değerini
döndürerek sonlanıyor.
1. AŞAMA
59. [ASAMA_1] fonksiyonuna geri
döndüğümüzde ve çağrılan
fonksiyonun amacının metin
karşılaştırma olduğunu ifade eden
bir fonksiyon adını gördüğümüzde
girilecek olan girdinin [asdf2009]
string'i olması gerektiğini net olarak
söyleyebiliriz.
1. AŞAMA
60. Bu bilgiyi kullanarak eğer
[MTN_KARSILASTIR] fonksiyonunun
döndürdüğü değer [0] ise atlanan
(JUMP edilen) adresi de
[METINLER_AYNI] şeklinde
isimlendirebiliriz.
1. AŞAMA
62. NE ÖĞRENDİK
IDA Pro hakkında
• Strings penceresi
• Kod highlighting (ışıklandırma)
• XREF'ler ile kod v.d. bölümlere atlama
• [ESC] tuşu ile geri dönme
• Rename ederek fonksiyon, değişken, v.d. bölümleri
anlamlandırma
• IDB veritabanı dosyaları ve yedekleme ihtiyacı
63. NE ÖĞRENDİK
Algoritma hakkında
• Döngüler, döngü çıkış koşulları testi ve "i++", yani [INC] kod
örnekleri
• "If" koşulu assembly kod örnekleri ("test eax, eax", "cmp eax,
ebx", v.d.)
X86 ve diğer
• Sistem API'leri ile ilgili bilgi bulma (MSDN, diğer)
• Üzerinde çalıştığımız kodu üreten derleyicinin çağrılan
fonksiyonlara parametre aktarma yolu olarak en başta ayrılan
stack alanını kullanmayı tercih ettiği
65. ÖN BİLGİ
[FOR] DÖNGÜ YAPISI
for (i=0;i<6;i++) {}
[FOR] döngüleri 3 ana bölümden oluşur:
• Sıfırlama [initialization]
• Test
• Sayaç artırma
66. ÖN BİLGİ
DİZİ [ARRAY] ERİŞİMİ
Dizi erişimleri [FOR] döngüleri içinde sıklıkla rastlanır çünkü dizi
elemanlarının tamamını işlemek gerektiğinde döngüye ihtiyaç
olaraktır.
Genel dizi elemanı erişim şekli aşağıdaki gibidir:
[BASE+COUNT*INCREMENT]
BASE, dizinin başlangıç adresidir. COUNT, dizinin erişilen bileşeninin
indeksidir. INCREMENT, dizi bileşeninin uzunluğudur.
67. ÖN BİLGİ
DİZİ [ARRAY] ERİŞİMİ
[BASE+COUNT*INCREMENT]
Örneğin; bir INTEGER array
erişiminde bileşen uzunluğu [4] BYTE
olaraktır ve bileşenlere yukarıdaki
gibi erişilecektir.
68. ÖN BİLGİ
DEĞİŞKEN SAYIDA GİRDİ ALAN [API] FONKSİYONLARI
Bazı API fonksiyonları işlevleri gereği değişken sayıda girdi
alabilirler.
Örneğin; [printf], [scanf], v.b. fonksiyonlar ilk parametreleri olan
[format string]'e uyumlu olarak diğer parametreleri stack'te
bulmayı beklerler.
printf("%d, %d n", i, j);
69. Ana fonksiyonda farklı aşamaların
çağrıldığı noktaları (printf
fonksiyonu ile) konsola yazılan
mesajlardan yola çıkarak tahmin
edebiliriz. Tahminimiz hatalı olsa
bile değiştirme imkanımız var.
2. AŞAMA
71. 2. AŞAMA
Fonksiyonun aldığı parametreyi
kopyaladığı lokal değişkeni de
[RENAME] ederek teste tabi
tutulacak olan verinin izini
sürmeye devam edelim.
72. 2. AŞAMA
[HATALI_DENEME] fonksiyonunun çağrılmasına
neden olabilecek JUMP instruction'ının öncesinde
[var_38] lokal değişkeninin [1] değeri ile
karşılaştırıldığını görüyoruz. Bu karşılaştırmadan
hemen önce de [sub_401974] adlı fonksiyonun
çağrıldığını görüyoruz. Muhtemelen bu
fonksiyonun içinde yapılan bir kontrol bu aşamayı
geçip geçmememize etkide bulunacak.
Dikkat edersek yukarıda [var_38] lokal değişken
alanının adresinin [var_44] lokal değişken alanına
aktarıldığını görebiliriz. Bu da [sub_401974]
fonksiyonu çağrıldığında [var_48] , yani AŞAMA
2'nin girdisiyle birlikte [var_38] lokal değişkeninin
adresinin de fonksiyona parametre olarak
verildiği ihtimalini doğuruyor.
74. 2. AŞAMA
Fare imlecini [L_PRM_GIRDI]
adını verdiğimiz alanın
üzerine getirdiğimizde ve
beklediğimizde bir başka
fonksiyonu çağırdığımızda bu
fonksiyonun alacağı
argümanların sırasını da
yukarıdan aşağıya doğru
görebiliriz.
Buna göre [L_PRM_GIRDI] ->
[arg_0] ise [var_44] -> [arg_4]
olmalıdır.
75. 2. AŞAMA
Edindiğimiz bilgileri yansıtmak için
[ASAMA_2]'nin [var_44]
değişkenini de (fonksiyona verilen
[2.] parametre anlamında)
[F_PRM_2_PTR] olarak
isimlendirebiliriz.
76. 2. AŞAMA
Çağrılan [sub_401974]
fonksiyonuna verilen [2.]
parametre bir pointer. Pointer'ın
işaret ettiği bellek alanı ise
[var_38]'di. Bu alanı da fonksiyon
parametresi ile ilişkilendirmek için
adını [L_PRM_2] olarak
değiştirebiliriz.
78. 2. AŞAMA
Fonksiyonun aldığı birinci
parametre bizim ASAMA 2'de
gireceğimiz girdi, ikinci parametre
ise bu fonksiyonun bir şekilde
kullanacağı bir bellek alanının
adresidir. [ASAMA_2]
fonksiyonunda kullandığımız
isimlere benzer olarak birinci
parametreye [PRM_GIRDI], ikinci
parametreye ise [PRM_2_PTR]
adını verebiliriz.
79. 2. AŞAMA [sub_401974] fonksiyonunun
içinden [sscanf] API fonksiyonunun
çağrıldığını görüyoruz. IDA PRO
sistem API fonksiyonlarını tanıdığı
için aldığı parametreleri gösteriyor.
80. 2. AŞAMA
Ancak [sscanf] fonksiyonunu daha iyi tanımak için MSDN veya diğer C ve C++
kaynaklarından faydalanabiliriz.
Buna göre [1.] parametre [sscanf] tarafından okunacak olan C string'ine işaret
ediyor, [2.] parametre okunan C string'indeki verilerin hangi formatta
değerlendirileceğini belirtiyor, diğer parametreler ise format string'de belirtilen
verilerin yazılacağı alanların [pointer]'larından oluşuyor.
81. 2. AŞAMA [sscanf] fonksiyonu çağrıldığında alacağı
parametreleri görebilmek için lokal
değişken isimleri üzerinde mouse
imlecimizi bekletebiliriz. Buna böre [1.]
parametre [var_28], [2.] parametre
[var_24] ve [3.] parametre de [var_20] de
yer almalı. Şimdi bu parametrelere atanan
değerleri inceleyebilir ve isimlerini bizim
için daha anlamlı isimlerle değiştirebiliriz.
82. 2. AŞAMA Fonksiyonun aldığı [1.] parametre olan
[PRM_GIRDI] değeri (ki bu değer aynı zamanda
[ASAMA_2] fonksiyonun aldığı girdi oluyor)
[sscanf]'in ilk parametresi oluyor. Bu
parametreye [CHAR_PTR] adını verelim. [2.]
parametre [%d %d %d %d %d %d] değeri olan
ve 6 adet integer pointer'ına işaret eden bir
string. Bu parametreye [FORMAT_STR_PTR]
adını verelim. [sscanf] fonksiyonuna verilen [3.]
parametre ise içinde bulunduğumuz
[sub_401974] fonksiyonuna [2.] parametre
olarak verilmiş olan bir pointer, bu alana da
[L_PRM_2_PTR_INT] adını verelim. Demek ki
[ASAMA_2] fonksiyonundan parametre olarak
verilen lokal değişken adresi 6 integer değer
adresinden ilkinin adresiymiş.
83. 2. AŞAMA
Windows sistem API dokümantasyonundan [sscanf] fonksiyonunun çıktısının
doldurmayı başardığı parametre sayısı olduğunu anlıyoruz.
Daha önceki incelememizden aldığı format string'de 6 adet [%d], yani integer
değer referansı bulunduğunu söyleyebiliriz. [sscanf]'in çıktısının atandığı [var_4]
lokal değişkeni [5] rakamı ile karşılaştırılıyor.
84. 2. AŞAMA
Eğer fonksiyonun aldığı string içinde [5] veya daha az parametre varsa oyunu
kaybediyoruz.
Kodun okunabilirliğini artırmak için okunan parametre sayısının [5]'ten büyük
olduğunda atlanan adresi [OKUNAN_DEGER_SAYISI_5TENBUYUK] olarak
adlandırabiliriz.
85. 2. AŞAMA
[sub_401974] fonksiyonunu
inceledikten sonra kullanım
amacının 6 adet INTEGER değer
okumak olduğunu söyleyebiliriz.
Bu nedenle fonksiyonun adını
[ALTI_INT_OKU] olarak
değiştirebiliriz.
86. 2. AŞAMA
[ALTI_INT_OKU] fonksiyonu
kendisine [2.] parametre olarak
verilen alandan başlayarak arka
arkaya [6] adet INTEGER değeri
belleğe dolduruyor olmalı.
Bunlardan ilkinin değerinin [1]'den
farklı olması halinde oyun
sonlanıyor. Dolayısıyla [2.]
aşamada girdiğimiz girdinin ilk
rakamı [1] olmalı.
87. 2. AŞAMA
Bu tespitimizi kalıcı hale getirmek
için ilk değerin [1] olduğu durumda
atlanan adrese
[BIRINCI_DEGER_BIR_ISE] adını
verebiliriz.
88. 2. AŞAMA
Birinci değerin [1] olması koşulunu
sağladıktan sonra karşımıza tipik
bir döngü çıkıyor.
Döngünün başında [var_C] lokal
değişkenimize [1] değerinin
atandığı ve bu değişkenin sayaç
olarak kullanıldığını söyleyebiliriz.
89. 2. AŞAMA
[var_C]'nin sayaç olduğunu tahmin
ettiğimize göre bu değişkenin adını
[SAYAC] olarak değiştirebiliriz.
90. 2. AŞAMA
Döngünün çıkış şartı ise sayacın
[5]'ten büyük olması.
Eğer girdiğimiz [6] rakam da
algoritmanın beklediği değerlere
uygunsa [ASAMA_2] fonksiyonu
başarılı bir şekilde sonlanacaktır.
PSEUDO CODE
for (int i=1; i>5; i++) {
...
}
91. 2. AŞAMA
Algoritmanın kritik olan
bölümü biraz karışık. Ancak
yapılan karşılaştırma ilk
parametreden [2] sonraki
integer değerinin [1]
sonraki değerin [2] katı
olup olmadığının
kontrolüdür. Daha sonraki
döngülerde de bu
karşılaştırma bir
kaydırılarak gerçekleştirilir.
[L_PRM_2] okunan
değerlerin atandığı
dizinin ilk elemanının
adresi ise [2.] elemanın
adresi [var_3C]
olacaktır.
92. 2. AŞAMA
PSEUDO CODE
if (arr[0] == 1) {
for (int i=1; i>5; i++) {
if (arr[i+1] != 2*arr[i]) {
HATALI_DENEME();
}
}
} else {
HATALI_DENEME();
}
Karmaşık bir algoritmayı ifade etmenin en iyi yolu pseudo kod ile ortaya
koymaktır.
CEVAP: 1 2 4 8 16 32
93. 2. AŞAMA
Uygulama ile biraz daha
oynadığımızda eğer uygulamaya
bir parametre verirsek bize bir
kullanım mesajı verdiğini
görebiliriz.
Bu mesaja göre uygulamamız bir
dosyayı da girdi olarak kabul
ediyor gibi görünüyor.
94. 2. AŞAMA
Benzeri bir tahmini uygulamanın
string'lerini inceleyerek de
yapabilirdik.
95. 2. AŞAMA
Bu tahminimizi test etmek için [1.] aşamadaki cevabımızı bir dosyaya
yazarak uygulamaya verelim.
Cevaplarımızı yazdıktan sonra bir defa [ENTER]'a basmayı unutmayalım.
Bu şekilde sonraki aşamalarda önceki cevapları tekrar tekrar yazmaktan
kurtulabiliriz.
99. NE ÖĞRENDİK
Algoritmalar hakkında
• [FOR] döngüleri assembly kod yapısı
• Sayaç sıfırlama
• Test
• Sayacı artırma
Diziler [Array] hakkında
• Dizi bileşenlerine erişim için adres hesaplama
[TABAN+SAYAC*BOYUT]
API fonksiyonları hakkında
• Değişken parametre alabilen API fonksiyon örnekleri
102. ÖN BİLGİ
[SWITCH .. CASE] DEYİMİ VE [JUMP TABLE]
Switch .. Case deyimi farklı derleyiciler tarafından farklı şekillerde
assembly'ye dönüştürülür. Bazı derleyiciler iç içe [if .. elseif .. elseif
.. else] şeklinde derlerken bazıları da bir Jump Table dizisi
kullanabilir.
Yukarıdaki kod bloğunda [EAX] register'ının barındırdığı adrese [JUMP] etmeden
önce [var_4] değişkeninin değeri (ki muhtemelen bir indeks değeri) [SHIFT LEFT]
instruction'ı ile [4] ile çarpılıyor. Bu offset'te bir [JUMP TABLE] içinde ilgili adresi
bulmak için kullanılıyor.
103. 3. AŞAMA
3. aşama fonksiyonumuz da bir
önceki aşamada olduğu gibi
[sscanf] fonksiyonunu çağırıyor.
Fakat bu defa format string'imiz
farklı [%d %c %d], yani [INT],
[CHAR], [INT] formatında veriler
okunuyor.
104. 3. AŞAMA
Daha önce de yaptığımız gibi
parametre ve lokal değişken
isimlerini daha anlaşılır kılmak için
[RENAME] edebiliriz.
İlk olarak format string pointer'ının
saklandığı lokal değişkenin ismini
değiştirelim.
105. 3. AŞAMA
Diğer aşamalar gibi alınan
parametrenin ve bu parametrenin
atandığı lokal değişkenlerin
isimlerini değiştirelim.
106. 3. AŞAMA
Bu noktaya kadar karşımızdaki
derleyicinin yaklaşımına alışmış
olmamız lazım. [sscanf]
çağrıldığında Girdi ve format
string'den hemen sonra [3] adet
pointer gelmeli. Bunlar [var_20],
[var_1C] ve [var_18]. Ancak bu
adreslerde pointer'ları saklanan
lokal değişken alanları ise:
• [var_4] [int]
• [var_E] [char]
• [var_8] [int]
107. 3. AŞAMA
Lokal değişken adları olarak
aşağıdakileri kullanabiliriz:
• [var_4] [int] – INT_1
• [var_E] [char] – CHAR
• [var_8] [int] – INT_2
108. 3. AŞAMA
Önceki aşamadan [sscanf]'in
döndürdüğü sonucun okunan veri
sayısı olduğunu biliyoruz.
Uygulamanın sonlanmaması için
en az [3] değer okunması lazım.
109. 3. AŞAMA
Okunan ilk [INTEGER] değer [7]
rakamıyla karşılaştırılıyor ve eğer
[7]den büyükse [JA] [Jump if
Above] instructionı ile oyun
sonlanıyor.
Buna göre ilk değer integer olmalı
ve [7] veya daha küçük bir değer
olmalı.
110. 3. AŞAMA
Doğru yolda ilerlerken ilginç bir olay gerçekleşiyor.
Grafik olarak kod sanki sona gelmiş gibi
görünüyor.
Gerçekte olan ise [ds:off_404264] adresinden
itibaren 1. INTEGER değerin 4 katı [shl eax,2]
offset'te bulunan adrese [JMP] ediyor olmamız.
Bu akış derleyicimizin [SWITCH .. CASE]
statement'ını yorumlama şekli.
111. 3. AŞAMA
[ds:off_404264] adresine atladığımızda toplam 8 adres
barındıran bir JUMP TABLE görüyoruz.
Yani [0-7] arasındaki bir rakamın [4] ile çarpıldığında
bulunabilecek offset'lerde diğer kod bölümlerinin adreslerini
görüyoruz.
112. 3. AŞAMA
Jump table'ımızın ilk değeri olan
olan [loc_40153F] değerine
geçelim [1. INT değerinin "0"
olduğu senaryo için]
113. 3. AŞAMA [EBP – 0xD] adresine [0x62] değeri
yazıldıktan sonra [EBP – 0x8] offset'indeki
değer [0x65] ile karşılaştırılıyor.
Fonksiyonun yukarı kısımlarına bakarsak
daha önceden bu alanın 2. INTEGER
değerin saklanması için kullanıldığını
görebiliriz.
Her nedense IDA PRO bu alana verdiğimiz
ismi burada kullanamamış.
114. 3. AŞAMA
IDA'da bir değeri Decimal
karşılığına dönüştürmek için
Context Menü'yü veya [H] tuşunu
kullanabiliriz.
115. 3. AŞAMA
1. INTEGER değerinin [0] olduğu
durumda 2. INTEGER değeri [101]
ile karşılaştırılıyor.
116. 3. AŞAMA Pek doğru bir isimlendirme olmasa da 2. INTEGER
değerinin [101] olması halinde [JMP] edilen
lokasyonun adını [IKINCI_INT_DEGER_101_ISE] olarak
belirleyebiliriz.
İsim çok uygun olmasa da tüm CASE dallarından hata
alınmadan atlanması gereken kod bölümünü daha
rahat görebiliyoruz.
117. 3. AŞAMA
Son aşamada [EBP + var_D] offset'inde bulunan değer gireceğimiz ikinci
değer olan [CHAR] tipindeki [1] byte'lık değer ile karşılaştırılıyor.
[var_D] lokal değişkeninin değeri ise yukarıda atanmıştı.
118. 3. AŞAMA
Geldiğimiz yola geri dönersek 1.
INT değerinin [0] olduğu durumda
[var_D] lokal değişkenine atanan
değer [0x62] imiş.
119. 3. AŞAMA
2. olarak gireceğimiz verinin tipi
karakter tipinde bir veri olduğu için
[0x62] değerini context menü ile
veya [R] harfine basarak ASCII
karaktere dönüştürelim
120. 3. AŞAMA
Seçtiğimiz dal için geçerli olan veriler
aşağıdaki gibi:
1. INT – [0]
2. CHAR – [b]
3. INT – [101]
121. 3. AŞAMA
Girdi dosyamıza aşağıdaki satırı
ekleyelim (veya siz seçtiğiniz dal için
belirlediğiniz rakamları girebilirsiniz):
0 b 101
123. NE ÖĞRENDİK
Analiz hakkında
• Switch .. Case deyiminin assembly kodunda Jump Table
kullanılarak uygulanma şekli
• Analiz ettiğimiz kodun kapsamını ihtiyaçlarımıza göre çizebilme,
girdi ve çıktıları dikkate alarak analiz zamanımızı ekonomik
kullanabilme
125. ÖN BİLGİ
RECURSIVE [ÖZYİNELİ] FONKSİYONLAR
Kendini çağıran fonksiyonlara verilen isimdir.
Genellikle aşağıdaki algoritmaların uygulanmasında karşılaşılırlar:
• Böl ve fethet algoritmalarında
• Sıralama (sorting) algoritmalarında
• Dizin, file system arama [tree ve graph traversal] gibi
algoritmalarında
Gözle analizleri çok zor olduğu için analiz sırasında [PSEUDO CODE]
yöntemi ile algoritmanın yazılmasında büyük fayda vardır.
127. 4. AŞAMA
Önceki fonksiyonlara benzer biçimde
yine okunan veriye bir referans
parametre olarak alınıyor.
Yine [sscanf] fonksiyonu çağrılıyor
ancak bu defa 2 INTEGER değer
okunmaya çalışılıyor.
128. 4. AŞAMA [arg_0] parametresinin adını daha
anlaşılır olması amacıyla
[PRM_GIRDI] olarak değiştirelim.
129. 4. AŞAMA
Alınan parametrenin kopyalandığı
lokal değişkenin, format string'in
adresinin atandığı lokal değişkenin
ve "sscanf" fonksiyonunun okuduğu
değerleri yerleştirdiği lokal
değişkenlerin adlarını önceki
çalışmamızda olduğu biçimde
değiştirelim.
130. 4. AŞAMA
Kodun bu bölümünü okuyarak okunan parametre sayısının [2] olması gerektiğini,
okunan 2. INTEGER'ın [ 1 < INT_2 < 5 ] aralığında olması gerektiğini söyleyebiliriz.
131. 4. AŞAMA
Derleyicimizin çağrılan fonksiyonlara parametre aktarmak için stackte ayrılmış olan bölümün
son kısımlarını kullanmayı tercih ettiğini ve Visual Studio gibi PUSH instruction'ları ile
parametre aktarımı gerçekleştirmediğini daha önce konuşmuştuk.
Bu bölümde daha önce format string pointer'ını ve fonksiyona verilen parametrenin bir
kopyasını saklamak için kullanılan lokal değişken alanlarının farklı amaçlarla kullanıldığını
görebiliyoruz.
Bu alanlar sırasıyla [INT_2] ve [5] değerlerini barındırarak çağrılan [sub_401603]
fonksiyonuna parametre aktarmak amacıyla kullanılıyor.
Bu bilgileri not edebilmek için IDA'nın comment özelliğini [;] kullanabiliriz.
132. 4. AŞAMA
Bu bölümde algoritmamızın başarılı sonuçlanabilmesi için çağrılan fonksiyonun
döndürdüğü değer ile [INT_1] değerinin aynı olması gerektiğini görebiliriz.
133. 4. AŞAMA
Bu aşamayı çözebilmemiz için çağrılan [sub_401603] fonksiyonunda uygulanan
algoritmayı çözmemiz lazım.
134. 4. AŞAMA [sub_401603] fonksiyonu tahmin
ettiğimiz gibi 2 parametre alıyor. 1.
parametrenin [0] ve [1] olduğu
durumlarda uygulama farklı dallardan
ilerliyor.
Bu biraz garip çünkü daha önce 1.
parametrenin değerinin [5] olacağını
görmüştük.
135. 4. AŞAMA Fonksiyonun aldığı parametrelerin
daha iyi anlaşılabilmesi için parametre
isimlerini [PRM_1_ILK_DEGER_5] ve
[PRM_2_INT_2] olarak değiştirelim
136. 4. AŞAMA Fonksiyon dallarını daha iyi
anlamlandırmak için 1. parametre'nin
[0]'dan büyük olması halinde atlanan
kod adresine
[ILK_PARAMETRE_1_VE_DAHABUYUK]
diyelim.
137. 4. AŞAMA
En sağdaki dal ise 1. parametrenin [2]
veya daha üstü olduğu durumda
işletiliyor. Bu nedenle bu dalın
adresine de
[ILK_PARAMETRE_2_VE_DAHABUYUK]
adını verebiliriz.
138. 4. AŞAMA
1. parametre'nin [0] olması halinde fonksiyon [0] değerini
döndürüyor, [1] olması halinde ise [2. parametre'nin
değeri]ni döndürüyor. (IDA PRO'da en alttaki kutuda
[var_8] lokal değişkeninin değerinin [EAX]'e atandığını
görebilirsiniz.)
Bu tespitlerimizi not etmek için de [;] comment işaretini
kullanarak ilgili kodların yanına kayıt düşebiliriz.
139. 4. AŞAMA
1. parametre'nin "2" veya daha büyük olduğu
koşulda devreye giren kod bölümünde [CALL] edilen
fonksiyon adlarına baktığımızda fonksiyonun
kendisini çağırdığını görüyoruz.
Bu durum fonksiyona ilk parametre olarak "5"
değerini sabit değer olarak vermemize rağmen
neden [0] ve [1] değerleri ile ilgili kod bölümlerinin
var olduğuna dair bize biraz ışık tutuyor. Belki de 1.
parametre fonksiyonun sonraki recursive
çağrılmaları öncesinde daha farklı 1. parametre
değerleri alıyordur.
140. 4. AŞAMA
Fonksiyonumuzun tam olarak ne yaptığını henüz çözmüş değiliz, ancak onu
şimdilik de olsa [RECURSIVE_FUNC] olarak değiştirebiliriz. Bu isim biraz da olsa
kodun okunabilirliğini artıracaktır.
141. 4. AŞAMA [RECURSIVE_FUNC] fonksiyonunun
stack frame'ine göz attığımızda stack'in
uç noktasında [var_10] ve [var_C]
değişkenleri bulunduğunu görüyoruz.
143. 4. AŞAMA
[EBX] register'ı önce 1. recursive çağrının sonucunu saklamak için kullanılıyor.
Daha sonra da [N] rakamı, yani 2. parametre [EBX] register'ına ekleniyor.
Kodun aşağı bölümlerinde 2. recursive çağrının sonucunun da [EBX]
register'ına eklendiğini ve toplam rakamın da [var_8] lokal değişkenine
atandıktan sonra fonksiyon return değeri olarak döndürüldüğünü görebiliriz.
144. 4. AŞAMA 2. çağrının 1. parametresi olarak da
[M-2] rakamının kullanıldığını
görebiliriz.
145. 4. AŞAMA
2. recursive çağrının 2. parametresi olarak da [N] rakamı kullanılıyor. Yani 2.
çağrı F(M-2,N) şeklinde gerçekleşiyor.
146. 4. AŞAMA
2. recursive çağrının sonucu daha önce [EBX]
register'ında saklanan veriye ekleniyor ve bu
değer fonksiyonun çıktısı olarak
döndürülüyor. Yani fonksiyonumuzu
aşağıdaki gibi tanımlayabiliriz:
F(M,N) = F(M-1,N)+F(M-2,N)+N
147. KURALLAR
Bu aşamayı geçmek için tespit ettiğimiz kuralları sıralarsak:
• M: Birinci parametre
• N: İkinci parametre
• M=0 ise 0 döndür [F(0,N)=0]
• M=1 ise N'i döndür [F(1,N)=N]
• M=5'tir
• N 2'den küçük ve 4'ten büyük olamaz
• FORMÜL => F(M,N) = F(M-1,N) + F(M-2,N) + N
4. AŞAMA
148. İlk parametre "5" olmak zorunda olduğuna göre fonksiyonun F(5,2)
parametreleri ile vereceği yanıtı hesaplamaya çalışalım.
• F(5,2)=F(4,2)+F(3,2)+2
• F(5,2)=[F(3,2)+F(2,2)+2]+[F(2,2)+2+2]+2
• F(5,2)=[[F(2,2)+2+2]+[2+0+2]+2]+[[2+0+2]+2+2]+2
• F(5,2)=[[[2+0+2]+2+2]+[2+0+2]+2]+[[2+0+2]+2+2]+2
• F(5,2)=24
4. AŞAMA
149. 4. AŞAMA
[RECURSIVE_FUNC] fonksiyonunun
çağrıldığı [ASAMA_4] fonksiyonumuza
dönersek recursive fonksiyonumuzdan
dönen sonucun [ASAMA_4]
fonksiyonuna verilen 1. INTEGER
rakam ile aynı olması gerektiğini
görebiliriz. 2. INTEGER değeri de
recursive fonksiyona parametre olarak
verilen 2. parametre değeri olacak.
152. NE ÖĞRENDİK
Recursive fonksiyonlar ve analizleri hakkında
• Recursive fonksiyonların gözle analizi çok zor olduğundan
[PSEUDO CODE] kullanılarak analiz edilmelerinde fayda vardır.
• Girdilerin üreteceği çıktıları tahmin etmeye çalışırken fonksiyon
çağrılarını daha fazla recursive çağrı yapılmayan durumlara
kadar indirgemek gereklidir.
154. ÖN BİLGİ
STRING/VERİ DECODE ETME
• Kodlama (encoding), kriptolama (encryption) gibi algoritmalarda
kullanılan veri dönüştürme yöntemidir.
• Okunan veri dönüştürüldükten sonra yine aynı adrese veya farklı
bir adrese kopyalanarak yazılır.
• Tek byte veya çoklu byte [WORD, DWORD] üzerinde işlem
yapılabilir.
• String decode etme durumlarında veri IDA'da [HEX] olarak
görüntüleneceğinden veri tipini değiştirme veya bir ASCII tablo
kullanma ihtiyacı olabilir.
• Genel algoritma:
• Bellekten oku
• Dönüştür
• Belleğe yaz
155. ÖN BİLGİ
BIT MASK İLE MASKELEME
• Belli bir verinin belirli [bit]'lerini sabit bırakmak ve diğerlerini
[0]'lamak için kullanılan yöntemdir.
• Logical [AND] operatörü ve bir [Bit Mask] kullanılarak
gerçekleştirilir.
Yukarıdaki işlemde [EAX]
register'ının son nibble'ı (4 bit'i) hariç
tüm bit'leri sıfırlanacaktır.
159. 5. AŞAMA
Daha önce analiz ettiğimiz ve
[MTN_UZUNLUK] olarak
isimlendirdiğimiz fonksiyon tekrar
karşımıza çıktı.
Stack frame'in sonunda [var_38]
alanı var ve fonksiyon bu adresteki
veriyi parametre olarak alıyor olmalı.
160. 5. AŞAMA
[var_38] alanına [PRM_GIRDI]
parametresi kopyalandığı için
okunablirliği artırmak adına bu alanı
[L_PRM_GIRDI] olarak değiştirelim.
161. 5. AŞAMA
Daha önceki analizimizden
[MTN_UZUNLUK] fonksiyonunun
kendisine parametre olarak verilen
metnin içindeki karakter sayısını
döndürdüğünü biliyoruz.
Döndürülen değer [6] rakamı ile
karşılaştırılıyor ve eğer [6]'dan farklı
ise uygulamayı sonlandırmak üzere
[HATALI_DENEME] fonksiyonu
çağrılıyor.
162. 5. AŞAMA
Kodun devamına baktığımızda bir
döngü görüyoruz ve [var_C]
değişkeni sayaç olarak kullanılıyor.
Döngünün başında [0] olarak
belirlenen sayaç değeri her döngü de
[5]'ten büyük mü diye kontrol
ediliyor.
164. 5. AŞAMA
[SAYAC] sağ alttaki kutuda
bulunan [INC] instruction'ı ile
[1] artırılıyor.
for (i = 0; i < 6; i++){
...
}
165. 5. AŞAMA
Comment'lerle açıklanmış olan bu bölümde ilk 3 instruction sonucunda [var_28]
alandan [SAYAC] offset'in adresi [EDX]e atanıyor. Bu adres biraz sonra yapılacak
işlemin sonucunun yazılacağı adres olarak kullanılacak.
4,5,6. instruction'larda fonksiyonun girdi alanının (PRM_GIRDI) [SAYAC]
offset'indeki BYTE değeri [0xF] değeri ile binary [AND] işlemine tabi tutuluyor. Yani
bu byte'ın sadece düşük değerli yarısı [4 bit] [EAX] register'ında kalıyor.
166. 5. AŞAMA
Her bir döngüde okunan girdi karakterinin son 4 bit'i
[0x403000] adresinden itibaren offset değeri olarak
kullanılıyor ve bu offset'teki byte değeri [EAX]'e atanıyor.
168. 5. AŞAMA
[0x403000] adresinden başlayan alandaki verilere göz
attığımızda bu adresten itibaren belli bir offset'ten atanan [1]
byte'lık verinin bir karakter olduğunu tahmin edebiliriz.
169. 5. AŞAMA
Atanmış olan karakter [var_28] adresinden itibaren her
döngüde [1] byte ilerleyerek yazılıyor.
171. 5. AŞAMA
Toplam 6 karakter [var_28] adresinden başlayarak yazıldıktan sonra döngü
sonlanıyor ve [var_22] adresine [0] yazılmış. [0] yani [NULL] byte'ı C string'leri
için string'in sonunu belirtiyor. Bir diğer deyişle [var_28] adresinden başlayan
veri sonuna eklenen null byte ile bir C string'ine çevrilmiş.
173. 5. AŞAMA
Daha önceden analiz ettiğimiz [MTN_KARSILASTIR] fonksiyonu çağrıldığın da
stack frame'in en üzerinde [var_38] ve [var_34] alanlarının bulunduğunu
görebiliriz. Tabi daha önce [var_38]'i [L_PRM_GIRDI] adıyla değiştirmiştik,
ancak bu alan burada farklı bir veriyi tutmak üzere kullanılacak.
175. 5. AŞAMA
Eğer [var_38]'de (şimdiki adı ile L_PRM_GIRDI) bulunan string [btrisk]
string'inden farklı ise [MTN_KARSILASTIR] fonksiyonunun [1] döndürmesi
gerekiyor (daha önceki analizimizde bunu tespit etmiştik).
176. 5. AŞAMA
2 string birbirinden farklı ise uygulamamız sonlanıyor. Eğer üretilen string
[btrisk] string'iyle aynıysa bu aşamayı başarı ile tamamlamış olmamız
gerekiyor.
177. 5. AŞAMA
KURALLAR
Bu aşamayı geçmek için tespit ettiğimiz kuralları sıralarsak:
• 6 karakterli bir string girmeliyiz
• Her bir karakterin byte değerinin son nibble'ını [son 4
bit'ini] 0x403000 adresinden itibaren offset olarak
kullanarak bir karakter dizisi elde etmeliyiz
• Elde ettiğimiz yeni karakter dizisi "btrisk" olmalı
178. 5. AŞAMA
Sondan başa doğru ilerlersek [btrisk] dizisini oluşturmak için
önce doğru indeks'leri (offset'leri) bulmalıyız.
Sıra:1 Indeks:13 [0xD]
Sıra:2 Indeks:11 [0xB]
Sıra:3 Indeks:6 [0x6]
Sıra:4 Indeks:4 [0x4]
Sıra:5 Indeks:7 [0x7]
Sıra:6 Indeks:14 [0xE]
0xD
0xB
0x6
0x4
0x7
0xE