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 -.-
Andere Artikel zu dieser Serie
- Reversing ist kein Ponyhof – InterCafe RE IV (November 3, 2024)
- Reversing ist kein Ponyhof – InterCafe RE III (Juni 21, 2011)
- Reversing ist kein Ponyhof - InterCafe RE II (Juni 20, 2011)
- Reversing ist kein Ponyhof - InterCafe RE (This post) (Juni 19, 2011)
I tried to use your RSS-FEED but the feed url showing me some crazy xml errors.
Try to use this link for rss support: http://easysurfer.me/wordpress/?feed=rss or http://easysurfer.me/wordpress/?feed=rss2
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
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
Du hast nen Keygen??
Nur her damit, gebe dir auch 30€
Nein, hab ich leider nicht und auch nicht mehr weiter angeschaut …
Bin davon ausgegangen, dein Satz war: “Einen Keygen für den ersten Offline-Check habe ich inzwischen geschrieben”
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
Nun bin ich zufällig auf deinen Blog gestoßen und hab begeistert gelesen, dass du daran arbeitest. Du bist meine letzte Hoffnung
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
Ich selber habe sowieso nicht allzuviel Ahnung, von allem nur ein bisschen
Ich habe grad gesehen, du bist ja auch bei Free-Hack angemeldet..
Alles klar, ich freue mich auf jede Rückmeldung
J