Reversing ist kein Ponyhof - InterCafe RE

Völlig deprimiert setze ich hiermit einen Blogpost ab, um meine RE-Ergebnisse bis hierhin zu verarbeiten und zu teilen. Dieser Blogpost wird sehr theoretisch, langwierig und schwer zu verstehen sein, aber wie schon gesagt. Reversing ist kein Ponyhof!

An dieser Stelle ein großes Lob an die Entwicker dieses Programmes. Hier ist es nicht mit einem 1-Byte-Patch und einem emulierten Server auf localhost getan, sondern mit verschlüsselten Server-Abfragen, viele verschachtelte Prüfmethoden, dynamisch generierte Forms und Prüfungen, ob das File modifiziert wurde.

Aber zum Anfang der Geschichte. Vor kurzem sah ich auf einem RE-Board eine Crackanfrage dieses .NET Programms. Es ist eine Internetcafe-Software, mit einem Server, Client und einem ServiceHoster. Nachdem ich die letzen 2 Tage im Urlaub war, fiel mir auf, dass ich diese Software schon heruntergeladen auf meinem Rechner hatte. Also ging es los:

Als erstes zückte ich, in Ermangelung des Redgate .NET Reflectors, ildasm.exe und erstellte mir einen vollständigen Dump der !733! Klassen. Nach einem kurzen suchen, wurde auch sofort folgender String gefunden:

IL_011a: ldstr “Die eingegebenen Registrierdaten sind falsch.”

Weiter unter sieht man sehr schön die If-Abfrage und der folgende Jump, welchen ich Euch natürlich nicht vorenthalten möchte:

    IL_0053:  ldarg.0 // Param[0] auf den Stack
    IL_0054:  ldfld      class [System.Windows.Forms]System.Windows.Forms.Button ht::h ; Läd die Adresse von dem Registrieren-Button
    IL_0059:  ldc.i4.0 ; Läd false auf den Stack
    IL_005a:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Enabled(bool) ; und den Button deaktivieren
    IL_005f:  ldarg.0 ; Param[0] auf den Stack
    IL_0060:  ldarg.0 ; Param[0] auf den Stack
    IL_0061:  ldfld      class si ht::k ; Läd die Adresse von der Textbox-Klasse
    IL_0066:  callvirt   instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text() ; Holt sich den Text (Paramter 1 für a()) und pusht ihn auf den Stack
    IL_006b:  ldarg.0
    IL_006c:  call       instance string ht::b() ; statischer Call -> Param2 aufm Stack
    IL_0071:  ldarg.0
    IL_0072:  call       instance string ht::c() ; Param3 aufm Stack
    IL_0077:  call       instance bool ht::a(string,
                                             string,
                                             string) ; Called die finale CheckSerial() Diese liefert einen Bool auf dem Stack zurück
    IL_007c:  brfalse    IL_0114 ; Wenn dieser Bool "false" ist -> Jump unregistriert (Messagebox)
    IL_0081:  br         IL_000d ; Sonst weiter mit registriert

Wie für erfahrenere unschwer zu erkennen ist, kann durch das patchen zu “brtrue” dieses Nag übergangen werden. Also hab ich diese Änderung vollzogen und über ilasm.exe Server.il das ganze neu compiled. Die Exe startet zwar auch, meldet sich aber sofort mit “Dieser Server hat eine andere Version als der Service. Wollen sie ein Update durchführen?”. Da ich kein Internet hatte, und auch kein Update machen wollte, wurde auf einen Druck auf “Nein” das Programm beendet. Da dachte ich mir: “Warum patched Du nicht einfach diese Überprüfung weg?”. Um es abzukürzen, hier der C#-Code dieses Nags:

    private void a()
    {
        string text = MultiLang.ml.ml_string(0xb6e, "Dieser Server hat eine andere Version als der Service.") + " " + MultiLang.ml.ml_string(0xb6f, "M\x00f6chten Sie ein Update durchf\x00fchren?");
        al0.aq.ar();
        if (MessageBox.Show(text, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk) == DialogResult.Yes)
        {
            while (File.Exists(avm.a() + @"\ServerUpdater.exe"))
            {
                new alf().a(avm.a() + @"\ServerUpdater.exe", "", false);
                break;
            }
        }
        al0.aq.Close();
    }

Da bahnte sich in meinem Kopf schon der erste große Fragezeichen an. Die ganze Klasse hat KEINEN AUFRUF der a()-Funktion, und da diese weder static, noch public ist, kann sie auch somit von irgendwo anders gecalled werden! Der Reflector verweist auf eine Funktion in am5::private void b(string A_0), doch diese kann nicht decompiled werden und aus dem IL-Code kann ich kein call entnehmen.

Wie dem auch sei, ich patchte im IL-Code die Form.Close-Funktion weg und siehe da, das Programm läuft weiter. Allerdings nur solange, bis es keinen Server findet, bei 127.0.0.1 abbricht und das Programm abstürzt. Dieses hab ich übrigens erst später herrausgefunden. Auf jeden Fall gab ich es für diesen Moment auf IL-Code zu patchen.

Ich besorgte mir also die Reflector 7 Version und zog einen Dump der 700 Klassen. Dort begann die Analyse der vorhin erwähnte Klasse des 1-Byte-Patches. Ich begann wieder mit Funktionsanalyse, Namensgebung und Kommentieren. Irgendwann kristallisierte sich dann ein Bild herraus. Bevor ich aber zu dem Ablauf des Serial-Checks komme, hier mal der Klassenheader wie er für den Check gebraucht wird:

private static byte[] arStaticByteUnknown1E = new byte[] {
        0x22, 0x48, 0x57, 0x[..] }; // Ein Art Salt, mal im Hinterkopf behalten
    private Button btnCancel; // Schließt die Reg-Form
    private Button btnOk; // Checkt den Reg-Key
    private Label lblHardwareCode; // Hier wird die Hardware-ID gefüllt
    private si tbxRegName; // Hier kommt der Regname rein
    private s9 ucSoftwareCode; // S9 = Usercontrol mit 8 Textboxen, je 4 Zeichen
    private s9 ucSoftwareKey; // Für den Code und für den Key
    private Button btnExtendTestTime; // Hiermit kann man die Testzeitverlängerung anfordern

Schonmal eine Menge an Daten, nicht? Vorallem ist wichtig, dass man versteht dass es sich bei S9 ucSoftwareCode etc um ein Usercontrol mit 8 Textboxen handelt. Wer sich das nicht vorstellen kann, hier mal ein Bild von der RegForm:

Die Form wird, wie schon erwähnt, komplett dynamisch zur Runtime geladen. Und die Namensgebung der einzelnen Controls machte die Sache nicht wirklich einfacher -.-

