Utku tarafından yazılmıştır.
Selamlar, ben rhotav. Bu yazımda ConfuserEx korumasında bulunan AntiTamper özelliğinin analizini yapacağım. Özellikle ConfuserEx seçmemin sebebi hem seriyi bozmamak (AntiDump korumasının analizi) hem de açık kaynak kodlu ve düzenlenen, sonradan yazılan korumalarda reverser’ların canını daha fazla sıkması.
Yazımızın temel yapısı şu şekilde olacak:
AntiTamper, ConfuserEx wiki sayfasına göre:
Bu açıklamaya göre AntiTamper korumasının uygulama bütünlüğünü korumak için (module constructor haricinde) bütün kod yapısını bozup, en başta da tekrar düzelttiğini söyleyebiliriz. Bunu örneklerle daha iyi anlayacağınızdan eminim.
Hedef yazılımı dnSpy üzerinde incelemeye başlayalım. Bütün fonksiyon body’leri ile oynadığını söylemiştik. Bunu kontrol etmek için direkt olarak Entry-Point’e (giriş noktası’na) gidebiliriz.
Main noktasına gittiğimizde tek satır kod olmadığını görüyoruz. Bu şekilde de olabilirdi:
Bunun sebebi; ConfuserEx bu korumayı dosyaya enjekte ederken fonksiyonun kod yapısını rastgele değerler ile karıştırır. Main noktasında decompiler hatası görmememiz, farklı bir fonksiyonda göremeyeceğimiz anlamına gelmez. Byte’ları karıştırma işlemini tamamen rastgele değerler ile yaptığı için bazen normal byte’lar yerine CIL Instruction List üzerinde anlamı olan byte’lar geldiğinde decompiler error görüyoruz.
Form1 CCTOR fonksiyonunun ismine Sağ Tık > Edit Method Body… kombinasyonunu uyguladığımızda anlamsız OpCode’ları göreceğiz :
Şimdi Main fonksiyonuna uygulayalım :
Anlamlı hiçbir byte gelmemiş…
Evet efendim işin cafcaflı kısmı tam olarak burası. Şimdi burada kaldırmaya direkt girmeden önce bu yöntemin teorisinden bahsetmek istiyorum.
Bir .NET uygulaması çalışma zamanında her zaman JIT Compiler ile iletişim halindedir. Uygulamayı pre-jit durumuna sokmadığınız durumda bütün fonksiyonlar tamamen runtime yorumlanır yani bir fonksiyonu çalıştıracağınız zaman fonksiyon yorumlanmak üzere clrjit’e (sürüme bağlı olarak mscorjit de olabilir) gönderilir. JIT Compiler’ın analizini yaptığım yazıya buradan gidebilirsiniz.
PwnLab’e özel olarak JIT Killer projemin ön gösterimi :
Dolayısıyla AntiTamper uygulanmış bir yazılımın da doğru düzgün çalışabilmesi için önce bozduğu fonksiyon yapılarını düzeltmesi gerekiyor. Bu düzeltme işlemi de bozulmamış bir fonksiyonda ve bozulmuş bütün fonksiyonlardan daha önce çalışması gerekiyor. Bu bahsettiğimiz nokta da Module Constructor noktası yani modülün .cctor noktası. Bunun için de modüle Sağ Tık > .cctor Noktasına Git diyebiliriz…
static <Module>() { .... }
Sizi bu noktaya götürdüğünde “Symbol Renaming” korumasına uğramış bir fonksiyonun çağrıldığına rastlayacaksınız. İsmi bozuk olan fonksiyonları daha rahat görebilmek ve okuyabilmek adına dnSpy üzerinden değiştiriyorum.
Bu hale döndü. Bir nebze olsun daha rahat okuyoruz şimdi.
Yapacağımız şey Initialize fonksiyonu çalıştıktan ve bittikten hemen sonra dnSpy’ın debug özelliğinin bize sunduğu “Modules” sekmesinden döküm alacağız. Noktaya breakpoint koyalım ve breakpoint tetiklendikten sonra bir kere “StepOver” ile devam ettirelim ve dökümü hafıza üzerinden dnSpy’da açalım :
Tamam, şimdi AntiTamper teorisine inebiliriz. ConfuserEx GitHub sayfasından baktığınız zaman 2 adet AntiTamper modu bulunduğunu göreceksiniz (Normal ve JIT). JIT Mode olarak adlandırılan yöntem ConfuserEx’in yapımcısının bitirmediği bir özellik dolayısıyla bunu göremiyoruz bitseydi .NET Reactor’ün kullandığı yöntem gibi olacaktı yüksek ihtimalle.
Zaten buraya baktığımızda her şey açıkça ortada. Amaç belli getJit’in döndürdüğü ICorJitCompiler sanal class’ında bulunan compileMethod adlı fonksiyon hooklanarak fonksiyon yapısı değiştirilecekti fakat devam ettirilmemiş…
Onu farklı bir analiz yazımızda detaylandırırız şimdi bütün ConfuserEx modlarında ve klasik ConfuserEx’de gördüğümüz AntiTamper korumasını irdeleyelim.
Buradan GitHub sayfasına gittiğimizde de göreceğimiz sınıfı hedef yazılıma enjekte ediyor. Yani benim ismini sonradan değiştirdiğim, constructor noktasında çalışan fonksiyon aslında bu. Bunu dnSpy üzerinde irdelemeye devam edebiliriz.
Initalize fonksiyonunu incelemeye başladığımızda AntiDump yazımda da bahsettiğim klasik, dinamik hesaplama kodları ile karşılaşıyoruz :
Module module = typeof(<Module>).Module;
string fullyQualifiedName = module.FullyQualifiedName;
bool flag = fullyQualifiedName.Length > 0 && fullyQualifiedName[0] == '<';
byte* ptr = (byte*)((void*)Marshal.GetHINSTANCE(module)); // Fonksiyonun hafıza üzerindeki adresi alındı.
byte* ptr2 = ptr + *(uint*)(ptr + 0x3C); // Hafıza adresi + 0x3C = e_lfanew (PE Header adresi)
ushort num = *(ushort*)(ptr2 + 6); // e_lfanew + 0x6 = Number of Sections
ushort num2 = *(ushort*)(ptr2 + 0x14); // e_lfanew + 0x14 = Size of Optional Header
uint* ptr3 = null;
uint num3 = 0U;
uint* ptr4 = (uint*)(ptr2 + 0x18 + num2); // e_lfanew + 0x18 + (e_lfanew + 0x14) = First Section Header
Bu hesaplama işlemlerini eğer hafıza üzerinde görmek isterseniz bunun için HxD kullanabilirsiniz. HxD’yi açıp “Open Main Memory” seçeneğine basalım ve önümüze açılan Process listesinden uygulamamızı seçelim.
Yalnız bu işlemi uygulama çalışır haldeyken yapmanız gerekiyor. Üstelik dnSpy üzerinde debug işlemini başlatırsanız hangi değişkenin hangi değeri tuttuğunu da göreceğiniz için doğrulamak daha kolay olur.
Uygulamayı açtıktan sonra örnek olarak e_lfanew’in hesaplanmasını doğrulamak için CTRL-G kombinasyonunu kullanarak dnSpy üzerinden kopyaladığım adresi yapıştırıp istediğim noktaya gidiyorum.
Kopyaladığım adres: “005D0080” (base address + 3C) = (005D0000 + 3C) adresinin direkt PE yani “50 45” noktasını seçtiğini görebiliyoruz.
Bu hesaplamaların amacı AntiDump korumasında silinecek yerleri bulup silmekti. Burada ise fonksiyon yapılarını çözmek için kullanması gereken şifreyi hesaplamak ve çözme işlemini gerçekleştirmek.
yine dnSpy yardımı ile uygulamanın Stream yapısındaki method kısmına bir göz atalım. Uygulamada bulunan bütün fonksiyonların listesi burada. .NET dosya hiyerarşisinde olan bir özelliktir bu. RVA, ILHeader ILCode LocalVar EH’yi depolayan yöntem gövdesinin verilerine işaret eder. ConfuserEx, RVA’yı değiştirir ve onu (yeşil kutuda işaretlediğim) “Section #0: BLABLABLA” bölümüne gömer (Constructor içerisinde çağrılan fonksiyonun ilk section header’a gitmesinin nedeni de bu). Bu bölüm yöntem gövdesini saklar. ConfuserEx bu bölümün içeriğini şifreler ve aslında Module Constructor kısmında çalışan çağrı işlemi bunu düzelten yapıyı çağırır.
Devamında yaptığı işlem aslında anahtar yerini saptayıp byte’lar ile XOR işlemine sokmaktan başka bir şey değil. Dolayısıyla incelemeye gerek yok üstte yaptığımız işlemin aynısını yapacak.
Kafanıza takılan, sormak istediğiniz bir şey olursa buradaki yorumları veya bloğumdaki yorumlar kısmını kullanabilirsiniz belki basit bir OSINT ile Twitter’dan da yazmak isterseniz kabulüm…
[TR] ConfuserEx Derin Analizi ~ AntiTamper
Selamlar, ben rhotav. Bu yazımda ConfuserEx korumasında bulunan AntiTamper özelliğinin analizini yapacağım. Özellikle ConfuserEx seçmemin sebebi hem seriyi bozmamak (AntiDump korumasının analizi) hem de açık kaynak kodlu ve düzenlenen, sonradan yazılan korumalarda reverser’ların canını daha fazla sıkması.
Yazımızın temel yapısı şu şekilde olacak:
AntiTamper Nedir?
AntiTamper, ConfuserEx wiki sayfasına göre:
Bu koruma, uygulamanın bütünlüğünü sağlar ve modülün yalnızca üzerinde herhangi bir değişiklik yapılmadığında yüklenmesini sağlamak için yöntemleri tüm modülün sağlama toplamı ile şifreler.
Bu açıklamaya göre AntiTamper korumasının uygulama bütünlüğünü korumak için (module constructor haricinde) bütün kod yapısını bozup, en başta da tekrar düzelttiğini söyleyebiliriz. Bunu örneklerle daha iyi anlayacağınızdan eminim.
Hedef Yazılımda AntiTamper’ın Tespit Edilmesi
Hedef yazılımı dnSpy üzerinde incelemeye başlayalım. Bütün fonksiyon body’leri ile oynadığını söylemiştik. Bunu kontrol etmek için direkt olarak Entry-Point’e (giriş noktası’na) gidebiliriz.
Main noktasına gittiğimizde tek satır kod olmadığını görüyoruz. Bu şekilde de olabilirdi:
Bunun sebebi; ConfuserEx bu korumayı dosyaya enjekte ederken fonksiyonun kod yapısını rastgele değerler ile karıştırır. Main noktasında decompiler hatası görmememiz, farklı bir fonksiyonda göremeyeceğimiz anlamına gelmez. Byte’ları karıştırma işlemini tamamen rastgele değerler ile yaptığı için bazen normal byte’lar yerine CIL Instruction List üzerinde anlamı olan byte’lar geldiğinde decompiler error görüyoruz.
Form1 CCTOR fonksiyonunun ismine Sağ Tık > Edit Method Body… kombinasyonunu uyguladığımızda anlamsız OpCode’ları göreceğiz :
Şimdi Main fonksiyonuna uygulayalım :
Anlamlı hiçbir byte gelmemiş…
Bir kavram karışıklığına değinmek istiyorum bu noktada. AntiTamper korumasının yaptığı şey esasında modülün bütünlüğünü korumak üzere fonksiyon yapılarını bozması ve çalışma anında düzgün çalışabilmesi için tekrar geri düzeltmesi. Yani asıl amacı kodun okunmasını engellemek değil buna Anti-Decompiler veya Anti-Disassembly diyebiliriz…
Dinamik Şekilde AntiTamper’ın Kaldırılması
Evet efendim işin cafcaflı kısmı tam olarak burası. Şimdi burada kaldırmaya direkt girmeden önce bu yöntemin teorisinden bahsetmek istiyorum.
Bir .NET uygulaması çalışma zamanında her zaman JIT Compiler ile iletişim halindedir. Uygulamayı pre-jit durumuna sokmadığınız durumda bütün fonksiyonlar tamamen runtime yorumlanır yani bir fonksiyonu çalıştıracağınız zaman fonksiyon yorumlanmak üzere clrjit’e (sürüme bağlı olarak mscorjit de olabilir) gönderilir. JIT Compiler’ın analizini yaptığım yazıya buradan gidebilirsiniz.
PwnLab’e özel olarak JIT Killer projemin ön gösterimi :
Dolayısıyla AntiTamper uygulanmış bir yazılımın da doğru düzgün çalışabilmesi için önce bozduğu fonksiyon yapılarını düzeltmesi gerekiyor. Bu düzeltme işlemi de bozulmamış bir fonksiyonda ve bozulmuş bütün fonksiyonlardan daha önce çalışması gerekiyor. Bu bahsettiğimiz nokta da Module Constructor noktası yani modülün .cctor noktası. Bunun için de modüle Sağ Tık > .cctor Noktasına Git diyebiliriz…
static <Module>() { .... }
Sizi bu noktaya götürdüğünde “Symbol Renaming” korumasına uğramış bir fonksiyonun çağrıldığına rastlayacaksınız. İsmi bozuk olan fonksiyonları daha rahat görebilmek ve okuyabilmek adına dnSpy üzerinden değiştiriyorum.
Bu hale döndü. Bir nebze olsun daha rahat okuyoruz şimdi.
Yapacağımız şey Initialize fonksiyonu çalıştıktan ve bittikten hemen sonra dnSpy’ın debug özelliğinin bize sunduğu “Modules” sekmesinden döküm alacağız. Noktaya breakpoint koyalım ve breakpoint tetiklendikten sonra bir kere “StepOver” ile devam ettirelim ve dökümü hafıza üzerinden dnSpy’da açalım :
Derin AntiTamper Analizi
Tamam, şimdi AntiTamper teorisine inebiliriz. ConfuserEx GitHub sayfasından baktığınız zaman 2 adet AntiTamper modu bulunduğunu göreceksiniz (Normal ve JIT). JIT Mode olarak adlandırılan yöntem ConfuserEx’in yapımcısının bitirmediği bir özellik dolayısıyla bunu göremiyoruz bitseydi .NET Reactor’ün kullandığı yöntem gibi olacaktı yüksek ihtimalle.
Zaten buraya baktığımızda her şey açıkça ortada. Amaç belli getJit’in döndürdüğü ICorJitCompiler sanal class’ında bulunan compileMethod adlı fonksiyon hooklanarak fonksiyon yapısı değiştirilecekti fakat devam ettirilmemiş…
Onu farklı bir analiz yazımızda detaylandırırız şimdi bütün ConfuserEx modlarında ve klasik ConfuserEx’de gördüğümüz AntiTamper korumasını irdeleyelim.
AntiTamper yapısının inject edilme işlemini irdelemeyi gerekli görmediğim için orayı geçeceğim.
Buradan GitHub sayfasına gittiğimizde de göreceğimiz sınıfı hedef yazılıma enjekte ediyor. Yani benim ismini sonradan değiştirdiğim, constructor noktasında çalışan fonksiyon aslında bu. Bunu dnSpy üzerinde irdelemeye devam edebiliriz.
PE yapısını hangi offset’te hangi verinin olduğunu yorumlamak için burayı referans alıyorum.
Initalize fonksiyonunu incelemeye başladığımızda AntiDump yazımda da bahsettiğim klasik, dinamik hesaplama kodları ile karşılaşıyoruz :
Module module = typeof(<Module>).Module;
string fullyQualifiedName = module.FullyQualifiedName;
bool flag = fullyQualifiedName.Length > 0 && fullyQualifiedName[0] == '<';
byte* ptr = (byte*)((void*)Marshal.GetHINSTANCE(module)); // Fonksiyonun hafıza üzerindeki adresi alındı.
byte* ptr2 = ptr + *(uint*)(ptr + 0x3C); // Hafıza adresi + 0x3C = e_lfanew (PE Header adresi)
ushort num = *(ushort*)(ptr2 + 6); // e_lfanew + 0x6 = Number of Sections
ushort num2 = *(ushort*)(ptr2 + 0x14); // e_lfanew + 0x14 = Size of Optional Header
uint* ptr3 = null;
uint num3 = 0U;
uint* ptr4 = (uint*)(ptr2 + 0x18 + num2); // e_lfanew + 0x18 + (e_lfanew + 0x14) = First Section Header
Bu hesaplama işlemlerini eğer hafıza üzerinde görmek isterseniz bunun için HxD kullanabilirsiniz. HxD’yi açıp “Open Main Memory” seçeneğine basalım ve önümüze açılan Process listesinden uygulamamızı seçelim.
Yalnız bu işlemi uygulama çalışır haldeyken yapmanız gerekiyor. Üstelik dnSpy üzerinde debug işlemini başlatırsanız hangi değişkenin hangi değeri tuttuğunu da göreceğiniz için doğrulamak daha kolay olur.
Uygulamayı açtıktan sonra örnek olarak e_lfanew’in hesaplanmasını doğrulamak için CTRL-G kombinasyonunu kullanarak dnSpy üzerinden kopyaladığım adresi yapıştırıp istediğim noktaya gidiyorum.
Kopyaladığım adres: “005D0080” (base address + 3C) = (005D0000 + 3C) adresinin direkt PE yani “50 45” noktasını seçtiğini görebiliyoruz.
Bu hesaplamaların amacı AntiDump korumasında silinecek yerleri bulup silmekti. Burada ise fonksiyon yapılarını çözmek için kullanması gereken şifreyi hesaplamak ve çözme işlemini gerçekleştirmek.
yine dnSpy yardımı ile uygulamanın Stream yapısındaki method kısmına bir göz atalım. Uygulamada bulunan bütün fonksiyonların listesi burada. .NET dosya hiyerarşisinde olan bir özelliktir bu. RVA, ILHeader ILCode LocalVar EH’yi depolayan yöntem gövdesinin verilerine işaret eder. ConfuserEx, RVA’yı değiştirir ve onu (yeşil kutuda işaretlediğim) “Section #0: BLABLABLA” bölümüne gömer (Constructor içerisinde çağrılan fonksiyonun ilk section header’a gitmesinin nedeni de bu). Bu bölüm yöntem gövdesini saklar. ConfuserEx bu bölümün içeriğini şifreler ve aslında Module Constructor kısmında çalışan çağrı işlemi bunu düzelten yapıyı çağırır.
Devamında yaptığı işlem aslında anahtar yerini saptayıp byte’lar ile XOR işlemine sokmaktan başka bir şey değil. Dolayısıyla incelemeye gerek yok üstte yaptığımız işlemin aynısını yapacak.
AntiTamper’in 2 sürümü olduğundan bahsetmiştik. JIT kısmının bitirilmeme nedeni aslında uyumluluk sorunları çıkartacağından ve sürekli güncellemeye muhtaç olacağından dolayı. Genellikle her .NET güncellemesinde JIT yapısı yenilenir. Yeni özellikler veya yeni yapılar eklenir ve dolayısıyla bu tarz uygulamalarda uyumluluk sorunu çıkabilir. .NET Reactor bunu iyi başarıyor çünkü sürekli güncelleme alıyor
Kafanıza takılan, sormak istediğiniz bir şey olursa buradaki yorumları veya bloğumdaki yorumlar kısmını kullanabilirsiniz belki basit bir OSINT ile Twitter’dan da yazmak isterseniz kabulüm…
Moderatör tarafında düzenlendi: