SİTE
İÇİ ARAMA |
|
Anket |
.NET Yeniliklerini Takip Etmek Sizin İçin Nekadar Önemli ?
Diğer anketler ve sonuçları için tıklayın.
|
|
|
|
|
Yeni bir decompile aracı : FOX
|
C#nedir?com üyelerinden ve forum moderatörlerinden Ozcan Değirmenci tarafından geliştirilen ve Amerikan firması Xenocode'da satılan Fox Kod Analiz aracının final sürümü çıktı. C#nedir?com forumlarında tartışılmaya başlanan bu fikir artık dünya çapında en çok kullanılan decompiler aracı olma yolunda emin adımlarla ilerliyor. Özcan Değirmenci Fox'un serüvenini sizler için kaleme aldı...
|
Özcan Değirmenci,
C#nedir?com üyelerinin özellikle de C#nedir?com forumlarını takip eden üyelerimizin
yakından tanıdığı isim. Bundan yaklaşık 1.5 yıl önce forumlarda derlenmiş .NET
uygulamalarının ILden C# yada başka bir .NET diline rahatlıkla dönüştürülebileceği
üzerine tartışmalar geçiyordu. Bu tartışmalar kimini memnun derken kimini mutsuz
ediyordu. Her fırsatta .NET platformunun IL kodunu gizleme gibi bir amacı olmadığını
vurgulayan Özcan Değirmenci decompile işleminin rahatlıkla yapılabileceğini
düşünüyordu. Forumda decompile işlemleri üzerine derin tartışmalar yaşanırken
Özcan Değirmenci daha sonra dünyadaki sayılı decompilerların arasına girecek
olan aracını kodlamaya başlamıştı bile. Aradan bir iki ay geçti ve fikri C#nedir?com
forumlarında çıkan ve Fox adını koyduğu decompiler aracının ilk sürümünü yine
C#nedir?com forumlarında duyurdu. Bir çok üyemiz bu projede testçi olarak yer
aldı ve Foxun gelişimine katkıda bulundu. Nihayet Foxun çalışabilen versiyonu
çıktığında bir çok kişi tarafından denendi ve beğenildi. Beğenenlerden biriside
merkezi Seattleda (USA) bulunan kod koruma ve decompile konusunda uzman bir
firma olan Xenocode oldu.
Böylece Foxun bu gelişimi C#nedir?com forumlarından çıkarak Xenocode firmasının
ticari bir uygulamasına dönüştü. Evet, Fox tüm hakları ile Özcan Değirmenci
tarafından Xenocode firmasına satıldı.
Fox Decompiler aracının 14 günlük deneme sürümünü http://www.xenocode.com/Products/Fox/
adresinden indirebilirsiniz.
Fox aracının C#nedir?comdaki gelişim evresini merak ediyorsanız aşağıdaki bağlantılarda
geçen bazı tartışmaları okuyabilirsiniz.
Decompiler
mevzusunun tartışıldığı mesajları
Fox
ile ilgili tartışmalar
Yine
Fox hakkında mesajlar
....
Ayrıca http://www.csharpnedir.com/forum2/search_form.asp
adresindeki arama kutucuğuna "fox" yada "decompiler" kelimelerini
yazıp aratırsanız daha fazla içeriğe erişebilirsiniz.
|
Şimdi
binlerce kişi tarafından trial versiyonun indirildiğini belirten Özcan Değirmenci
gelen tepkilerden oldukça memnun olduğunu belirtiyor. Değirmenci, son versiyonu
geçtiğimiz günlerde yayınlanan Foxun serüvenini ve gelişim aşamalarını
birazda teknik detaylarını C#nedir?com için kaleme aldı. Bundan sonrasını
kendisinden dinleyelim :
"Bundan 1,5 sene öncesinde
www.csharpnedir.com forumlarında
ilk defa tartıştığımız ve duyurusunu yaptığımız Fox o tarihte yazılmaya
başlandı. İlk 5 ay kadar çok vaktim olmadığı için bakamadım. Ama daha sonraki
4 ay süresinde bu konuya daha fazla vakit ayırıp Fox’un ilk betasını çıkarttım.
Fox o zamanlar cok daha işin başında olduğu için decompile ederken bir çok
hatalar alıyorduk. Çünkü decompile mantığında metadata ları okumak işin
en basit kısmıdır. MetaDatalar okunduktan sonra o metadatal arı ayıklayıp(parse)
anlamlı tablolarda ortaya çıkardıktan sonra bir methodun gövdesini dosya
içinden okuyup daha sonra bu gövdeyi anlaşılır kod haline çevirmek en zor
kısmıdır."
Özcan Değirmenci |
Foxun Serüveni
Bu makalede sizlere
kısaca Fox’un gelişme sürecini, bir decompiler nasıl yazılırı ve decompilerlardan
nasıl korunuluru anlatmaya çalışacağım. Konuyu elimden geldiği kadar kısaca anlatmaya
çalıştım ancak yinede uzun bir makale olduğu için hepinizden özür dilerim… Microsoft’un en
yeni ve en gözde yazılım geliştirme platformu olan .NET ilk çıktığında benimde
içine dahil olduğum bir çok insan bu platformdaki açıklara dikkat bile etmeden
platformun onlara sağladığı geniş imkanlara ve program yazmadaki rahatlıklarına
kapılıp bu platforma geciş yaptı. Aslında JAVA programcıları için çok fazla
ses getirdiği söylenmesede özellikle Visual Basic yada ASP kullanan yazılım
geliştiricileri açısından .NET gerçekten büyük bir devrimdi. Aslında coğu programcı
sadece sözlere kanıp .NET ile uğraşmaya başladı bile diyebiliriz. Ortada herşeyin
çok daha kolay yapıldığına dair sözler dolaşıyordu ama yinede kimse olayı çokta
bilmiyordu. Ancak zaman geçtikçe görüldüki Microsoft cidden doğruyu söylemişti.
Platform gerçekten çok esnek davranıyordu. Ve yine beklenmedik şekilde çok az
bug ile ve en azından Microsoft platformlarının hepsi ile son derece uyumlu
çalışıyordu. Bütün bunların yanında birde dünyanın belkide o zaman kadarki en
sağlam editörüde .NET geliştiricilerinden yana olunca ortaya tam bir şaheser
çıktı diye düşünüyorduk. Ne ilk çalışmadaki bekleme nede editörün aşırı hafıza
kullanımı bizim açımızdan bir sorun değildi. O zamanlar bende .NET’i ilk duyduğumda
merak etmiş ve direk onla ilgilenmeye başlamıştım. Ancak daha sonra zaman geçtikçe
bu platformdaki açıklar üzerine düşünmeye başladım. Aslında kendi kendime düşünmekten
ziyade bir arkadaşın tavsiyesi uzerine indirdiğim ‘Anakrino’
programı ile şoke olmuştum. Programla kendi yazdığım exe nin kodlarını hemen
hemen ayni şekilde görüntüleyebiliyordum. Decompiler sözcüğü ile ilk defa o
zamanlar haşır neşir oldum. Bu sözcük beni yeni program yazmaya başlamış biri
olarak çok etkilemişti. Açıkcası karizmatik geldi bile denilebilir.
O günden sonra bu konu uzerine biraz daha ilgilenir oldum. Daha sonrasında yine
bir arkadaşım sayesinde Reflector’la tanıştım. ‘Reflector’
‘Anakrino’ya göre daha iyi decompile eden bir araçtı. Hatta daha sonra Lutz
Roeder’den öğrendiğime göre Anakarino ile Reflector aslında aynı
amaçla beraber başlamışlar ancak daha sonra aralarındaki anlaşmazlıktan dolayı
Saurik ayrılmış ve kendi bir tool çıkarmış ve Reflector ayrı bir tool olarak
çıkmış. Bu her iki decompilerın yapabildikleri gerçekten çok ilginç gelmişti
bana.
Aradan geçen zamanlar esnasında decompilerlar bir çok insan tarafından hala
yeni yeni duyulmaya başlıyordu ki bu nedenle www.csharpnedir.com’da
bu konuda bir çok soru çıkmaya başlamıştı. Bu sorularda daha çok bunların ne
kadar rahatsız edici olduğu üzerine tartışıyorduk. Ama benim için hayatta en
sevmediğim şey belkide başkalarının yaptıkları üzerine konuşmak olduğu için
o zamanlar bende bir tane decompiler aracı yazmaya karar verdim. İşte yaklaşık
bundan 1,5 sene öncesinde www.csharpnedir.com forumlarında ilk defa tartıştığımız
ve duyurusunu yaptığımız Fox o tarihte yazılmaya başlandı. İlk 5 ay kadar çok
vaktim olmadığı için bakamadım. Ama daha sonraki 4 ay süresinde bu konuya daha
fazla vakit ayırıp Fox’un ilk betasını çıkarttım. Fox o zamanlar cok daha işin
başında olduğu için decompile ederken bir çok hatalar alıyorduk. Çünkü decompile
mantığında metadata ları okumak işin en basit kısmıdır. MetaDatalar okunduktan
sonra o meta dataları parse edip anlamlı tablolarda ortaya çıktıktan sonra bir
methodun gövdesini dosya içinden okuyup daha sonra bu gövdeyi anlaşılır kod
haline çevirmek en zor kısmıdır. Bu esnada gövde okunup IL ifadeleri ortaya
çıktıktan sonra bir compilerin bu kodları nasıl oluşturabileceğini düşünüp
buna göre ters işlem yapıp ortaya anlaşılır kod çıkartmak gerekir. İşte işin
en zor kısmı olan bu kısım surekli gelişmesi gereken bir kısımdır ki yeni çıkmış
bir decompilerda bu tip hatalardan kaynaklı hatalar oluşması çok normaldir.
Ancak isimlerini buraya yazamayacağım kadar çok arkadaş o zamanlar www.cshaprnedir.com’daki
yazılarımı görmezden gelmeyip bana testler konusunda destek çıktılar. Bu esnada
Fox biraz daha gelişti ve son beta 1.0.8 versiyonu ortaya çıktı.
Bu noktada benim için aslında olayın çok daha ileri gitmesi için bir sebep yok
görünüyordu. Çünkü ben istediğim şeyi gösterip bunu bir Türk’ün de isterse yapabileceğini
ispatlamıştım. Hatta bir ara Fox’un Open Source olup olmaması konusunda yaptığım
anket nedeni ile Reflector’un yazarı ‘Lutz Roeder’ den bile tepki alıcak kadar
ismi duyulmuştu o zamanlar. Ayrıca istediğimizde neler yapabileceğimizin de
bir göstergesi olmuştu. Programcılık yaparken tek ihtiyacımız olan şey cesaret
edip başlamaktir. Ben Fox’u yazacağım dediğimde hiç unutmam bir arkadaşım bana
“Bence bunu siteden duyurma çünkü ne yapacağını bile bilmeden duyurup da yapamazsan
insanlara karşı sorumlu olursun” demişti. Belki kendi açısından haklı idi ama
benim kendi düşünceme göre bir işi yapmayı istemek ve başlamak onu yapmakla
hemen hemen ayni şeydir. Gerisi sadece zaman meselesidir. Nitekim öylede oldu;
işe başlarken meta datalar hakkında çok bilgisi olmayan, CodeDom u doğru dürüst
sınıflandırmayı bile bilmeyen, IL den çok çok az anlayan biri olarak elimdeki
4 Microsoft dökümanı sayesinde bütün bunlar zamanla çözüldü ve ortaya bir decompiler
aracı çıktı. Beta sürümünden sonra çıkan başka terslikler ve engeller nedeni
ile Fox o beta asamaşında uzun süre kalmak zorunda kaldı. Ancak bir süre sonra
Fox konusunda “XenoCode” benle gürüşmek istedi ve Fox’u tüm hakları ile satın
almak istediklerini belirtti. Yapılan görüşmeler sonucunda Fox’u satın aldılar
ve bende onlar icin Fox’u geliştirmeye devam ettim. Fox o günden sonra ‘Xenocode
Fox’ oldu. Bügün ise Fox şu anda en iyi .NET decompiler olarak lanse edilen
ve 2 haftada 20.000 download!a ulaşan bir program oldu. Bu beklenmedik başarı
bügün Xenocode’u ve beni Fox’u daha da geliştirmeye itiyor.
Şu anda bir çok decompilerın çözemediği yada Label’larla çözdüğü kodları bile
daha anlamlı kodlar haline çevirebilen Fox bunun yanında “Export Visual
Studio .NET 2005” özelliği ile de ilgi çekiyor. Bu özellik sayesinde
Fox verilen bir exe yada dll’i sanki siz onu proje yapıp da sonra build etmişsiniz
gibi geriye dönüşük Proje dosyasını oluşturuyor. Bu esnada Component, Form,
UserControl gibi designer destekli class’ları anlıyor varsa bunların resx dosyalarını
onlarla ilişkilendiriyor ve hatta AssemblyInfo.cs, csproj ve sln dosyalarını
bile otomatik üretebiliyor. Oluşan bu projeye VS.NET ile açıp tekrar buil edebilirsiniz
hemde hiç kod değiştirmenize gerek kalmadan. .NET 2.0 la gelen Genericslere
tam desteği var. Her türlü DllImport yada Marshaled call ifadelerini
tam manası ile çözüyor. Normal attribute çözümüni yapabildiği gibi; return:
ve param: attribitute türlerinide çözebiliyor. Ayrıca sınıfların Base ve Derived
tiplerini gösterebiliyor. Text Search yapabiliyor. .resources dosyalarını tam
manası ile çözüp resx leri oluşturabiliyor. System.Resources.ResourceReader
1.0 ve 2.0 versiyonuna tam desteği var. Yani bu ResourceReader ların hangisi
için hazırlanmış .resources dosyası olursa olsun onu tam bir resx şeklinde çözebiliyor.
Bilinen resx data tiplerinin yanında application/x-microsoft.net.object.binary.base64
ve application/x-microsoft.net.object.bytearray.base64 her ikisinede desteği
mevcut. Unicode textleri otomatik algılayıp ona göre yazabiliyor. Ayrıca hyperlinked
görüntü sayesinde istediğiniz tip yda üye elemana tek tıkla ulaşabiliyorsunuz.
C#, VB.NET ve IL dillerinde dönüşüm yapabiliyor. Yine C# ve VB.NET dillerinin
her ikisindede Export edebilme özelliği mevcut. Ayrıca Tab Page görünümü sayesinde
aynı kodu iki dilde açıp karşılaştırma yapabilirsiniz. Ayrıca Fox da oto casting
vardır. Yani decompile edilen kodda method call lar kontrol edilir ve eğer tipler
uyumsuzsa otomatik casting kodları ekler. Bunun gibi daha bir çok decompile
stratejisini yapısında barındırır.
Fox’un aslında biz Türk programcılar için en önemli özelliği ise bizimde istediğimizde
bir şeyler yapabildiğimizdir ve sonuçta yaptığımız şey dünya standartlarında
ses getirecek cinsten bile olabilir. Zaten Fox’u yazarken bunu göstermeyi düsünüyordum
ve sanıyorum bugün bunu az çok ispatlamış bulunuyoruz. Dünyanın üzerine en cok
yatırım yapılan platformlarından birinde yazdığınız tüm kodları sanki build
edilmemiş gibi çözen bir ürün.
Fox ve benzeri
programlar çoğu yazılımcı tarafından eleştirilselerde emin olduğum bir şey varki
bu eleştirenlerin hemen hemen hepsi bu araçları kullandığıdır. Bu araçlar eleştiriliyor
çünkü; onların gözünde başkasının kodunu çalmaya yarayan araçlar. Fox onların
günlerce olan emeğine hiç aldırmaksızın tüm kodu çözmek için yaklaşık bir kaç
dakika harcıyor ve onların yaklaşık 1 yıllık emeğini çalıyor. Aslında olay benim
gözümde bu şekilde canlanmıyor. Benim gözümde ise Fox ve benzeri decompilerlar
birer kurtarıcı. Bir windows programcısı olarak Fox hem benim yapabileceklerimi
sürekli geliştiriyor hemde bir çok kez beni günlerce uğraşacağım bir şeyi cok
daha kısa sürede yapmama imkan sağlıyor. Örneğin bir windows kontrolünde yapmaya
calıştığım bir şeyin neden olmadığı konusunda takıldığımda Fox’la System.Windows.Forms.dll’ini
çözüp o control’ün kodlarına bakıp bu hatanın nereden kaynakladığını çok rahat
bulabiliyorum. Yada örneğin AxWebBroser tarzında basit bir browser objesi yazmak
için shdocvw.dll’i açıp içinden gerekli COM interfacelerine bakıp benzerlerini
yazıp kendim AxHost dan bir control türeterek istediğim işlevi yapacak bir Web
Browser türetebiliyorum. Bunlar işin cok basit kısımları. Bence bundan da önemli
olan şey Fox’dan öncede decompiler araçları vardı. Yani Fox’un olmaması durumu
değiştirmez. Birde kötü niyetli kullanımlar bizi bağlamaz. Eğer biz herşeyin
kötü niyetli kullanılacağını düşünüp ona göre davranmaya kalkarsak sonuçta üretim
yapmakdan çıkar adam akıllı işler yapamayız. Bu tip araçları kullanmaya başladığınızda
sizlerde işinize ne kadar yaradıklarının farkına varıcaksınız ve cidden böyle
bir araca sahip olduğunuz için sevineceksiniz. En azından ben kendi adıma Fox
yada benzeri araçlarsız program yazmamın daha zor olucağına inanıyorum.
Şimdi yukardaki uzun Fox hikayesinden çıkıp isterseniz bu decompile olayında
biraz daha işin içine girip bazı ip uçları vererek bu işi nasıl yaptığımızı
detaylandıralım. Tabii burada çok kısa değinebilmemiz ancak mümkün. Detayları
atlamak şartı ile olayın kısa mantığını anlatmak bile yeteri kadar uzun olucaktır.
100.000 satır kodu olan bir programın yaptığını burada detaylı anlatmak da çok
mantıklı olmasa gerek.
Biraz
da teknik detay ...
.NET Dosyaları birer PE(Portable Executable) dosyalarıdır. Bu dosyalar ilk iki
byte’ında 0x5a4d “MZ” değerini taşımak zorundadırlar. Daha sonra 0x3c offsetindeki
4 byte ise bize bu dosyanın PE flagının yerini belirtir. Buradan okunan 4 byte’ın
bize verdiği integer degerdeki offset e gidip 4 byte okuduğumuzda ‘PE\0\0’ yani
0x00004550 değerinin olması gerekiyor. Bu kontroller yapıldıktan sonra dosyanın
PE dosyası olup olmadığı anlaşılır. Daha sonra ise bu dosyadan COFF (Common
Object File Format) Headerı okunur. Bu header bize bu dll in hangi tipde makinalarda
calışması için compile edildiği, bazı COFF flaglarını ve OptionalHeaderSize
gibi bazı gerekli bilgilerini verir. Bu optional header size sayesinde
dosyanın DataSectionlarının offsetlerini bulabiliriz. COFF Header dan hemen
sonrasında ise OptionalHeaderlar gelir. Bunları üçe ayırabiliriz. StandartFields,
NTSpecificFields ve DataDirectories. StandartFields bize dosyanın exe yada dll
olduğunu belirten BaseOfCode (0x00400000 exe, 0x10000000 dll), exe ise EntryPoint
inin RVA (Relative Virtual Address) ini, CodeSize ını (genelde text section’ın
boyutu, daha dogrusu tum code sectionlarının toplam boyutu) gibi bazı önemli
fieldlar barındırır. NTSpecificFields ImageBase(dll, exe, CE_Exe), Section bilgileri,
SubSystem (WındowsGui, WindowsCui, Native etc.etc.), DllCharacteristic gibi
bazi bilgileri barındırır. DataDirectories ise; Export Table, Import Table,
Resource Table, Exception Table, Certificate Table gibi Image Data Directorylerin
boyutu ve RVA larını barındırır.
Daha sonra DataSection
RVA sına gidip buradan SectionHeaderlarını okuyabiliriz. Bir .NET çıktısında
COFFHeader da belirtilen NumberOfSections kadar section header vardır. Her section
header o section’ın RVAsını, RawDataSize gibi bilgileri barındırır. Bu headerları
kullanarak aradığımız bir RVAnın bu dosyada hangi section’a denk düştüğünü bulup
ona göre bu section’ın RVA sı + bizim aradığımız RVA şeklinde aradığımız yere
ulaşabiliriz.
Daha sonrasında
bu dosyanın CLIHeaderını DataDirectoryler listesindeki CLIHeader ın RVA sına
giderek okuyabiliriz. CLIHeader Meta Data, Resources, StrongNameSignature, CodeManagerTable,
VtableFixups, ExportAddressTableJumps, ManagedNativeHeader gibi ImageDataDirectoryleri
ve bazı flagları barındırır.
CLIHeader dan sonra
MetaData ları okuyabilmek için MetaDataHeader’ı okumamız gerekir. Bunun için
CLIHeader’ın Meta Data ImageDirectory sinin RVA sına gidip okuma işlemini gerçekleştirmemiz
gerekir. Meta Data header bazı flagların yanında en önemli olarak bize bu dosyanın
Heaplerinin bilgilerini verir. Bir .NET dosyasında 5 tane heap header vardır.
Bunlar; StringsHeap(#Strings), BlobHeap(#Blobs), GUIDHeap(#Guids), USHeap(#US)
ve Tables Heap(#-, #~ ) dir. Sırası ile açıklamak gerekirse;
StringHeap: dosyadaki stringlerin tutulduğu yerdir. Örneğin
method isimleri, method reference isimleri, field reference isimleri, field
isimleri hep burada tutulurlar.
BlobHeap: Bloblar komplike data yapılarıdır. Burada bir byte[] şeklinde
bulunan signaturelar çözülerek kullanıldıkları yere göre değişik anlamlar içeren
veriler barındırırlar. Örneğin bir method’a ait referans için o method’un tokeni
yada signature’ı gibi. .NET 2.0 la birlikte gelen Genericsler sayesinde bu heap’in
önemi daha da arttı ve artık burada TypeInstanceların ve Method Instantiations’lar
da tutulmaya başlandı ki bunlar bir generic type dan yeni bir type instance
yarattığınızda veya bir method’un generic çağrılmasında o instance’ın çözülebilmesi
için gerekli type signature’ları barındırır.
GUIDHeap: Programda kullandığınız GUID’leri barındırır.
USHeap: User String’leri yani kullanıcı tanımlı mesaj yada benzeri
stringleri içinde barındırır.
Tables Heap: MetaData Tablelarını barındırır.
Şimdi sıra MetaData Tablelarının kaçar elaman barındırdığını okumaya geldi.
Bunu okumak icin #~ yani TablesHeap header’da belirtilen offset e gideriz ve
orada sırası ile tüm table’ların kaç tane eleman barındırdığını okuruz. Daha
sonra sting heap’e gider ve oradan tüm stringleri okuruz. String heap de stringler
‘\0’ yani Null Terminated string array şeklinde barındırılır. Daha sonra blob
heap’e gider ve blobları okuruz. Bloblar sırası ile dizilmiş şekildedirler.
Her blob’un ilk başında onun encode edilmiş şekilde kaç byte’dan oluştuğu vardır.
Bu byte sayısı kadar byte[] ise onun datasını barındırır. Sonrada Guid heap’e
gider ve oradan 16 şar byte lık arrayler şeklinde guidleri okuruz. Daha sonra
US Heap’e gideriz ve yine blob da olduğu gibi peşpeşe gelen row lar şeklinde
her row un başında encode edilmiş int şeklinde kaç byte olduğunu okur sonrada
o kadar byte[] şeklinde string’i okuruz.
Burada kısaca .NET
in data tutma şeklinede değinmek gerekirse. .NET öncelikle çok sık kullanılan
data tutma şekillerinden biri encode edilmiş inttir. Encode edilmiş int dediğimiz
yapı ile int değerinin en az byte kullanılarak tutulması amaçlanmıştır. Bunu
şu şekilde çözümleyebiliriz.
int DecodeInt32(BinaryReader reader)
{
int
value = reader.ReadByte();
if
((value & 0x80) == 0)
return
value;
if
(value == 0xff)
return
-1;
if
((value & 0xc0) == 0x80)
return
((value & 0x3f) << 8) | reader.ReadByte());
return
((value & 0x3f) << 24) | (reader.ReadByte() << 16)
|
(reader.ReadByte() << 8) | reader.ReadByte();
}
.NET dosyalarında
bir GUID, Blob yada String Indexi ise 2 yada 4 bytelık bir sayısal değer olarak
tutulur. Bunu bulmak için MetaData Header da okuduğumuz HeapSizes değerini kullanabiliriz.
int stringLength
= (int) (((HeapSizes & 0x01) != 0) ? 4 : 2);
int guidLength = (int) (((HeapSizes & 0x02) != 0) ? 4 : 2);
int blobLength = (int) (((HeapSizes & 0x04) != 0) ? 4 : 2);
Buna göre eğer
.NET dosyasında herhangi bir yerde örneğin Blob okumamız gerektiğinde bu dosyanın
blupLengthine bakarız ve eğer 2 ise 2 byte okuruz aksi takdirde 4 byte okuruz.
Bu sayede .NET dosyasının boyutu küçültülmüş oluyor.
Yine aynı şey Meta
Data Tablelarında da geçerlidir.
MetaData Table
Header da bulunan 64 int elemanlı Rows dizisi bize bu dosyanın MetaDataTablelarının
her birindeki eleman sayılarını veriyordu. Buradan hareketle hangi Tableda
kaç eleman olduğuna bakarız ve eğer eleman sayısı 0xffff den küçükse bu durumda
2 bytelık bir değer büyükse 4 byte lık bir değer okuruz.
Örneklemek gerekirse
eğer dosya içinde bir TypeRef okumamız gerektiğinde TypeRef tableında yani
1. index deki Row countuna bakarız eger bu sayı 0xffff den kuçukse bu durumda
2 byte lık bir değer okuruz aksi halde 4 bytelık bir değer okuruz. Okuduğumuz
bu değer o table dan bir index i belirtir.
Şimdi kaldığımız
yerden devam edicek olursak. Heap okumasını tamamladığımıza göre şimdi sıra
en önemli yerlerden biri olan MetaData Tablelarını okumaya geldi. .NETte tanımlı
44 tane MetaDataTable vardır. Bunları yazmak ve önemlilerini kısaca yazmak gerekirse;
Module
= 0:
Module leri barındırır. Bir dll yada exe içinde birden fazla module barındırabilir.
Genelde bu sayı 1 dir. Yani her .NET exe sinde ve dll inde bir tane modül vardır.
Module Table ındaki her bir row için sırası ile: Generation, Name, MvId, EncId,
EncBaseId olmak üzere 5 tane colon vardır.
TypeRef = 1:
Bu assembly haricinde başka bir assemblyden çağrılan ve kullanılan tüm Type
Reference lar için birer row girilir. Örneğin siz int a diye bir değişken tanımladığınızda
burada otomatikman int için bir row oluşturulur. Çünkü int bir başka assembly
de (mscorlib.dll) tanımlıdır ve oradan kullanılır. TypeRef tableında her bir
row için: ResolutionScope, Name ve Namespace olmak üzere üç kolon mevcuttur.
TypeDef
= 2 :
Bu assembly içinde tanımladığınız tüm typelardır. Yani class, delegate, struct
ve enumlar burada birer row seklinde tutulurlar. Her bir TypeDef için sırası
ile Flags, Name, Namespace, Extends, FieldList ve MethodList olmak üzere 6 tane
colon bulunur.
FieldPointer
= 3 :
Field = 4 :
Bu assembly içindeki tüm TypeDeflerin herbirinin içindeki tüm fieldlar bu table
da bulunur. Yani yarattığınız tum class, struct ve enumlar içindeki fieldlar
buraya konulur. TypeDef deki type tanımlamalarındaki FieldList bu table da bir
index E karşılık gelir. Yani o typedef in fieldlarının bu table da hangi index
den başladığını bu sayede bulabilir ve fieldları buradan okuyabilirsiniz. Field
table ında her field için sırası ile; Flags, Name ve Signature olmak üzere 3
colon vardır.
MethodPointer
= 5:
MethodDef = 6:
Bu assembly içindeki yarattığınız tüm TypeDeflerin herbirinin içindeki tüm methodlar
bu table da bulunur. Yani yarattığınız tüm class, delegate ve struct ların methodları
buraya konulur. TypeDef deki type tanımlamalarınındaki MethodList bu table da
bir indexe karşılık gelir. Yani o typedef in methodlarının bu table da hangi
index den başladığını bu sayede bulabilir ve methodları buradan okuyabilirisiniz.
MethodDef table’ında her bir methoddef için sırası ile; RVA, ImplFlags, Flags,
Name, Signature ve ParamList olmak üzere 6 colon vardır.
ParamPointer
= 7 :
Param = 8 :
Bu assembly içinde yarattığınız tüm Type Def lerde yazdığınız tüm Methodların
kullandığı parametreler buraya konulur. Yani MethodDef table ındaki her method
un varsa parametreleri buraya konulur. MethodDef deki her row un ParamList i
bu tableda bir index’e denk gelir. Böylece o methodun parametreleri buradan
okuyabilirsiniz. Param table’ında her parametre için sırası ile; Flags, Sequence
ve Name olmak üzere üç kolon vardır.
InterfaceImpl = 9 :
Bir TypeDef in hangi interface leri implement ettiğini bulmak için kullanılır.
Yani her eğer bir Type Def bir interface i implement ediyorsa; Örneğin class
MyClass : IDisposable gibi; bu durumda bir row bu table da oluşur. Bu tabeldaki
her bir row da sırası ile Class ve Interface olmak üzere iki kolon vardır. Bu
kolonlardan ilki hangi TypeDef için bu row un olduğunu belirtir ki bizim örneğimizde
bu MyClass ve ikincisi ise TypeDef yada TypeRef tableında ki bir row a karşılık
gelerek hangi interface olduğunu bulmamızı sağlar. Tabi bizim örneğimizde IDisposable.
MemberRef
= 0xA :
Herhangi bir TypeDef, TypeRef, ModuleRef, MethodDef veya TypeSpec içindeki bir
member’a referas sağlamak adına kullanılır diyebiliriz. Burada her bir member
için oluşan row da sırası ile Class, Name ve Signature olmak üzere 3 kolon barındırır.
Burada Class hangi table la alakalı olduğunu bildirir.
Constant
= 0xB :
Tanımladığınız constantlar burada birer row şeklinde belirtilirler. Burad aher
bir row Type, Parent ve Value olmak üzere 3 kolon vardır. Burada Type burada
tutulan değerin Native Element Tipini belirtir. Parent ise Field, Parametre
yada Property table ında bir index belirtir. Value ise bir blob indexini belirtir.
CustomAttribute
= 0xC :
Tanımladığınız tüm CustomAttributelar burada bulunur. Her custom attribute için
Parent, Type ve Value olmak üzere 3 colon vardır. Parent MethodDef, Field, TypeRef,
TypeDef, Param, InterfaceImpl, MemberRef, Module, DeclSecurity, Property, Event
StandAloneSig, ModuleRef, TypeSpec, Assembly, AssemblyRef, File, ExportedType
ve ManifestResource tablelarından birinde bir index belirtir. Type ise TypeDef,
TypeRef, MethodDef, MemberRef ve UserStrin den herhangi birinde bir index belirtir.
Value ise blob şeklinde Value yu belirtir.
FieldMarshal
= 0xD,
DeclSecurity = 0xE,
ClassLayout = 0xF,
FieldLayout = 0x10,
StandAloneSig = 0x11,
EventMap = 0x12 :
EventMap bir relation table’ıdır diyebiliriz. Bu table da Event’ler ve bunların
hangi TypeDef e ait oldukları ilişkilendirilmiştir. Her bir EventMap row unda
sırası ile Parent ve EventList olmak üzere iki colon vardır. Parent hangi TypeDef
in eventleri olduğunu EventList de event tableında bir index belirtir.
EventPointer
= 0x13,
Event = 0x14 :
Assembly içindeki tüm TypeDef lerinizde tanımladığınız tüm eventler burada birer
row şeklinde girilir. Her bir event row unda sırası ile EventFlags, Name ve
EventType olmak üzere 3 colon vardır. EventFlag flagları, Name ismini ve EventType
da TypeDef yada TypeRef içindeki bir row da o eventin Handler tipini belirtir.
PropertyMap
= 0x15 :
EventMap in benzeri şekilde buda Property – TypeDef arasındaki iliskileri belirler.
Yani hangi Property nin hangi TypeDef a ait olduğunu belirtir. Her bir PropertyMap
rowunda Parent ve PropertyList olmak üzere iki kolon bulunur. Bu komonlardan
ilki hangi TypeDef in Propertyleri için bu row un olduğunu PropertyList ise
hangi indexden itibaren Propertylerin başladığını belirtir.
PropertyPointer
= 0x16,
Property = 0x17 :
Tüm property tanımları bu table da olurlar. Bu table daki her bir property için
Flags, Name ve Type olmak üzere 3 colon mevcuttur. Type colonu bir blob index
belirtir. Bu blobdan okunan değeri çözerek bu Property nin Type ını ve parametrelerinin
typelarını bulabiliriz.
MethodSemantics
= 0x18 :
Her bir event’in yada Property nin bildiginiz gibi içi aslında methodlardır.
Yani
public int MyProperty
{
get {return _MyProperty;}
set {_MyProperty = value;}
}
şeklindeki bir property aslında iki ayrı methoddur.
public int get_MyProperty() {return _MyProperty;}
ve
public void set_MyProperty(int value) {_MyProperty = value;}
işte bu table bir property ile onun MethodDef lerini yada bir Eventle onun MethodDef
leri arasındaki ilişkilendirmeyi yapmakta kullanılır. Her bir MethodSemantics
row unda sırası ile Semantics, Method ve Association olmak üzere 3 colondan
oluşur. Semantics flagları, Method methoddef tableında bri index i ve Association
ise Event veya Property table’ında bir index belirtir.
MethodImpl
= 0x19,
ModuleRef = 0x1A:
Bir assembly içinde kullandığınız DllImport lar nedeni ile hangi dll lere refernas
taşıyorsanız onların herbirine burada bir row eklenir. Bu sayede DllImport çözümlerinde
hangi modülden bu method’un çağrıldığını bulabiliriz. Her bir ModuleRef Name
olmak üzere bir colondan oluşur.
TypeSpec
= 0x1B:
Bir Type’ın signaturedan çözülmesini sağlamak için bir blob index belirtir.
Bu blob’un çözümlenmesi ile ortaya bir Type çıkar. Her TypeSpec de bulunan rowlar
sadece Blob’dan bir index barındırırlar.
ImplMap
= 0x1C,
FieldRVA = 0x1D:
Static olan field tanımlamalarının değerlerini barındırır. Yani static yazdığınız
field’ın cctor methodunda yüklenen ilk değerini tutar. Herbir FieldRVA rowu
RVA ve Field olmak üzere iki kolondan oluşur.
EncodingLog = 0x1E,
EncodingMap = 0x1F,
Assembly
= 0x20:
Bu dosya içinde tanımlı bulunan tüm Assembly’ler burada birer row şeklinde bulunur.
Genelde birden fazla olmaz. Her AssemblyRow HashAlgId, Major, Minor, Revision,
Build Flags, PublicKey, Name ve Culture olmak üzere 9 colondan oluşur.
AssemblyProcessor
= 0x21,
AssemblyOS = 0x22,
AssemblyRef = 0x23 :
Assemblyniz içinde diğer assemblylere taşıdığınız her bir Assembly Referans’ı
burada bir row şeklinde tutulur. Örneğin mscorlib.dll, System.dll gibi. Her
AssemblyRef row unda sırası ile Major, Minor, Revision, Build, Flags, PublicKey
Name, Culture ve HashValue olmak üzere 9 colon vardır.
AssemblyRefProcessor
= 0x24:
AssemblyRefOS = 0x25:
File = 0x26:
ExportedType = 0x27:
ManifestResource = 0x28:
Her bir manifest resource için bir row buraya kaydedilir. Her manifest resource’un
sırası ile Offset, Flags, Name ve Implementation olmak üzere 4 colon u vardır.
NestedClass
= 0x29:
Nested classları tutmanızda yardımcı olurlar. Nested class’lar direk namespace
içerisinde değilde bir class’ın altında bulunan classlardır. Aslında onlarda
TypeDef de birer row olarka tutulurlar ancak burada onlarla onların Declaring
Type ları arasındaki ilişkilendirme tutulur. Her bir NestedClass rowunda sırası
ile NestedClass ve EnclosingClass olam üzere 2 colon vardır.
GenericParam = 0x2A:
Generic parametreler burada tutulurlar. Her bir Generic Parametre sırası ile
Number, Flags, Owner ve Name olmak üzere 4 colondan oluşur.
MethodSpec
= 0x2B:
Burada generic methodların Instantiation ları tutulur. Her bir MethodSpec row’unda
sırası ile Method ve Instantiation olmak üzere iki colon vardır.
GenericParamConstraint
= 0x2C:
Burada herhangi bir Generic Parametresinin Constraintleri tutulur. Herbir row
Owner ve Constraint olmak üzere iki colon’dan oluşur.
----
Bu tableların hepsini detayları ile açıklarsak safalar dolusu döküman oluşur.
Ben sadece burada birkaç tanesini açıkladım. Bunlar en önemli olanlar diyeceğim
ama diğerleride bunlar kadar önemli sayılırlar. Ayrıca yine burada çok fazla
kolonlarına değinmedim. Kolon çözümlemeleride aslında bazı durumlarda kompleksleşebilir.
O nedenle çok girmeyeceğim bu konuya ama şu kadarını söyliyeyim işte MetaData
Table lar dediğimiz şeyler bunlardır. Yani bir Assembly içindeki tüm herşey
bu tablelarda tanımlıdır. Ve tüm bu tablelar birbirleri ile ilişkilidir. CLR
bunları çalışma zamanında ilişkilendirip buna göre çalıştırır.
Şimdi
bu konuda çok kısa bir örnek yazalım daha detaylı olsun ve anlamaya yardımcı
olsun; Bir Assembly’miz olsun ve içinde iki tane classımız tanımlı olsun
public class MyClass
{
int _MyField = 0 ;
Colors _Color = Colors.Red ;
public
MyClass()
{
}
public
string DoOperation(bool parameter)
{
if
(parameter)
return
“True”;
else
return
“False”;
}
[Browsable(false)]
public int MyProperty
{
get
{return _MyField;}
set
{_MyField = value;}
}
public
Colors Color
{
get
{return _Color;}
set
{_Color = value;}
}
}
public enum Colors
{
Red,
Green,
Blue
}
şimdi yazdığımız
assembly’de sadece yukardaki tiplerin olduğunu düşündüğümüzde bu assembly’ nin
meta data tableları şu şekilde oluşucaktır.
TypeDef;
Flags ........... Name ....... Namespace ...... Extends
..... FieldList .... MethodList
1 MyClass*
MyNamespace* 0 1
1
1 Colors*
MyNamespace* 1
3
0
TypeRef:
ResolutionScope ............. Name ................
Namespace
0x23000001 Int32*
System*
0x23000001 Boolean*
System*
0x23000002 BrowsableAttrıbute*
System.ComponentModel*
....
MethodDef:
RVA .... ImplFlags .... Flags ... Name ..........
Signature ... ParamList
... 0 6
DoOperation*
3 1
... 0 6
get_MyProperty*
4 1
... 0 6
set_MyProperty* 6 1
... 0 6
get_Color
12
2
... 0 6
set_Color
24
2
... 0 0x806
.ctor*
48
2
Field:
Flags ................ Name ...............
Signature
1 _MyField*
67
1 _Color*
45
1 _Red*
456
1 _Green*
33
1 _Blue*
97
Param:
Flags ............... Sequence ............ Name
1 0
parameter*
1 0
value*
1 0
value*
Property:
Flags ............... Name ................ Type
0x200 MyProperty*
...
0x200 Color*
...
PropertyMap:
Parent .............. PropertyList
0x00000001 1
CustomAttribute:
Parent .............. Type ................ Value
0x17000001 0x01000003 ...
MethodSemantics:
Semantics ........... Method .............. Association
0x0002
2 0x17000001
0x0001
3
0x17000001
0x0002
4 0x17000002
0x0001
5
0x17000002
Assembly:
HashAlgId .. Major .. Minor .. Revision .. Flags ..
PublicKey .. Name ....... Culture
0x8004
1 1 5345
0x8000 0
MyAssembly*
0
AssemblyRef:
Major .. Minor .. Revision .. Build .. Flags .. PublicKey
........ Name ........... Culture
2 0
0
0 1
b77a5c561934e089* mscorlib* 0
2 0
0
0
1 b77a5c561934e089*
System* 0
Yanında * işareti
olanlar aslında heaplerden okunan indexler olucak ama ben return değerlerini
yazdım.
İşte yukarda yazdığımız basit bir Assembly Build edildikten sonra Meta Data
Table ları yaklaşık olarak bu şekilde oluşacaktır. Listeyi elle hazırladığım
için belki bir kaç ufak hata olabilir ama sonuçta bu tarz birşeyler ortaya çıkıcaktır.
Tabi bu yapı aslında
daha da komplike bir şekilde olur ama ben örnek olsun diye basitce anlattım
ve bu sırada birçok meta data table’ınıda yazmadım. Gerçekte ise oluşturduğumuz
type lar, referansları, attributeları, methodlar, parametreler, düşünüldükçe
bu yapının nasıl bir hal aldığı daha çok anlaşılacaktır.
Kısaca yukardaki
gibi çözdüğümüz meta data sayesinde elimizde bu assembly’nin tam çözümlenmiş
Type ve Member yapısı oluşur. Yani bir nevi Reflection la çözülen yapının aynısı.
Şimdi sıra bir
methodun decompile edilmesine geldi. Bir methodun decompile edilebilmesi için
o method’un Body’sini yani methodun iç kısmını okumamız gerekir. Bunun için
Method’un RVA’sına gidilir ve okuma yapılır. Okuma işlemi method’un büyüklüğüne
göre iki formatta olur. FatFormat (buyuk methodlar, try catch veya local variable
barındıran methodlar) ve Tiny format. Tiny formatta method da sectionlar yoktur
ve Code’lar buradan okunur. FatFormatta ise methodun kodu birden fazla section’a
bölünür ve oradan sırası ile okunur. Aslında try catch kullandığınızda yada
variable kullandığınızda method otomatikman FatFormatta olur. Çünkü TinyFormatta
try catch ve Lacal Variable yoktur.
Method RVA’sını
o RVA yı kapsayan sectiondan bulup o position’a gidip oradan Method Body’sini
okuduğumuzda elimizde byte[] şeklinde bir data oluşacaktır. Okunan bu byte[]
şeklindeki data o methodun core datasıdır. Şimdi bunu işlemek gerekir. Aslında
okunan bu byte[] tum IL datasıdır. Bunun için once IL’yi biraz inceleyelim.
IL opcode lardan oluşur yani bir komut gibi düşünürsek bir işlemi ifade eden
bir data ve eger varsa bunun aldığı bir data sonrasındada o data olucak sekilde
dizilirler. IL de her bir opcode’un bir Operand Type’ı vardır.
Bu Operand Typelar;
OperandType.InlineNone:
Operand dan sonra herhangi bir data olmaz. Sıradaki Operand’a geçilir.
OperandType.ShortInlineBrTarget:
Operand dan sonra bir sbyte lık data target belirtir. Bu operand tipi branch
yani zıplama operand larında olur ve zıplamanın olacağı adresi belirtir. Yani
goto ... gibi düşünebilirsiniz.
OperandType.InlineBrTarget:
Operand dan sonra 4 byte lık bir integer data target belirtir. Bu operand tipi
branch yani zıplama operandlarında olur ve zıplamanın olacağı adresi belirtir.
Aynı ShortInlineBrTarget gibidir ancak tek fark gidilcek adres 4 byte la ifade
edilir.
OperandType.ShortInlineI:
Operand dan sonra bir sbyte lık data vardır.
OperandType.InlineI:
Operand dan sonra 4 bytelık bir integer data vardır
OperandType.InlineI8:
Operand dan sonra 8 bytelık bir long data vardır
OperandType.ShortInlineR:
Operand dan sonra bir single data vardır
OperandType.InlineR:
Operand dan sonra bir double değer vardır
OperandType.InlineString: Operand dan sonra 4 bytelık bir integer
string’in US Heap deki index’ini belirtir. Burada önemli olan bu index’in ilk
20 bit’i kullanılır. Yani (reader.ReadInt32() & 0x000fffff) le dönen değer
bize US Heap’deki index’i verir. Buradan okunan indexdeki string değeri bu operand
için data olarak kullanılır.
OperandType.ShortInlineVar:
Operand dan sonra bir byte lık data vardır
OperandType.InlineVar:
Operand dan sonra 2 bytelık unsigned short değer okunur. Bu değer bir index
belirtir. Bu index bu method içinde tanımlanan local variable indexidir. Yani
sonuçta bu index’deki variable bu operand’in datasıdır.
OperandType.InlineSig:
OperandType.InlineMethod:
OperandType.InlineField:
OperandType.InlineType:
OperandType.InlineTok: Operand dan sonra 4 bytelık bir int değer okunur.
Bu int değer aslında bir tokendir. Bu token’in ilk byte’ı bize hangi MetaTable
olduğunu bulmamızı sağlar ve son 3 byte’ı ise o tabledaki index değerini verir.
Yani;
int token = reader.ReadInt32();
int table = ((token & 0xff000000) >> 24);
int index = (token & 0x00ffffff);
buradan çıkan
sonuca göre örneğin table = 1 ve index = 1 ise bu bize; 1 nolu table in 1. item’ı
demek oluyor. Buda 1 nolu table TypeRef ve bunun 1. item’ı. Yalnız unutmamak
gerekirki MetaData Table larda indexler 1 den başlar. Bu nedenle 1. item demek
aslında 0. index deki itemdır. Yani burada bu Opcode un datası TypeRef table’ının
0. index deki TypeRefdir.
OperandType.InlineSwitch:
Operand switch operandıdır. Yani switch ve bunun case leri belirli bir mantıkda
sıralanır. Bunun cözümlenme mantığı ise ilk 4 byte bu switch’in kaç case’i olduğunu
gösterir. Daha sonra bu case sayısı kadar her case’in target’ini yani hangi
ofset’e zıpladığını belirten 4 bytelık targetlar vardır.
int[] cases =
new int[reader.ReadInt32()];
for (int i = 0; i < cases.Length; i++)
cases[i] = reader.ReadInt32();
Buradan çıkan sonuca göre cases array şeklinde switch in case lerini belirtir
ve bu operandın datası olur.
Bu operand tiplerine göre body den okuduğumuz code’u çözerir.
Yani code array
de donerken sırası ile önce opcode’u okuruz. Sonra bu opcode’un OperandType
ına göre sonraki datasını (varsa) okuruz ve sonra yeni bir operand’a geçeriz.
Burada önemli bir diğer nokta ise operandlardır. .NET’te iki tip operand vardır.
Bunlardan ilki tek byte’lık operandlar diğerleri ise 2 byte’lik operandlardır.
Bunun için önce bir byte okuruz eger bu byte 0xFE degilse bu demekki o tek byte’lık
bir operand’dır. Yok eğer bu byte 0xFE ise bu durumda o operand’ı çözmek için
sonraki byte da okunur ve ilk okunan ile orlanır.
byte operand =
reader.ReadByte();
if (operand == 0xfe)
{
byte next = reader.ReadByte();
operand = next | 0xfe;
}
Bunu yaptikdan
sonra da yukardaki gibi operand datası okunur.
Şimdi şu ana kadar
biz bir dosyayı aldık onun headerlarını okuduk, sonra onun meta data table larını
okuduk ve şimdi de bir method bodysini okuyup onun opcodelarını yani IL kodlarını
çıkarttık.
İşte bundan sonra
asıl zor kısım yani onun CodeDom’unu yani expression ve statementlarını oluşturmak
kalıyor. Burası konusunda cok bilgi veremeyeceğim. Ama burada önemli olan her
opcode türü için yapılacak işlemler ve bunların stack da tutulmasını yaparak
bir geri cözümleme yapmak. Bu ilk çözümlemede ortaya çıkan kod çok okunabilir
birşey olmayacaktır. Daha sonra compilerın ürettiği kodları düşünerek tersi
işlemle anlamlı okunaklı bir kod üretilmesi gerekir.
Örneğin bir branch gördüğümüzde eğer bu contional branch’sa bunu if, while,
do while, for gibi bir şeye uydurmak gibi çözümleme yapılmalıdır. Dahada detaylandırcak
olursak;
Örneğin kodumuz
şu şekilde olsun :
L_0043: br.s L_0072
L_0045: ldarg.0
L_0046: ldfld ......
L_004b: ldarg.0
L_004c: ldarg.0
……………………..
L_006e: ldloc.0
L_006f: ldc.i4.1
L_0070: add
L_0071: stloc.0
L_0072: ldloc.0
L_0073: ldarg.0
L_0074: call …
L_0079: callvirt …
L_007e: blt.s L_0045
L_0080: ldc.i4.0
gibi bir kodumuz olduğunu düşünürsek burada 0043 deki br.s yani branch short
bir Branch operand dır ve sonrasında 1 bytelık bir daha (target) alır. Şimdi
o kod bize L_0072. ofset e zıplamamızı söylüyor. Oraya gidip baktığımızda onun
öncesinde 006e den itibaren
ldloc.0 -> 0.
local variable i evaluation stack a yükle)
ldc.i4.1 -> Int32 olarak 1 sayısal değerini evaluation stacke koyar
add -> İki değeri toplar ve sonucu evaluation stacke koyar
stloc.0 -> Stackdan bir değer Pop eder ve onu 0. local variable’a atar
Şimdi burada yapılan
işlem aslında şunun gibi birşey , a diye bir sayısal değişkenimiz olsun ve bu
bizim local variable larımızın 0. index dekisi olsun. Yani ilk tanımlanan Local
variable. Bu kod bu durumda şu demek oluyor.
a = a + 1;
ki buda
a += 1;
ki buda
a++; ‘le aynı şeydir…
Gördüğünüz gibi o
zıplamanın adresinden önce bir i++ işlemi oluyor. L_0072 den aşşağıya doğru devam
ettiğimizde ise yine 0. variable ın 0073 de stack e yüklendiğini görüyoruz. L_007e
de ise bir conditional zıplama görüyoruz.
blt.s L_0045 -> eğer ilk değer diğerinden küçükse calışır.
Yani ilk değer diğerinden küçükse L_0045 e geri dönüyor.
İşte bu şekilde kodu detaylı incelediğimizde ortaya şu çıkıyor;
for (int i = 0; i < …Count; i++)
{
…..
}
gibi bir kod. İşte bu şekilde yapılan dünya kadar işlem sonucunda IL kodu ancak
anlaşılır bir kod çıkartır ortaya.
Burada bir diğer önemli nokta ise .NET teki tüm statement ve
expressionlara çok iyi hakim olmak gerekir. Yani tüm expressionların nasıl çalıştığını
output un ne olduğu gibi.
Örneğin;
lock statement’ının aslında System.Threading.Monitor.Enter la başlatılan bir
monitor ve System.Threading.Monitor.Exit lede çıkıldığını bilmek gerekirki bu
method’a bir call olduğunda otomatikman lock statement oluşturulmalı.
Yada foreach’in aslında IEnumerator ve IEnumerable la alakalı
olduğunu ve bunun mantığının ..
IEnumerator enumerator1 = obj.GetEnumerator();
try
{
while (enumerator1.MoveNext())
{
int
data = (int)enumerator1.Current;
/////
}
}
finaly
{
if (enumerator1 is IDisposable)
{
((IDisposable)enumerator1).Dispose();
}
}
şeklinde olması gerektiğini bilmek gerekir.
Bir diğer önemli nokta. CodeDom’un çok iyi oluşturulması gerekir. Yani tam
bir modelleme yapılması gerekir. Sonuçlar ortaya modellenmiş şekilde çıktığında
ise artık geriye tek bir iş kalmış olucak ki oda bu model’i render etmek. Bunun
içinde her dil için bir renderer yazmak yeterli olucaktır. Ani C#, VB.NET gibi.
Bu dillerin herbirinde bu modellenmiş kodlar farklı şekilde çıktı verir.
Burada kod model’e bir örnek olarak isterseniz if, else kalıbını verelim.
public interface IConditionStatement : IStatement
{
IBlockStatement Then {get;set;}
IBlockStatement Else {get;set;}
IStatement Condition {get;set;}
}
yine bir diğer kod model objesi olan SizeOfuda verelim...
public interface ISizeOfExpression : IExpression
{
ITypeName Type {get;set;}
}
İşte bunun gibi varolan tum expression ve statementlarin codemodelini oluşturmak
çok önemli. Çünkü biz decompile ederken bu codemodeli oluşturuz ki bu sayede
elimizde codedom oluşmuş olur.
Daha sonra renderer her expression ve statement’ı yeri geldiğinde render ederiz.
Örnegin C#Renderer SizeOF Expression’ı gördüğünde sizeof(expression.Type) şeklinde
bir işlem uygular.
Sonuç olarak Fox’un hikayesine değindikten sonra nasıl decompile yapılabileceğini
az çok göstermiş oldum. Umarım anlaşılır olmuştur. Aslında bütün bunlar olayın
sadece yüzeysel bir anlatımı. Bu konuda uğraşmak isteyenlerin daha detaylı çalışma
yapmaları gerekecek. Çünkü her bir meta data table’ının kendine ait özel specific
durumları bunların çözümü, yaklaşık 50 civarında expression, 10 civarında Type
bütün bunlar hepsi ayrı ayrı çözümlenmeli. Bütün bu anlattıklarımdan sonra umarım
herkes .NET’in ve CLR nin hangi mantıkla çalıştığını az çok anlamışlardır. Bizim
yaptığımız aslında CLR’nin yaptığına benzer birşey olduğu için engellemenin
çok fazla yolu yok.
Ancak yinede herkese kodlarını korumak için ellerinden geleni yapmalarını tavsiye
ederim. Şu anda bir çok kod koruma aracı mevcut. Bu kod koruma araçlarından
hangisinin daha uygun olduğunu belirlerken asla unutmamanız gereken şey ise
Obfuscation’ın bir işe yaramayacağı. Çünkü obfuscate etmek sadece ama sadece
rename etmektir. Yani örneğin bizim TypeDef table’ındaki bir TypeDef’i okuduğumuzu
varsayalım. Obfuscate işlemi uygulandığında USHeap ve StringHeap deki stringler
obfuscate edilip _1, _2 yada a, b gibi birşeyler olucak. Yani sadece String
ve USHeap de bir şeyler değişicek. Zaten decompiler araçları bunları sadece
ama sadece isimleri gösterirken kullanıyor. Yukardada söylediğim gibi asla textler
üzerinden işlem olmaz. Bir TypeDef’in name ini okurken bize sadece bir index
değeri gelir. Bu StringHeap deki bir index dir ve gidip oradan o text’i alır
yazarız. Onun haricinde o TypeDef’i cağıran hiçbir yerdede bu isimle işlem yapılmaz.
Direk TypeDef table’ında o TypeDef’in indexi ile işlem yapılırlar. İşte bu nedenle
yapmanız gereken koruma tool’unuzu seçerken obfuscate etmesine göre değil exktra
özelliklerine göre seçmeniz. Bu konuda benim tavsiyem Fox’u satın alan Xenocode’un
diğer tool’u olan PostBuild’dir. PostBuildde .NET kodlarınızı direk Native koda
çevirebilme şansınız var. Bu sayede artık ortada MetaData lar kalmadığı için
Fox yada Reflector’un çözmeside mümkün olmayacaktır.
Hepinize kolay gelsin iyi çalışmalar.
Özcan DEĞİRMENCİ
[email protected]
|
|
|
|
|
SON 10 Haber
|
|
|