public FormReg(string HardwarecodeStringParam, string A_1, string A_2, string A_3, string VersionStringParam, bool A_5, bool A_6)
    {
        this.Init();
        this.d = A_6;
        this.lblHardwareCode.Text = HardwarecodeStringParam;

Hier ist ein Teil des Konstruktors zu sehen. Dabei wird die HardwareID übergeben und das Label damit gefüllt.

private void BtnOK_Click(object A_0, EventArgs A_1)
    {
        if (this.c)
        {
            base.Close();
        }
        if (((this.tbxRegName.Text != "") && (this.GetStringSoftwareCode() != "")) && !(BtnOk_Click() == ""))
        {
            this.btnOk.Enabled = false; // Reg Button disablen
            if (!this.CheckRegKey(this.tbxRegName.Text, this.GetStringSoftwareCode(), GetStringSoftwareKey()))
            {
                this.b = MultiLang.ml.ml_string(0x7ec, "Die eingegebenen Registrierdaten sind falsch.") + " " + MultiLang.ml.ml_string(0x699, "Bitte \x00fcberpr\x00fcfen Sie Ihre Eingabe.");
                this.n.Interval = ((int) (this.a.NextDouble() * 1000.0)) + 1; // Random Zeit zwischen 1 und 2 Sek.
                this.n.Start(); // Timer Starten -> Tick macht den Butten wieder klickbar.
                this.d();
            }
            else
            {
                string str = "REQUESTCHECKREGISTRATION";
                if (this.d)
                {
                    str = "REQUESTCHECKREGISTRATIONFROMSPLASHSCREEN";
                }
                al0.h.a.c(str + arq.b + this.tbxRegName.Text + arq.b + this.b() + arq.b + GetStringSoftwareKey());
            }
        }

Dieses hier ist wieder von dem 1-Byte-Patch bekannt. Hier wurden 3 Strings auf den Stack gepushed und dann ht::a aufgerufen. ht::a ist in diesem Fall CheckRegKey geworden, aber das ist im Grunde das Prinzip.

Ab hier hat mich meine Arbeit in zwei “Wege” aufgeteilt: Patchen oder Keygen-Schreiben. Da ich ein fauler Mensch bin hab ich mich für letzteres entschieden. Daher werd ich auch hier erst den ersten Weg verfolgen. Durch das Reflector-Plugin Reflexil (1.1) können IL-Patches vorgenommen werden, ohne das sich die Binary groß ändert. Folglich kam die “Update”-Meldung auch dann nicht mehr. Ich patchte den Jump und landete damit folglich im der Else-Verschachtelung. Hier werden meine Daten übergeben und registriert. FALSCH! Das Programm braucht ja eine Internet-Verbindung, daher findet dieser Key-Abgleich auch verschlüsselt online statt -.- Das Resultat ist ein Array aus String, wo arStrings[1] auf “OK” geprüft wird:

public static void c(string A_0, string A_1, string A_2)
    {
        // gekürzt btw
        string str = null;
        MessageBoxIcon hand = MessageBoxIcon.Hand;
        bool flag = false; // DIESE FLAG IST WICHTIG!
        string str2 = A_0;
        if (str2 != null)
        {
            if (str2 == "OK")
            {
                str = MultiLang.ml.ml_string(0x903, "Die Software wurde registriert. Vielen Dank.");
                hand = MessageBoxIcon.Asterisk;
                flag = true; // FLAG GESETZT!
            }
            else if (str2 == "INVALID")
            {
                str = MultiLang.ml.ml_string(0x7ec, "Die eingegebenen Registrierdaten sind falsch.") + " " + MultiLang.ml.ml_string(0x699, "Bitte \x00fcberpr\x00fcfen Sie Ihre Eingabe.");
                hand = MessageBoxIcon.Hand;
            }
            else if (str2 == "MAXCOUNTREACHED")
            {
                string str3 = ((MultiLang.ml.ml_string(0x904, "Die Registrierung wurde auf unterschiedlichen Computern eingegeben.") + " " + MultiLang.ml.ml_string(0x894, "Bitte wenden Sie sich an Ihren Lieferanten, um die Software wieder freizuschalten.")) + "\n\n") + "   " + ICResources.GetCommonValue("SupplierName") + "\n";
            }
            else
            {
                goto Label_019F;
            }
            goto Label_0269;
        }
    Label_019F:
        str = MultiLang.ml.ml_string(0x7ec, "Die eingegebenen Registrierdaten sind falsch.");
        hand = MessageBoxIcon.Hand;
    Label_0269:
        objArray = new object[] { str, hand, A_1, A_2, flag };
        ayn.ab method = new ayn.ab(h5.a);
        al0.aq.Invoke(method, objArray);
    }

Hier ist auch relativ offentsichtlich was hier passiert. Wenn A_0 “OK” ist, so wird die Flag gesetzt, alle sind Happy und das Programm registriert. FALSCH! Der Server sendet in A_1 und A_2 noch weitere Daten mit, welche in ein Objekt-Array gepackt wird und dynamisch invoked wird. Diese Server Response läuft sicher wieder durch diverse Check-Algorithmen. Spätestens hier hab ich keine Lust mehr weiterzutracen, die ganzen Klassen machen mich echt fertig.

Aber da ist ja noch der Ansatz mit der Serial :-) Hier mal meine unbenannten Methoden zum Checken der Serial:

private bool CheckRegKey(string sName, string sCode, string sKey)
    {
        byte[] buffer;
        string StringBuyerName;
        string sRandomKey;
        bool flag;
        if (((sName.Length != 5) || (!(sName.ToUpper() == sName) || !(sCode == ""))) || !(sKey == ""))
        {
            buffer = new byte[1];
            u8.m whiteLabel = (u8.m) ICResources.GetWhiteLabel();
            StringBuyerName = null;
            switch (whiteLabel)
            {
                case u8.m.a:
                    StringBuyerName = "blueimage";
                    goto Label_00AD;
 
                case u8.m.b:
                    StringBuyerName = "TerminalWorld";
                    goto Label_00AD;
 
                case u8.m.c:
                    StringBuyerName = "ActiveBizCenter";
                    goto Label_00AD;
 
                case u8.m.d:
                    StringBuyerName = "CSDInternetPoint";
                    goto Label_00AD;
 
                case u8.m.e:
                    StringBuyerName = "TATAMAX";
                    goto Label_00AD;
 
                case u8.m.f:
                    StringBuyerName = "iCafeDoSul";
                    goto Label_00AD;
 
                default:
                    return false;
            }
            goto Label_0175;
        }
        return true;
    Label_00AD:
        sRandomKey = "gjfdklgjgdfkltgjlgjdfklvbjltjeriogjdfklgjsdklfjsdklfwjitsdjklfsdjrklw343fjsdklt324sdtgjiop";
        string sRandKey2 = "kresjkvcx" + sName.Trim().ToUpper() + "lewkdmso";
        if (al0.a(al0.b.b)) // AND Operator //  return ((a & A_0) == A_0); a & 1 = 1 (i guess)
        {
            sRandKey2 = sRandKey2 + "jdksanm";
        }
    Label_0175:
        flag = false;
        for (int i = 1; i < 20; i++)
        {
            buffer[0] = (byte) i;
            string sHex = this.GetSHA512Hash("2q3D2e52qD25" + StringBuyerName + Convert.ToBase64String(buffer), true);
            if (sCode.Replace("-", null).ToUpper().Trim() == this.GetMD5Hash(sRandomKey + sRandKey2 + FormReg.a.ToString(e) + sHex, true).ToUpper())
            {
                return true;
            }
        }
        return false;
    }

Nunja, der Ablauf ist (soweit ich sagen kann) so aufgebaut:

  • Aus den Ressourcen wird ein Label ausgelesen. Wenn dieser Name einer der “Käufer” entspricht, so wird in Label_00AD gesprungen.
  • Hier wird ein Key initialisiert und ein zweiter Key mit dem Käufernamen gebildet
  • Nun kommt die If-Abfrage die ich nicht verstehe. A10 hat ein Enum, beginnend ab 0. A10.a(Enum B) prüft, ob A10Enum[0] & EnumB[1] == 1 ergibt. Und da Bit-Operationen nie meine Stärke waren, denk ich einfach das die if “1=1″ ergibt, also immer gesprungen wird.
  • Nun wird es Hart: Es wird ein Byte[0] mit dem aktuellen Interator gefüllt, ein SHA512 aus einem String, dem Käufernamen sowie dem Base64-String des Iterators gebildet und als Hex zurückgeliefert. Nun wird aus dem Code (nicht Key) ein String gebildet (4 * 8 -> 32 Zeichen = MD5) und mit dem MD5 aus sRandKey1, sRandKey2, dem Salt von vorhin sowie dem SHA512 Hash bebildet und dieser mit dem Code vergleichen.

Wer es schafft hierraus einen Keygen zu basteln bekommt einen Keks, und zwar einen richtig großen!

So, durch diesen Blogpost hab ich ein bisschen Klarheit in mein Gehirn schaffen können und Euch hoffentlich einen kleinen Einblick. Wer Fragen / Hilfestellungen für mich hat kann gerne einen Kommentar hinterlassen =)

