Hacking and Cracking #1: Warum C#, Java und co. nicht für proprietäre Software taugt

Ich werde in diesem Artikel ganz bewusst die Namen der beiden Programmen, an denen ich mich versucht habe, verzichten, da ich den Entwicklern und Herstellern keinen Schaden machen will.

Immer mehr Firmen entwickeln in immer leichteren Programmiersprachen: Typische Beispiele sind hierbei C#, VB.NET und Java. Allerdings sind Programme von Firmen meist mit Lizenzmodelenen oder anderweitigem bestückt. Was viele Hersteller jedoch nicht beachten, ist dass die Compiler dieser drei Programmiersprachen nicht wie bei C oder C++ nach Maschinencode, sondern nach Bytecode übersetzen. Und aus Bytecode lässt sich mehr oder weniger dem Originalsourcecode äquivalenten Code erzeugen. Als Entwickler hat man nun drei Möglichkeiten diesem unerwünschten Verhalten entgegenzuwirken:

  1. Man Programmiert in einer Sprache, die sich nicht derart einfach decompilen lässt.
  2. Man lässt das ganze Lizenzding einfach sein und stellt das Programm unter OpenSource.
  3. Man benutzt einen Obfuscator, der den Sourcecode soweit wie möglich unleserlich macht (dies ist bereits eine gute Methode seinen Quellcode zu schützen, decompilt kann er allerdings nach wie vor werden).

Kleines Praxisbeispiel

Ich habe hier als erstes ein Programm eines Herstellers genommen, dass zur Konfiguration und Abfrage von Daten eines Hardwaregeräts dient. Da das Programm in C# geschrieben wurde, benutze ich als Decompiler ILSpy. Nun zur Analyse:

Als ich die Software gestartet hatte, wurde ich gleich von einem kleinem Fenster begrüßt, das mir mitteilte, dass ich die Demo-Version noch 60-Tage lang nutzen darf. Nach einem Klick auf OK öffnete sich nun das Hauptprogramm. Jetzt weiß ich bereits, dass die Lizenzüberprüfung noch durchgeführt wird, bevor das Hauptfenster überhaupt das erste mal gezeichnet wird. Zudem erkannte man an der Programmoberfläche, dass die Software mittels WPF (Windows Personal Foundation) gestaltet wurde. Nach diesen Informationen wäre der erste Punkt, wo ich im Sourcecode nachsehen würde, die Methode Application_Startup in der App.cs. Und ich wurde fündig. Ab Zeile 40 war der Teil für die Lizenzüberprüfung zuständig (leicht abgeändert, ebenfalls um den Hersteller zu schützten):

if (File.Exists(c.LicenceDat))
{
    try
    {
        object obj = null;
        gz.LoadObj(c.LicenceDat, ref obj, false, true);
        gl.Licence = (Licence)obj;
        LincenceRequest lincenceRequest = new LincenceRequest();
        lincenceRequest.FillElements();
        if (lincenceRequest.GetFullStr() != gl.Licence.LicStr)
        {
            //Fehler werfen
        }
        goto IL_EC;
    }
    catch (Exception pExc)
    {
        //Fehlerbehandlung
    }
}
gv.Licence = new Licence();

Wenn man sich jetzt noch den Code der LoadObj ansieht hat man alle benötigten Informationen, um sich ein kleines Programm zu schreiben, welches einem eine gültige Lizenzdatei erstellt:

using (FileStream fileStream = new FileStream(pFile, FileMode.Open, FileAccess.Read))
{
    using (DeflateStream deflateStream = new DeflateStream(fileStream, CompressionMode.Decompress, true))
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        object obj = binaryFormatter.Deserialize(deflateStream);
        pObj = obj;
    }
}

Mit dieser Methode wird das Objekt aus der Datei geladen. Man benötigt nur noch das Gegenstück zum Abspeichern, welches sich ganz leicht aus der LoadObj ableiten lässt (Decompress -> Compress, Deserialize -> Serialize, Read -> Write).

Alle Informationen aus dem Quellcode zusammengefasst, kann man sich nun seinen Crack bauen. Dazu erstellt man sich zuerst mal ein neues Projekt im Visual Studio (oder mit was man auch entwickelt) und fügt gleich eine DLL des Programms hinzu (darin sind die Klassen Licence und LicenceRequest ausgelagert). Den Code zur Erstellung der Lizenzdatei lässt sich aus dem Originalsourcecode ableiten:

using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;

namespace DLLNAMESPACE
{
    class Program
    {
        static void Main(string[] args)
        {
            LincenceRequest lincenceRequest = new LincenceRequest();
            lincenceRequest.FillElements();
            Licence l = new Licence();
            l.LicStr = lincenceRequest.GetFullStr();
            try
            {
                using (FileStream fileStream = new FileStream(Environment.GetEnvironmentVariable("userprofile") + @"\Desktop\Lizenz.dat", FileMode.Create, FileAccess.Write))
                {
                    using (DeflateStream deflateStream = new DeflateStream(fileStream, CompressionMode.Compress, true))
                    {
                        BinaryFormatter binaryFormatter = new BinaryFormatter();
                        binaryFormatter.Serialize(deflateStream, l);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

Bei einem anderen Programm konnte ich mir aus der Quellcodeanalyse folgenden Code zur Erstellung einer Lizenz ableiten:

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using Microsoft.VisualBasic;

namespace PROGRAMMCrack
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "PROGRAMM Crack";
            Console.Write("Register for: ");
            string name = Console.ReadLine();
            Console.WriteLine();
            string serial = EncryptBase64(new Random().Next(150000, 200000).ToString(), "PROGRAMM");
            string registered = EncryptBase64(serial + name, "PROGRAMM");
            Console.WriteLine("Put the following lines into your config.ini:");
            Console.WriteLine();
            Console.WriteLine("[Serial]");
            Console.WriteLine("Serialnumber=" + serial);
            Console.WriteLine("Registered=" + registered);
            Console.WriteLine();
            Console.ReadKey();
        }

        private static string EncryptBase64(string text, string key)
        {
            byte[] Key = new byte[0];
            byte[] IV = new byte[] { 18, 52, 86, 120, 144, 171, 205, 239 };
            Key = Encoding.UTF8.GetBytes(Strings.Left(key, 8));
            DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider();
            byte[] bytes = Encoding.UTF8.GetBytes(text);
            MemoryStream memoryStream = new MemoryStream();
            CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateEncryptor(Key, IV), CryptoStreamMode.Write);
            cryptoStream.Write(bytes, 0, bytes.Length);
            cryptoStream.FlushFinalBlock();
            return Convert.ToBase64String(memoryStream.ToArray());
        }
    }
}

Weiterführende Links

[Java] Bytecode Basics

Schreibe einen Kommentar

Your email address will not be published / Required fields are marked *