Obfuscator Eazfuscator i zabezpieczenia aplikacji .NET

Dzisiaj rozmawiałem z twórcą obfuscatora dla .NET – Eazfuscator i przyznam szczerze, że trochę zdębiałem. Interesowało mnie jak obecnie radzi sobie Eazfuscator z deobfuscatorem de4dot. Po pierwszej odmowie udzielenia mi odpowiedzi, gdyż reprezentuję firmę sprzedającą zabezpieczenia oprogramowania, poinformowałem go, że interesuje mnie sam obfuscator dla moich prywatnych celów, jednak odpowiedź jaką uzyskałem postanowiłem opublikować, w sumie jako ostrzeżenie dla innych klientów.

Obfuscator Eazfuscator

Rozmowa zeszła na techniki zabezpieczające aplikacje .NET przed inżynierią wsteczną i uzyskałem odpowiedź, że najlepszą techniką zabezpieczającą jest zmiana nazw klas, zmiennych i nazw funkcji… Dopytałem z ciekawości, czy sobie nie żartuje, ale najwidoczniej nie:

> Renaming is the best obfuscation technique? I hope you’re kidding 🙂
No pun intended. Renaming is the best technique because it is completely
*irreversible *— a Holy Grail property of any protection scheme.

Oleksiy Gapotchenko

Jak widać cała moja wiedza o zabezpieczeniach oprogramowania była jak dotąd uboga, a najlepszą metodą ochrony obfuscatora za 399 USD jest zmiana nazw w skompilowanym pliku. Doh!? Nie mutacja kodu, nie wirtualizacja, żadne dynamiczne szyfrowanie, JITtowanie… tylko zmiana nazw! Nie muszę chyba tłumaczyć nikomu, że nazwy klas, zmiennych czy funkcji nie są nikomu potrzebne przy próbie łamania zabezpieczenia, dotyczy to zarówno aplikacji .NET, natywnych czy Java po zastosowaniu obfusatora i takie stwierdzenia jak powyżej twórcy jednego z najpopularniejszych obfuscatorów stawiają duży znak zapytania nad poziomem ich wiedzy o tym czym są zabezpieczenia oprogramowania.

Rynek obfuscatorów dla .NET, których ceny są astronomicznie wysokie w porównaniu do systemów zabezpieczających 32 i 64 bitowe natywne aplikacje, a poziom skomplikowania zabezpieczeń natywnych wykracza technicznie daleko poza metody stosowane w zabezpieczeniach aplikacji .NET zawsze mnie zastanawiał. Zwłaszcza w obliczu aplikacji takich jak de4dot, które jednym klikiem sprawiają, że wszystkie te drogie zabezpieczenia znikają niczym pieniądze z portfeli klientów, którzy inwestują w tak słomianą ochronę.

Patcher dla C#

Czekałem na koniec świata, ale jakoś go nie było, tymczasem mała klasa w C# do patchowania plików z paroma przydatnymi opcjami jak np. patchowanie stringow Delphi.

using System;
////////////////////////////////////////////////////////////////////////////////
//
// Simple C# Patcher Class for Windows (Complex)
//
// You can patch:
//
// - byte arrays
// - single bytes
// - integers
// - strings
// - delphi strings
//
// Language : C#
// Author   : Bartosz Wójcik
// Website  : https://www.pelock.com
//
////////////////////////////////////////////////////////////////////////////////

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace NetPatcher
{
    class Patcher
    {
        public FileStream inputFile;

        public Patcher()
        {
        }

        ~Patcher()
        {
            CloseFile();
        }

        public bool OpenFile(string filePath)
        {
            try
            {
                inputFile = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite);

                return inputFile == null ? false : true;
            }
            catch (Exception e)
            {
                return false;
            }

            return false;
        }

        public void CloseFile()
        {
            if (inputFile != null)
            {
                inputFile.Close();
            }

            inputFile = null;
        }

        public long PatchArray(long fileOffset, byte[] patchBytes)
        {
            inputFile.Seek(fileOffset, SeekOrigin.Begin);
            inputFile.Write(patchBytes, 0, patchBytes.Length);

            return inputFile.Position;
        }

        public long PatchByte(long fileOffset, byte patchByte)
        {
            inputFile.Seek(fileOffset, SeekOrigin.Begin);
            inputFile.WriteByte(patchByte);

            return inputFile.Position;
        }

        public long PatchString(long fileOffset, string patchString)
        {
            System.Text.Encoding asciiEncoding = System.Text.Encoding.ASCII;
            byte[] encodedPatchString = asciiEncoding.GetBytes(patchString);

            return PatchArray(fileOffset, encodedPatchString);
        }

        public long PatchDelphiString(long fileOffset, string patchString)
        {
            System.Text.Encoding asciiEncoding = System.Text.Encoding.GetEncoding(1250);
            
            byte[] encodedPatchString = asciiEncoding.GetBytes(patchString);

            PatchByte(fileOffset, (byte)encodedPatchString.Length);
            PatchArray(fileOffset + 1, encodedPatchString);

            return inputFile.Position;
        }

        public long PatchInt32(long fileOffset, Int32 patchInt32)
        {
            byte[] encodedInt32 = BitConverter.GetBytes(patchInt32);
            PatchArray(fileOffset, encodedInt32);

            return inputFile.Position;
        }

        public long PatchFill(long fileOffset, long Length, byte patchByteFill)
        {
            while (Length-- != 0)
            {
                PatchByte(fileOffset++, patchByteFill);
            }

            return inputFile.Position;
        }

    }
}

Źródła dostępne także na https://github.com/PELock/Simple-DotNet-Patcher-Class

Narzędzia do analizy aplikacji .NET

Analiza aplikacji .NET

Zestaw narzędzi do analizy .NET-owych aplikacji znacznie różni się od klasycznych narzędzi dla zwykłych aplikacji x86 / x64, odmienna architektura kodu wymusiła utworzenie całej gamy dedykowanych narzędzi dla plików wykonywalnych .NET, spróbuję przedstawić kilka z nich, które mogą się przydać w reversingu.

Dekompilacja kodu

Pierwszym i podstawowym narzędziem, które chyba zna większość osób zajmujących się analizą oprogramowania i nie tylko jest dekompilator .NET Reflector.

.NET Reflector

.NET Reflector

Pozwala on na dekompilację plików wykonywalnych z formatu kodu przejściowego CIL (Common Intermediate Language) do kodu wysokiego poziomu w wybranym lub ulubionym przez nas języku np. C#, VB# etc.

Jest to najbardziej znany dekompilator i nie tylko, gdyż dzięki całej masie wtyczek umożliwia np. modyfikację plików binarnych (wtyczka Reflexil), debugowanie aplikacji (wtyczka Deblector) i wiele innych czynności związanych z analizą kodu.

Ostatnio .NET Reflector stracił na popularności, gdyż projekt od początku istnienia był darmowy, jednak po przejęciu go przez firmę Red Gate Software (notabene twórców obfuscatora SmartAssembly, z czego można wysnuć teorię spiskową, że chcieli ograniczyć dostęp do jednego z najpopularniejszych narzędzi do analizy aplikacji) i początkowych zapewnień o utrzymaniu jego darmowego statusu, po jakimś czasie został przekształcony w komercyjną wersję, z najtańszą licencją za 35 USD. W Internecie można jednak znaleźć złamane kopie jak również całkowicie zrekompilowane (wraz ze źródłami), bardziej odporne na narzędzia zabezpieczające.

Strona domowa — www.reflector.net

Lista wtyczek  — http://reflectoraddins.codeplex.com/

Telerik JustDecompile

Darmowy dekompilator firmy Telerik, raczej dla niewymagających użytkowników, ponieważ liczba dostępnych opcji (lub ich brak) nie powala na kolana, nie można nawet przejrzeć kodu w postaci IL, nie mówiąć o innych rzeczach, jest tu tylko dekompilator i wyszukiwarka referencji.

Strona domowa — www.telerik.com/products/decompiler.aspx

IL DASM

Warto wspomnieć również o tym narzędziu, chociaż z mojego doświadczenia wynika, że jest ono raczej mało używane, to deasembler dla .NET-owych binariów firmy Microsoft dołączany do SDK .NET-a oraz dostarczany wraz z Visual Studio.

Pozwala on na przeglądanie struktury pliku oraz deasembling do kodu przejściowego w związku z czym nie jest tak poręczny w analizie jak .NET Reflector.

IL DASM

Simple Assembly Explorer

Jest to kolejny deasembler i edytor jednak już bardziej zaawansowany od IL DASM-a, posiadający masę opcji, pozwalających na łatwą modyfikację kodu IL, kopiowanie instrukcji, wycinanie, wszystko bardzo poręcznie skonstruowane, od jakiegoś czasu jest to mój faworyt jeśli chodzi o modyfikację .NET-owych plików binarnych.

Simple Assembly Explorer

SAE posiada system wtyczek oraz wbudowany deobfuscator, który może przydać się w analizie zabezpieczonych aplikacji.

Simple Assembly Explorer Deobfuscator

Jeśli chcesz nauczyć się podstaw programowania w IL, zmodyfikować szybko i sprawnie binaria to jest to idealne narzędzie.

Strona domowa — http://code.google.com/p/simple-assembly-explorer/

Dis#

Dis# to stosunkowo mało znany dekompilator, być może ze względu na to, że to komercyjne narzędzie, projekt dawno nie był aktualizowany, ale może być traktowany jako ciekawostka, gdyż posiada kilka interesujących cech jak wbudowany deobfuscator, edytor kodu pozwalający w prosty sposób zamieniać nazwy funkcji, zmiennych etc.

Dekompilator Dis#

Strona domowa — http://netdecompiler.com

Debuggery

Z moich obserwacji i doświadczeń wynika, że większość analiz związanych z .NET da się rozwiązać statycznie (w przeciwieństwie do natywnych aplikacji), jednak i tutaj może przydać się prześledzenie wykonywanego kodu.

Dotnet IL Editor (DILE)

Dile to prosty w obsłudze debugger dla .NET-owych aplikacji, troszkę przypomina debugger z Visual Studio.

DILE

Strona domowa http://sourceforge.net/projects/dile/

Identyfikatory

Dla aplikacji natywnych podstawowym identyfikatorem jest PEiD oraz częściej uaktualniany Protection ID (oraz kilka innych), dla .NET powstał odpowiednik PEiD o nazwie DNiD.

Identyfikator DNiD

Wykrywa on obecnie większość stosowanych zabezpieczeń stosowanych dla aplikacji .NET-owych. Do ściągnięcia lokalna kopia  — DNiD.v0.11-Rue.rar (384 kB)

Dumpery

Aplikacje .NET-owe obecnie rzadko rozprowadzane są w czystej formie po kompilacji, gdyż dzięki narzędziom takim jak .NET Reflector jest to praktyczne równoznaczne z rozprowadzaniem open source i większości wypadków do zabezpieczania używane są obfuscatory.

Część obfuscatorów oprócz modyfikacji kodu IL, całość aplikacji „opakowuje” w kod ładujący (z ang. loader) w formacie natywnym (x86), który zwykle deszyfruje całe .NET-owe assembly i dopiero w odszyfrowanej formie ładuje je do pamięci.

Taka forma zabezpieczenia nie pozwala na używanie narzędzi .NET-owych i wymagane jest najpierw zrzucenie z pamięci załadowanych assembly .NET-owych w celu dalszej analizy.

.NET Generic Unpacker

Projekt autorstwa Daniela Pistelli (obecnie pracujący nad IDA w HexRays), generyczny jak nazwa wskazuje dumper, który potrafi wykryć w pamięci obraz pliku wykonywalnego .NET mimo zewnętrznego natywnego loadera.

.NET Generic Unpacker

Strona domowa —  http://www.ntcore.com/netunpack.php

DotNetDumper

Prosty w użyciu dumper, który również posiada obsługę zrzucania z pamięci plików kilku rodzajów zabezpieczeń, generalnie z tymi dwoma dumperami można sobie poradzić z większością zabezpieczeń natywnych.

DotNet Dumper

Do ściągnięcia lokalna kopia  — DotnetDumper.zip (66 kB)

Oprócz dedykowanych dumperów, równie dobrze działają klasyczne metody przeszukiwania pamięci np. w OllyDbg w poszukiwaniu sygnatur .NET-owych aplikacji (np. stringów „_CorExeMain”).

Fixery

Po zrzuceniu z pamięci obrazu pliku .NET jest on często niezdatny do analizy w narzędziach takich jak .NET Reflector, wynika to ze zmian jakie wprowadzają najczęściej obfuscatory, aby dodatkowo utrudnić analizę. Tak zrzucone obrazy plików należy naprawić.

Universal Fixer

Universal Fixer

Do ściągnięcia lokalna kopia  — Universal_Fixer.zip (31 kB)

Zawsze aktualna kopia — http://forum.tuts4you.com/topic/25376-universal-fixer/

Deobfuscatory

Liczba obfuscatorów dostępnych na rynku jest ogromna, mogę śmiało powiedzieć, że przebija ilość dostępnych narzędzi zabezpieczających dla natywnych aplikacji. W związku z tak zmasowanym atakiem, powstały deobfuscatory dla wielu narzędzi, często zintegrowane i obsługujące wiele rodzajów zabezpieczeń.

de4dot

de4dot to aktywnie rozwijany deobfuscator obsługujący sporą listę zabezpieczeń, podstawa jeśli ktoś myśli poważnie o analizie oprogramowania .NET

Deobfuscator de4dot

Do ściągnięcia ze strony —  https://github.com/0xd4d/de4dot/downloads

Podsumowanie

Obecny stan zabezpieczeń .NET-owych może z początku przerazić i zniechęcić do dalszej analizy, ale jak widać istnieje wiele narzędzi, które potrafią ułatwić nam życie.

Celowo nie opisywałem tutaj gotowych unpakerów, które można z łatwością znaleźć samemu, gdyż nie zawsze sprawdzają się w działaniu i warto wtedy wiedzieć jak sobie poradzić bez ich pomocy.

Jeśli natknę się na jakieś ciekawe narzędzie to dopiszę je do artykułu, a jeśli wy znacie coś interesującego do analizy aplikacji .NET — opiszcie w komentarzach, a ja to przejrzę i z chęcią dodam opis do artykułu.

Na koniec jeszcze kilka linków, gdzie można znaleźć narzędzia do analizy .NET-owych aplikacji:

BlackStorm — http://portal.b-at-s.net/download.php?list.9

RCE Tool Library — http://www.woodmann.com/collaborative/tools/index.php/Category:.NET_Tools

Native Blocks

Native Blocks to projekt Daniela Pistelli, obecnie powiązanego z HexRays, jest to reassembler dla aplikacji napisanych w środowisku .NET. W skrócie można napisać, że służy on do modyfikacji kodu ILASM, wykorzystując dodatkowe skrypty JavaScript, dzięki czemu możliwe staje się proste usunięcie zabezpieczeń i technik stosowanych przez obfuscatory dla aplikacji .NETowych.

Prezentowany przykład pokazuje jak usunąć sztuczki używane przez obfuscator Wise Owl’s Demeanor (chyba najdroższy obfuscator dla .NET $799) w oprogramowaniu Reflector. Materiał (jeśli ktoś jeszcze nie widział, bo już trochę to wisi na necie) warty obejrzenia, bo pokazuje nowe drogi do analizy zabezpieczonego oprogramowania .NET.

Oryginalny artykuł — http://rcecafe.net/?p=96

Crackme .NET – Rozwiązania

Jakiś czas temu napisałem o konkursie na crackme dla .net zorganizowanym przez Krakowska Grupa Developerów .NET, dzisiaj z ciekawości tam zajrzałem i zobaczyłem, że jest tylko 1 rozwiązanie, więc postanowiłem zadziałać.

1. NemCrackMe # 1 by Pawlos

Standardowy schemat name/serial:

NemCrackMe # 1 by Pawlos

Szybka wrzutka do Reflectora i dekompilacja kodu po kliknięciu „Please Crack Me”:

cm1_check_serial

Mówiąc w skrócie, kod na powyższym obrazku, wywołuje jakąś magiczną funkcję (która jest dynamicznie tworzona) z 2 parametrami, którymi są teksty z kontrolek edycyjnych:

private void button1_Click(object sender, EventArgs e)
{
    Type type = this.tBuilder.CreateType();
    type.GetMethod("o").Invoke(Activator.CreateInstance(type), new object[] { this.textBox1.Text, this.textBox2.Text });
}

Jak nietrudno zgadnąć jest to właściwa metoda sprawdzająca poprawność hasła. Ok, tylko gdzie ona jest hmm? Bez większego doświadczenia w programowaniu w C#, po prostu kliknąłem w inne metody crackme, w tym w konstruktor formy i wszystko stało się jasne:

cm1_dynamic_code

Crackme generuje funkcję sprawdzającą poprawność wpisanego hasła w konstruktorze formy, funkcja generowana jest dynamicznie w pamięci wykorzystując składnię języka IL.

Składnia języka IL może nie jest jakaś szczególnie zawiła, ale zrozumienie tego kodu bez jakiegokolwiek doświadczenia może być trudne, idealnie byłoby gdybyśmy mogli skorzystać z Reflectora do zdekompilowania kodu.

Aby to zrobić, utworzyłem pomocniczy program, który tworzy assembly .net-we z powyższą procedurą i zapisuje je do pliku DLL, dzięki czemu można go wgrać do Reflectora i pokaże nam wszystko ładnie na tacy. Po dekompilacji otrzymałem taki algorytm:

public class MyDynamicType
{
    // Fields
    private int[] coes;
    private int num1;
    private int num2;

    // Methods
    public MyDynamicType();
    public void o(string, string);

// konstruktor
public MyDynamicType()
{
    this.num1 = 0x654321;
    this.num2 = 0x123456;
    this.coes = new int[] { 0x21, 12, 0x5d3, 110, 5, 0x610, 8 };
}

public void o(string text1, string text2)
{
    long num = 0x73L;
    int num2 = 1;
    foreach (char ch in text1)
    {
        num += Math.Abs((int) (ch * this.coes[num2++ % this.coes.Length]));
    }
    num += this.num1;
    num -= this.num2;
    if (text2 == num.ToString())
    {
        MessageBox.Show("Udało się", "NemCrackMe");
    }
}

}

Name: bartosz Password: 5779531

No i to by było na tyle. Użyte zabezpieczenie raczej nie jest dobrym przykładem, gdyż nawet bez większego doświadczenia można zrozumieć zasadę jego działania, myślę, że dodatkowym utrudnieniem byłaby obfuskacja instrukcji sprawdzających numer seryjny, choćby seria zmieniająca codeflow (swoją drogą to niezła baza do takich eksperymentów).

Generator kluczy oraz pomocniczy program tworzący assembly dostępny jest na końcu artykułu wraz z innymi rozwiązaniami.

Dzisiaj przeglądałem kolekcję wtyczek do Reflectora i jest tam plugin o nazwie ReflectionEmitLanguage, który z kodu IL generuje kod w HLL generujący dynamiczny kod IL wskazanej metody i chyba (głowy nie dam) właśnie to zostało użyte do wygenerowania funkcji sprawdzającej hasło w tym crackme.

2. PIJA CrackMe 1

Chyba najciekawsze crackme i zarazem stosujące dobre zabezpieczenia. Ale od początku:

PIJA CrackMe 1

Crackme wymaga wpisania numeru seryjnego i kodu aktywacyjnego, jednocześnie wyświetla kod identyfikacyjny (jak nietrudno zgadnąć, będzie to mieć coś wspólnego z identyfikatorem naszego sprzętu).

Szybka dekompilacja pod Reflectorem metody na naciśniecie buttona sprawdzającego:

private void enter_Click(object sender, EventArgs e)
{
    try
    {
        byte[] buffer3;
        string text = this.txtSerial.Text;

        // generuj klucz deszyfrujacy
        byte[] key = Utils.GetKey(this.regKey, text);

        // generuj wektor inicjujacy
        byte[] iV = Utils.GetIV();

        Rijndael rijndael = Rijndael.Create();
        rijndael.Padding = PaddingMode.Zeros;

        // otworz plik crackmelib.dll
        using (FileStream stream = File.Open("crackmelib.dll", FileMode.Open))
        {
            buffer3 = new byte[stream.Length];
            new CryptoStream(stream, rijndael.CreateDecryptor(key, iV), CryptoStreamMode.Read).Read(buffer3, 0, (int) stream.Length);
        }

        // wywolaj funkcje A() z odszyfrowanej (w pamieci) biblioteki crackmelib.dll
        ((IA) Assembly.Load(buffer3).CreateInstance("crackmelib.B")).A(this.txtActivationCode.Text);
    }
    catch
    {
        MessageBox.Show("Uuuups! Try again");
    }
}

Dla rozjaśnienia kolejne kroki:

  1. Generowany jest klucz deszyfrujący AES na podstawie „Activation request” i „Serial”
  2. Generowany jest wektor inicjujący dla AES
  3. Plik „crackmelib.dll” zostaje odszyfrowany w pamięci według ustawionych parametrów
  4. Odszyfrowana biblioteka „crackmelib.dll” zostaje załadowana do pamięci
  5. Z biblioteki „crackmelib.dll” zostaje wywołana funkcja A() z parametrem jakim jest kod kod aktywacyjny

Użycie wychwytywania wyjątków z użyciem try{} catch{} sugeruje, że albo klucz deszyfrujący będzie poprawny i nastąpi poprawne odszyfrowanie i załadowanie assembly z pliku „crackmelib.dll”, albo nastąpi wyjątek.

Activation request

Wyświetlany po uruchomieniu crackme identyfikator generowany jest w konstruktorze głównej formy crackme:

cm2_request_code

Za jego generację odpowiada biblioteka „CommonInterface.dll”:

public static string GetRequestCode()
{
    return (GetProcessorID() + GetBIOSSerialNumber());
}

private static string GetProcessorID()
{
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from Win32_Processor");
    foreach (ManagementObject obj2 in searcher.Get())
    {
        foreach (PropertyData data in obj2.Properties)
        {
            if (data.Name.ToUpper() == "PROCESSORID")
            {
                return data.Value.ToString();
            }
        }
    }
    return null;
}

private static string GetBIOSSerialNumber()
{
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from Win32_BIOS");
    foreach (ManagementObject obj2 in searcher.Get())
    {
        foreach (PropertyData data in obj2.Properties)
        {
            if (data.Name.ToUpper() == "SERIALNUMBER")
            {
                return data.Value.ToString();
            }
        }
    }
    return null;
}

Odczytywanie danych o procesorze i danych BIOS odbywa się poprzez klasy interfejsu WMI (przykłady użycia na CodeProject).

Wektor inicjujący AES

Wektor generowany jest w kodzie biblioteki „CommonInterface.dll”:

public static byte[] GetIV()
{
    foreach (object obj2 in Assembly.GetExecutingAssembly().Evidence)
    {
        if (obj2 is Hash)
        {
            return ((Hash) obj2).MD5;
        }
    }
    return new byte[0];
}

Kilka słów wyjaśnienia, metoda GetExecutingAssembly() zwraca assembly, w którym znajduje się aktualnie wykonywany kod, czyli w tym przypadku będzie to assembly „CommonInterface.dll”.

Następnie enumerowane są wszystkie elementy assembly określone jako Evidence, które są obiektem typu Hash. Może to troche skomplikowane, bo dokumentacja też tego jakoś prosto nie wyjaśnia, ale debugging wykazał, że obliczana jest po prostu suma MD5 z pliku „CommonInterface.dll”.

Czyli wektor inicjujący stanowi sumę kontrolną pliku crackme, jest to zapewne zabezpieczenie przed patchowaniem pliku, bo jakakolwiek zmiana, spowoduje wygenerowanie błędnego wektora inicjującego.

Klucz deszyfrujący AES

Klucz deszyfrujący generowany jest w funkcji GetKey() znajdującej się w tej samej bibliotece „CommonInterface.dll”:

public static byte[] GetKey(string reques, string serial)
{
    byte[] buffer = new byte[] {
        0, 5, 7, 0x59, 0x38, 0x22, 0x4e, 0x22, 0x38, 0x4e, 0x17, 0x2d, 9, 12, 0x38, 0x4e,
        0x22, 0x57, 90, 0x6f, 0x17, 0x4e, 0x62, 0x17, 0x38, 12, 0x5f, 0x2c, 0x43, 0x43, 12, 0x17
     };

    // pobierz 1 czesc identyfikatora sprzetowego (o dlugosci polowy buffer)
    byte[] bytes = Encoding.ASCII.GetBytes(reques.Substring(0, buffer.Length / 2));

    // numer seryjny z base64 zamien na bajty
    byte[] buffer3 = Convert.FromBase64String(serial);

    // klucz deszyfrujacy = pierwsza_czesc_hardware_id[] + numer_seryjny[]
    for (int i = 0; i < (buffer.Length / 2); i++)
    {
        buffer[i] = (byte) (bytes[i] + buffer3[i]);
    }

    return buffer;
}

O sile tego crackme świadczy właśnie ta powyższa procka, otóż cała reszta kodu znajdująca się w crackmelib.dll jest zaszyfrowana algorytmem AES z kluczem o długości 32 bajtów (256 bitów), klucz deszyfrujący tworzony jest z pary identyfikatora sprzętowego i numeru seryjnego (+reszta tablicy buffer) i musi być poprawny, inaczej odszfrowany kod będzie nieprawidłowy i nie będzie można załadować tego assembly.

Krótko mówiąć bez znajomości poprawnej pary identyfikator + serial nie można odszyfrować reszy kodu potrzebnego do pracy. Można próbować brute forcem znaleźć poprawną kombinację pierwszych 16 bajtów tablicy buffer (128 bitów), jednak jak przypuszczam taka próba zajęłaby więcej czasu niż czekanie przeze mnie na prezenty od świętego mikołaja za dobre zachowanie 😉

Schemat ten przypomina stosowane w exe-protektorach makra szyfrujące kod według danych licencyjnych, ma to swoje zalety i wady, ale z pewnością można powiedzieć, że to dobre zabezpieczenie z punktu widzenia łamiącego.

Po tej szybkiej analizie miałem już sobie odpuścić, jednak z ciekawości zajrzałem jeszcze raz na stronę konkursową i mój wzrok przykuło rozwiązanie własnie do tego crackme i temat na konkursowym forum, gdzie było zamieszczone ogłoszenie o umieszczeniu crackme oraz HA! autor podaje poprawną serię kodu identyfikatora sprzętowego, numeru seryjnego oraz kodu aktywacyjnego. No to teraz z górki :).