Greez

P.s. Ich konnte es nicht lassen, den einen Jump auch noch zu patchen. Leider hat das keine Auswirkungen -.-

6 people like this post.

Andere Artikel zu dieser Serie

  1. Reversing ist kein Ponyhof – InterCafe RE IV
  2. Reversing ist kein Ponyhof – InterCafe RE III
  3. Reversing ist kein Ponyhof - InterCafe RE II
  4. Reversing ist kein Ponyhof - InterCafe RE (This post)
    • Terri
    • 8. Jul. 2011 12:42am

    I tried to use your RSS-FEED but the feed url showing me some crazy xml errors.

    • Jannik
    • 19. Okt. 2011 9:25pm

    Hallo,

    sehr gute Arbeit. Auch ich habe dieses Programm schon seit einem halben Jahr an der Gurgel und ich habe es nicht geschafft, es fertig zu patchen geschweige davon einen KG & Patch zu schreiben.

    Sehr gute Analysen, und ja, eine sehr schwierige Aufgabe, auch wenn der Developer “nur” eine Basis-Version des DotFuscators benutzt.

    Ich sehe, du bist ziemlich gut in deiner Sache. Wenn du noch keins hast und vielleicht mal Lust hast, ein paar Releases in einem Team abzugeben, kontaktier mich einfach mal.

    Jannik-BBB

      • Easysurfer
      • 22. Okt. 2011 10:53am

      Danke erstmal für den Kommentar!
      Einen Keygen für den ersten Offline-Check habe ich inzwischen geschrieben, allerdings wird dieser Key beim Online-Check nicht funktionieren.

      Wie ich in Teil 3 erläutert habe müsste man den AuthServer emulieren, und dazu habe ich bisher keine Motivation gefunden…
      Aber wenn Du ebenfalls darin interessiert bist können wir uns ja mal zusammen daran versuchen ;-)

      Team habe ich keins, aber leider habe ich auch keine Zeit einem Team beizutreten… :/ Ich werde euch aber später mal kontaktieren.

      Easy

        • Richi
        • 22. Mai. 2013 6:31pm

        Du hast nen Keygen??

        Nur her damit, gebe dir auch 30€ :-)

          • Easysurfer
          • 24. Mai. 2013 1:29pm

          Nein, hab ich leider nicht und auch nicht mehr weiter angeschaut … :)

            • Richi
            • 24. Mai. 2013 7:42pm

            Bin davon ausgegangen, dein Satz war: “Einen Keygen für den ersten Offline-Check habe ich inzwischen geschrieben” ;-)

    • Jannik
    • 25. Okt. 2011 10:15pm

    Gute Arbeit wirklich, habe letztens erst nur den ersten Eintrag gesehen und dann erst gesehen, als mein Kommentar abgeschickt war ;D

    Ja, ich habe einen Typen, dem ich schon seit Monaten ein gutes InternetCafe sozusagen schulde und mit dieser InterCafe Geschichte fing es an, erst 2011, fail, 2008, same protection -> fail und mittlerweile sind wir bei 2004 angekommen, welche in VB5/6 noch geschrieben wurde, leider ist auch diese nicht leicht zu patchen, keygennen scheint einfach zu sein aber ich bin absolut kein keygenner, sondern ein Gelegenheitsreverser :D Nun bin ich zufällig auf deinen Blog gestoßen und hab begeistert gelesen, dass du daran arbeitest. Du bist meine letzte Hoffnung :D

    Ich habe mich auch an einem Keygen versucht, leider habe ich irgendwas falsch übertragen. Die For Schleife habe ich einigermaßen übertragen können, aber irgendwie habe ich keine Ahnung gehabt, wo die die Hardware ID implemntiert wurde ;) Najut, du hast es ja nun geschafft, viel Glück und Motivation weiterhin :D Ich selber habe sowieso nicht allzuviel Ahnung, von allem nur ein bisschen :D

    Ich habe grad gesehen, du bist ja auch bei Free-Hack angemeldet.. :)
    Alles klar, ich freue mich auf jede Rückmeldung :)

    J

  1. Noch keine TrackBacks.


− 6 = drei