String Deobfuscation with Invoke ~ Python [TR]

Selamlar, bu yazımda “Crypto Obfuscator” adlı yazılımın string korumasını Python’da dnlib ve Reflection ikilisini kullanarak nasıl deobfuscate edebileceğimizi anlattım. Maksat değişiklik olsun…

Dosya Analizi

Crypto Obfuscator’un deneme sürümü ile sadece string encryption işlemine soktuğumuz dosyayı dnSpy üzerine açalım.

Rastgele bir event’e gittiğimizde içinde bulunan stringlerin şifrelendiğini ve junk kodlar bıraktığını gördük (Junk kodların temizlenme işlemi bu yazıda yapılmayacak): Screenshot_1

Anahtar değeri decryption methoduna parametre olarak gönderiyor ve geri dönen değer ile işini sürdürüyor.

Yapılacaklar

Import ve Load

Aynı klasör içerisinde tutuyorum bütün dosyalarımı. Önce gerekli kütüphaneyi indirelim.

pip install pythonnet

Reflection kütüphanelerini import edelim.

import clr
from System.Reflection import Assembly, MethodInfo, BindingFlags
from System import Type

Main bloğunda dnlib.dll dosyamızı referans olarak ekleyelim. module ve assembly adlı değişkenlerimize de obfuscated uygulamayı load edelim.

def DecryptStrings():
    MethodIdentifier()


def Save():
    module.Write("deobfuscated.exe")


def MethodIdentifier():
    pass


if __name__ == "__main__":
    clr.AddReference(r"DNLİB PATH")
    import dnlib
    from dnlib.DotNet.Emit import OpCodes
    global module # her fonksiyondan erişebilir olması için
    global assembly
    module = dnlib.DotNet.ModuleDefMD.Load("WindowsFormsApp3.exe")
    assembly = Assembly.LoadFrom("WindowsFormsApp3.exe")
    MethodIdentifier()
    DecryptStrings()
    Save()

Neden ayrı ayrı iki modül yüklüyoruz konusuna ufak bir açıklama geçeyim. assembly, Reflection kütüphanesinden Invoke işlemi için yüklendi (dnlib’de bu özellik yok). module, düzenleme işlemleri için.

Decryption Metodu

Bu kısımda sadece programa decryption metodunu bulduracağız. Zaten invoke işlemi ile decrypt edeceğimiz için decryption algoritmasını yazmamıza gerek yok.

dnSpy ile tekrar açıp analiz edelim, decryption metodunun nasıl ayırt edici özellikleri varmış bakalım. Decryption metodumuz burada: Screenshot_3

Şimdi programın okuyabileceği şekil olan IL kısmına geçelim. (Sağ Tık > Edit IL Instructions) Screenshot_4

En ayırt edici özellikler olarak methodun

IL uzunluğu = 107

  1. OpCode = LdcI4
  2. OpCode = Ldsfld
def MethodIdentifier():
    global stringMethodName #1
    global invokeMethod #1
    eFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic #2
    for type in module.Types: #3
        for method in type.Methods:
            if not method.HasBody: pass #4
            if len(method.Body.Instructions) < 107: pass #5
            i = 0
            while i < len(method.Body.Instructions): #6
                if(method.Body.Instructions[i].IsLdcI4() and
                     method.Body.Instructions[i + 2].OpCode == OpCodes.Ldsfld): #7
                    stringMethodName = str(method.Name)
                    stringTypeName = str(type.Name)
                    for types in assembly.GetTypes(): #8 ...
                        if types.Name == stringTypeName:
                            classInstance = types
                            break

                    for methods in classInstance.GetMethods(eFlags):
                        if methods.Name == stringMethodName:
                            invokeMethod = methods
                            return
                    break
                i += 1

Kodumuzu açıklayalım :

Decryptor Method

Decryption methodumuzu bulduktan sonra Decrypt metodunun yapacağı tek şey şifrelenmiş alanları bularak, işe yarayan parametreyi decryption metoduna gönderip invoke ediyoruz. Çıkan sonucu yerine yerleştiriyoruz.

Yapmamız gereken ilk şey obfuscated bölümleri tanımak. dnSpy’ı açıp bakıyoruz. Screenshot_5

Obfuscated bir yerin IL instruction’larına baktığımızda sadece metoda sayısal bir ifade gönderildiğini göreceğiz. Ama birden çok metot böyle çalışabilir. İşte bu kısımda önceden tanımladığımız “stringMethodName” değişkeni işimize yarayacak. “call” OpCode’lu kısmın Operand’ında eğer atadığımız “stringMethodName” geçiyorsa burası obfuscated bir alandır diyebiliriz.

O zaman bunun ayırt edici özellikleri :

Kodu yazalım :

    decryptedStrings = 0 # Ne kadar stringi decrypt ettiğini görelim burda
    for type in module.Types: # Klasik dolaşma işlemleri
        if not type.HasMethods: pass # Eğer metodu yoksa atla
        
        for method in type.Methods:
            if not method.HasBody: pass
            i = 0
            operText = ""
            while i < len(method.Body.Instructions):
                operText = str(method.Body.Instructions[i].Operand).encode()
                if(method.Body.Instructions[i].OpCode == OpCodes.Call and
                        operText.find(str(stringMethodName).encode()) != -1):
.........

Şimdi yapmamız gereken şey argüman olarak göndereceğimiz şeyin değerini çekmek ve Invoke edip gelen değeri yerine yerleştirmek :

  keyValue = method.Body.Instructions[i - 1].GetLdcI4Value()
  result = str(invokeMethod.Invoke(None, [keyValue]))
  method.Body.Instructions[i - 1].OpCode = OpCodes.Nop
  method.Body.Instructions[i].OpCode = OpCodes.Ldstr
  method.Body.Instructions[i].Operand = result
  decryptedStrings += 1
i += 1

Gerekli dosyalar ve kodun tamamı :

https://github.com/Rhotav/Python-Method-Invoker