Mając te dane można stworzyć dekoder (w samym crackme nie można wpisać tego podanego identyfikatora sprzętowego), który wykorzystując podane parametry odszyfruje plik crackmelib.dll:

        public static void DecodeAssembly()
        {
            Assembly asmCrackme;
            string asmFile = "CommonInterface.dll";

            // sciezka, gdzie znajduje sie crackme
            string asmPath = Path.Combine(Directory.GetCurrentDirectory(), asmFile);

            Console.WriteLine("[i] Loading {0} assembly.", asmFile);

            // zaladuj assembly z pliku
            asmCrackme = Assembly.LoadFile(asmPath);

            if (asmCrackme == null)
            {
                Console.WriteLine("[!] Cannot load assembly!");
                return;
            }

            // dekoduj tak jak to robi crackme
            try
            {
                // zestaw poprawnych seriali podanych przez autora na forum konkursowym:
                // http://ms-groups.pl/kgd.net/Konkurs_CrackMe/Lists/Forum%20konkursowe/Flat.aspx?RootFolder=%2Fkgd%2Enet%2FKonkurs%5FCrackMe%2FLists%2FForum%20konkursowe%2FPIJA%20CrackMe%201&FolderCTID=0x0120020070660992493BC44DA19C94BED291A957
                // (inaczej pozostaje brute force 128 bitowej wartosci)
                string validRequest = "BFEBFBFF00010676BPSHY3J";
                string validSerial = "9gfHIoPHxMVLJzPk+QKHeg==";
                string validActivation = "+1XY8HQBb8KmSaMS/X8RUDafoZOIhHZb9bpgiYwW78Cpnz1j9WfdwpQ+5Yq8RU9l";

                byte[] buffer3;
                byte[] key = GetKey(validRequest, validSerial);
                byte[] iV = GetIV(asmCrackme);

                Console.WriteLine("[i] Decryption key:\r\n\r\n{0}", HexDump(key));
                Console.WriteLine("[i] Initialization vector:\r\n\r\n{0}", HexDump(iV));

                Rijndael rijndael = Rijndael.Create();
                rijndael.Padding = PaddingMode.Zeros;

                using (FileStream stream = File.Open("crackmelib.dll", FileMode.Open))
                {
                    buffer3 = new byte[stream.Length];
                    new CryptoStream(stream, rijndael.CreateDecryptor(key, iV), CryptoStreamMode.Read).Read(buffer3, 0, (int)stream.Length);
                }

                // zapisz zdekodowany plik
                File.WriteAllBytes("crackmelib.decoded.dll", buffer3);

                Console.WriteLine("[i] Assembly decoded successfully.");
            }
            catch
            {
                Console.WriteLine("[!] Cannot decode file (invalid keys?)!");
                return;
            }
        }

Po odpaleniu tego kodu, otrzymamy ładne assembly z ostatnim fragmentem crackme.

Analiza crackmelib.dll

Pamiętacie, po odszyfrowaniu tej biblioteki, wywoływana była metoda A() z parametrem, jakim był kod aktywacyjny:

cm2_crackmelib

Metoda na początku porównuje hash pliku „PIJA_Crackme.exe” z zakodowaną wartością i jeśli się nie pokrywają, kasowany jest plik crackmelib.dll, a następnie tworzona jest jego kopia z losową zawartością:

private void MessFile()
{
    int count = 0x1800;
    File.Delete("crackmelib.dll");
    FileStream stream = File.Create("crackmelib.dll");
    byte[] buffer = new byte[count];
    Random random = new Random();
    for (int i = 0; i < count; i++)
    {
        buffer[i] = (byte) random.Next(0xff);
    }
    stream.Write(buffer, 0, count);
    stream.Flush();
    stream.Close();
}

Jeśli ktoś zpatchuje główny plik crackme, nadpisana zostanie zaszyfrowana biblioteka sprawdzająca poprawność kodu aktywacyjnego i wszelkie następne próby rejestracji skończą się niepowodzeniem.

Keygen i pomocnicza aplikacja dekodująca znajduje się na końcu artykułu, jeszcze dla jasności:

cm2_cracked

Było to najlepsze crackme ze wszystkich, a użyte zabezpieczenia znakomicie sprawdzają się w warunkach bojowych,  wzajemnie sprawdzane sumy kontrolne są doskonałą metodą na ochrone przed patchowaniem (oczywiście bardziej ukryte), a zastosowany algorytm szyfrujący sprawia, że złamanie tak zabezpieczonej aplikacji bez poprawnego klucza jest praktycznie niemożliwe.

Na koniec jeszcze małe TheDailyWTF, które wyświetliło się podczas debugowania crackme pluginem Deblector (który i tak się do niczego raczej nie przydał):

cm2_zombie

Zombies ahead?

3. MKS CrackMe 1

Bardzo proste crackme, dodatkowo opublikowane razem z rozwiązaniem (WTF?) .

MKS CrackMe 1

W zasadzie, jedynym problemem jest to, że domyślnie wyłączony jest button do sprawdzania hasła:

private void Form1_Load(object sender, EventArgs e)
{
    this.button1.Enabled = false;
}

W rozwiązaniu zaproponowanym przez autora crackme (hehe nie moge ;)) użyty jest plugin Reflectora o nazwie Reflexil do spatchowania tego kodu, my jednak będziemy oryginalni i użyjemy IDA do znalezienia kodu wyłączającego button:

