Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

BTRisk X86 Tersine Mühendislik Eğitim Sunumu - Bölüm-2

254 views

Published on

BTRisk X86 Tersine Mühendislik Eğitim Sunumu - Bölüm-2

Published in: Engineering
  • Be the first to comment

BTRisk X86 Tersine Mühendislik Eğitim Sunumu - Bölüm-2

  1. 1. X86 TERSİNE MÜHENDİSLİK EĞİTİMİ BÖLÜM-2 İSTANBUL blog.btrisk.com @btrisk /btrisktv /btrisk
  2. 2. OYUN 1. AŞAMA
  3. 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. 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. 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. 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. 7. ÖN BİLGİ IDA PRO HAKKINDA TEMEL BİLGİLER IDA arayüzü hakkındaki temel bilgiler • Işıklandırma (highlighting)
  8. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  18. 18. 1. AŞAMA İlk olarak basit bir dinamik analiz ile başlıyoruz.
  19. 19. 1. AŞAMA Dinamik analiz sırasında gözlemlediğimiz string'lerden birisini Strings penceresinden bularak çift tıklayalım.
  20. 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. 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.
  22. 22. 1. AŞAMA [Tersine muhendislik oyunumuza hos geldiniz.] string'ine referans verilen fonksiyonumuz bu fonksiyon.
  23. 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. 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. 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. 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. 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. 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.
  29. 29. Fonksiyon adını değiştirdikten sonra fonksiyonun görünümü. 1. AŞAMA
  30. 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. 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. 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. 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. 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. 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. 36. 1. AŞAMA Bu fonksiyonu [HATALI_DENEME] olarak isimlendirelim. Yaptığı iş sadece [8] statü koduyla uygulamayı sonlandırmak.
  37. 37. 1. AŞAMA Çıkış yapan fonksiyonu [RENAME] ettikten sonra kodu biraz daha rahat okunur hale getirmiş olduk.
  38. 38. 1. AŞAMA İlk adımda çağrılan fonksiyonun adını da [ASAMA_1] olarak rename edebiliriz.
  39. 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.
  40. 40. 1. AŞAMA Uygulamanın anlaşılabilirliğini artırmak için [ASAMA_1] fonksiyonunun aldığı parametreye isim verelim.
  41. 41. 1. AŞAMA Fonksiyonun aldığı parametreye [PRM_GIRDI] adını verelim.
  42. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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.
  52. 52. Parametre isimlerini düzenlediğimizde kodun aşağı bölümlerinde değeri atanan lokal değişkenlerin de kullanım amaçlarını daha iyi anlayabiliriz. 1. AŞAMA
  53. 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. 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. 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. 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. 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. 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. 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. 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
  61. 61. 1. AŞAMA Tahminimiz olan [asdf2009] metnini denediğimizde haklı olduğumuzu görebiliriz.
  62. 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. 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
  64. 64. OYUN 2. AŞAMA
  65. 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. 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. 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. 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. 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
  70. 70. 2. AŞAMA Öncelikle fonksiyonun aldığı parametreyi daha belirgin hale getirmek amacıyla [RENAME] edelim.
  71. 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. 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.
  73. 73. 2. AŞAMA [sub_401974] fonksiyonuna geçtiğimizde bu fonksiyonun [2] parametre aldığı tahminimizin doğru olduğunu IDA'nın yaptığı [arg_0] ve [arg_4] parametre çıkarımlarından görebiliriz.
  74. 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. 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. 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.
  77. 77. 2. AŞAMA Çağrılan [sub_401974] fonksiyonunun aldığı parametreleri de anlamlandırmak için [arg_0] ve [arg_4] adlarını da değiştirelim.
  78. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 94. 2. AŞAMA Benzeri bir tahmini uygulamanın string'lerini inceleyerek de yapabilirdik.
  95. 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.
  96. 96. 2. AŞAMA Uygulamaya parametre olarak dosya verdiğimizde de uygulamanın sağlıklı olarak çalıştığını görebiliriz.
  97. 97. 2. AŞAMA [2.] yanıtımızı da dosyanın içine yazarak uygulamamızı çalıştıralım.
  98. 98. 2. AŞAMA [2.] aşamadaki tahminimizin de doğru olduğunu görebiliriz.
  99. 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
  100. 100. NE ÖĞRENDİK Analiz hakkında • [PSEUDO KOD] yöntemi ile analiz edilen algoritmaları dokümante etme
  101. 101. OYUN 3. AŞAMA
  102. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  122. 122. 3. AŞAMA
  123. 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
  124. 124. OYUN 4. AŞAMA
  125. 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.
  126. 126. 4. AŞAMA Ana fonksiyonda 4. aşama olarak tahmin ettiğimiz fonksiyonun içine girelim.
  127. 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. 128. 4. AŞAMA [arg_0] parametresinin adını daha anlaşılır olması amacıyla [PRM_GIRDI] olarak değiştirelim.
  129. 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. 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. 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. 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. 133. 4. AŞAMA Bu aşamayı çözebilmemiz için çağrılan [sub_401603] fonksiyonunda uygulanan algoritmayı çözmemiz lazım.
  134. 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. 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. 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. 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. 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. 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. 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. 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.
  142. 142. 4. AŞAMA Fonksiyonun 1. parametresine [M], 2. parametresine [N] dersek 1. recursive çağrının [M-1] ve [N] parametreleri ile yapıldığını görebiliriz.
  143. 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. 144. 4. AŞAMA 2. çağrının 1. parametresi olarak da [M-2] rakamının kullanıldığını görebiliriz.
  145. 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. 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. 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. 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. 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.
  150. 150. 4. AŞAMA Yaptığımız hesaplamalara göre 4. aşama için [24 2] girdileri başarılı olmalı.
  151. 151. 4. AŞAMA
  152. 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.
  153. 153. OYUN 5. AŞAMA
  154. 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. 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.
  156. 156. 5. AŞAMA Ana fonksiyonda 5. aşama olarak tahmin ettiğimiz fonksiyonun içine girelim.
  157. 157. 5. AŞAMA IDA'nın da yardımıyla [ASAMA_5] fonksiyonunun da tek bir parametre aldığını görebiliyoruz.
  158. 158. 5. AŞAMA Okunabilirliği biraz daha artırabilmek için alınan parametrenin adını [PRM_GIRDI] olarak değiştirelim.
  159. 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. 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. 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. 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.
  163. 163. 5. AŞAMA Anlaşılabilirliği biraz daha artırmak için [var_C] değişkeninin adını [SAYAC] olarak değiştirebiliriz.
  164. 164. 5. AŞAMA [SAYAC] sağ alttaki kutuda bulunan [INC] instruction'ı ile [1] artırılıyor. for (i = 0; i < 6; i++){ ... }
  165. 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. 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.
  167. 167. 5. AŞAMA [0x403000] adresine göz attığımızda burada bir dizi karakterin veri olarak bulunduğunu görüyoruz.
  168. 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. 169. 5. AŞAMA Atanmış olan karakter [var_28] adresinden itibaren her döngüde [1] byte ilerleyerek yazılıyor.
  170. 170. 5. AŞAMA [var_28] adresinin kullanım amacıyla ilgili tespitimizi de yorum satırı olarak kaydedebiliriz.
  171. 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ş.
  172. 172. 5. AŞAMA [var_34] değişkenine [btrisk] string'inin adresi yazıldıktan sonra [var_28] alanının adresi [EAX] register'ına atanıyor.
  173. 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.
  174. 174. 5. AŞAMA [MTN_KARSILASTIR] fonksiyonumuz daha önceki analizimizden de bildiğimiz üzere kendisine verilen 2 string'i birbiri ile karşılaştırıyor.
  175. 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. 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. 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. 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
  179. 179. 5. AŞAMA [0xDB647E] nibble'larını üretecek karakterleri bulmak için bir ASCII tablosundan faydalanabiliriz.
  180. 180. 5. AŞAMA İstediğimiz sonucu verecek örnek bir karakter dizisi olarak [MKVT7N]'yi deneyebiliriz.
  181. 181. 5. AŞAMA
  182. 182. NE ÖĞRENDİK Veri encode etme analizi hakkında • ASCII tablosundan faydalanma • Bit maskeleme • Veri dönüştürme (encode) etme örneği

×