cm3_patch

Jak widać na obrazku powyżej, mamy tam instrukcje „ldc.i4.0” (flaga FALSE), która sprawia, że button jest wyłączany, trzeba tam wrzucić instrukcję „ldc.i4.1”, problemem może być znalezienie jej hexadecymalnego odpowiednika, ale wystarczy przewinąć deadlisting w IDA (albo zajrzeć na stronę MSDN z listą opcodów albo tutaj), żeby znaleźć jakiś inny kod, w którym ją znajdziemy, potem tylko szybki patch i button do sprawdzania hasła będzie włączony.

Algorytm sprawdzania hasła jest banalnie prosty:

private void button1_Click(object sender, EventArgs e)
{
    if ((this.textBox1.Text.Length > 0) && (Convert.ToBase64String(Encoding.Default.GetBytes(this.textBox1.Text)) == this.textBox2.Text))
    {
        MessageBox.Show("You are great", "Registration");
    }
    else
    {
        MessageBox.Show("You failed. Try again.", "Registration");
    }
}

Name: bartosz Password: YmFydG9zeg==

Crackme może nie jest oryginalne, ale naprowadziło mnie na plugin Reflexil, który przyda się w przyszłości (pozwala na manipulację .net-owych assemblies i zapisywanie zmian do plików), tak więc zawsze jakis plus 🙂

4. SimpleCrackMe

Chyba najprostsze crackme z całej listy (w wersji widnieje nazwa firmy Comarch).

SimpleCrackMe

Do złamania wystarczy zmusić crackme do włączenia buttona. Jak to zrobić? Posłużymy się opisanym wyżej pluginem Reflexil. Kod wyłączający buttona zwykle znajduje się w procedurach ładowania okna lub inicjowania komponentów.

Po jego znalezieniu, z menu Tools, wybieramy Reflexil i w prawym panelu, na dole, pojawia się okno Reflexila z kodem IL bieżącej procedury:

cm4_patch

Mamy podobną sytuację jak w przypadku poprzedniego crackme. Należy zmodyfikować flagę FALSE, która wykorzystana jest do wyłączenia buttona. Aby to zrobić, klikamy prawym przyciskiem myszki na zaznaczony opcode i wybieramy EDIT, wyświetli się nam okno z opcją podmiany instrukcji:

cm4_edit_op

Na liście combo znajdują się wszystkie dostępne instrukcje, wybieramy więc „ldc.i4.1” (dokładnie tak jak w poprzednim crackme). Teraz, aby zapisać zmiany, należy kliknąć w drzewku Reflectora ikonkę modułu SimpleCrackme.exe i w oknie Reflexila pojawi się opcja zapisu:

cm4_save

Tutaj wystąpuje jeden problem. Otóż crackme jest podpisane cyfrowo (tzw. StrongNamed Assemblies) i zapisanie zmodyfikowanej wersji crackme spowoduje jego zawieszenie podczas uruchamiania.

Przy próbie zapisu zmodyfikowanego crackme, Reflexil pozwala wybrać co z tym fantem zrobić:

cm4_signed

Z dostępnych opcji należy wybrać opcję usunięcia sygnatury strong name a następnie wybrać „Cancel and keep it delay signed”, albo jak komuś się chce, to można się pobawić w ponowne podpisywanie, własnym kluczem. Nie jestem do końca obeznany z tematem podpisywania assemblies, więc jeśli popełniłem jakiś błąd, skorygujcie mnie.

W każdym razie, po tych zmianach crackme się ładnie uruchamia i można klikać w button:

cm4_cracked

Finał

Na tle wszystkich crackme, wyróżniło się crackme PIJA CrackMe 1. Wniosek jaki mi się jednak nasuwa jest taki, że jakiekolwiek zabezpieczenia .net nie mają sensu bez zastosowania obfuscatorów (i to dobrych).

Keygeny

Paczka z kodami dodatkowych narzędzi i generatorów kluczy:

konkurs_dotnet_crackme.zip (4.51 MB)

Konkurs na Crackme w .NET

W zasadzie nie wiem co mnie opętało, że o tym piszę, może fakt, że nic tu od dawna nie było pisane.

No więc tak, konkurs na najbardziej ehem zaawansowane technologicznie zabezpieczenie .NET

http://ms-groups.pl/kgd.net/Konkurs_CrackMe/default.aspx

Osobiście nie widziałem żadnego polskiego softu do zabezpieczania .NET-ów, więc poziom „zaawansowania” tych crackmesów jest równie „wysoki” jak wiedza ich autorów o tworzeniu crackmesów jak i zabezpieczaniu aplikacji, więc nie spodziewajcie się wodotrysków…