tr4ceflow Crackme/Keygenme 1
In diesem Artikel geht es um das Crackme/Keygenme 01 von tr4ceflow. Der Crack selbst ist mit einem kleinen Patch getan, aber der Keygen ist anspruchsvoll. Ich selbst habe nicht viel Erfahrung mit Keygenning, zumindest nicht was native Algorithmen angeht. Daher werde ich den Algorithmus, den Code und den Prozess zum Keygen ziemlich ausführlich beschrieben.
Los gehts mit der ersten aufpoppenden Message-Box die es wegzupatchen gilt. Target in Olly laden, starten und nach dem Aufpoppen der Messagebox unterbrechen. Nun im Stacktrace soweit zurückgehen, dass man aus den “System”-DLLs wie Apphelp und USER32 in der eigentlichen Executable landet. In diesem Falle ist das der Stack:
Und die Instructions zu denen dieser RETURN führt:
Wir patchen nur den Call zur Messagebox und die folgende Instruction, nicht das Laden der Argumente auf den Stack. Der hier verwendete Compiler scheint die Argumente nicht direkt per PUSH auf den Stack zu laden, sondern vielmehr den Stackpointer manuell setzen und die Argumente direkt schreiben. Daher muss das SUB ESP,10 ebenfalls entfernt werden. MessageBoxA ruft diese Argumente ab und SUB ESP,10 fixxt den Stack wieder. Da keine Argumente abgerufen werden, muss der Stack auch nicht gefixxt werden.
Fangen wir an mit den Keygen. Die ersten Schritte des Keygens habe ich jeweils in ASM und C++ Code abgebildet. Es soll euch eine Idee geben, wie kompakt ASM Code eigentlich sein kann und vorallem wie der Computer intern Funktionen wie String.ToUpper() realisiert.
Am Anfang der Key-Checkroutine wird natürlich erstmal der Username abgerufen. Dies geschieht über GetDlgItemTextA mit einem Verweis auf die verwendete Textbox. Als Resultat gibt die Funktion die Anzahl der gelesen Zeichen zurück:
In nächsten Schritt wird erstmal geprüft, ob der Username kleiner oder gleich 5 Zeichen lang ist. Wenn dem so ist, so wird ein statisches Array von 20 Integer-Einträgen in ein genau so großes lokales Array kopiert. Dieses Array wird später zum Checken des Keys gebraucht und hier initialisiert. Des weiteren wird der letze Buchstabe des Usernames in das Register ESI geladen.
Nun wird gecheckt, ob der aktuelle Buchstabe (also am Anfang der erste) Minus 97 kleiner wie 25 ist. Wieso 97 und 25? Ein Blick in die ASCII Tabelle verrät, das ab 97 die kleinen Buchstaben anfangen und davon gibt es 25. Wenn dem so ist (also der aktuelle Buchstabe ein Kleinbuchstabe ist), so werden 0×20 abgezogen. Auch hier verrät wieder ein Blick in die Tabelle: genau 0×20 Zeichen drunter ist der selbe Buchstabe in groß. Nun werden in jedem Fall noch als Check 65 Zeichen abgezogen und auch überprüft, ob dann immernoch ein Großbuchstabe vorliegt. Diese Schleife wird durchlaufen, bis der aktuelle Buchstabe der letze Buchstabe ist. Und ja, genau so Funktioniert auch die “ToUpper” Funktion in jedem Framework
Nach dieser noch unspektakulären Funktion nähren wir uns dem Check Algorithmus. Ab jetzt wird nur noch direkt C++ Code verwendet, da ab hier der ASM Code mehr verwirren als erklären wird. Es wird über diesen (eigentlich selbsterklärenden Code) der kleinste Buchstabe in dem Username rausgesucht und abgespeichert.
Mit diesem Buchstaben werden nun wilde Sachen gemacht. Zunächst wird über den Rechenschritt 2 - KleinsterBuchstabe eine negative Zahl erzeugt. In einer Schleife wird nun von jedem Buchstaben diese neue Zahl abgezogen und im Anschluss noch der Buchstabenindex draufaddiert. Ein Rechenbeispiel macht das ganze vielleicht klarer:
“TEST” ist unser Username-String, dabei ist E natürlich das kleinste Buchstabe. (0×45 = 69). Nun wird diese Zahl berechnet: 2 - 69 = -67 . In einer Schleife werden nun alle Buchstaben durchlaufen, so dass sich ergibt:
T = 84 ; 84 - 67 + 0 = 17 E = 69 ; 69 - 67 + 1 = 3 S = 83 ; 83 - 67 + 2 = 18 T = 84 ; 84 - 67 + 3 = 17
Ich mache das so ausführlich, da wir nachher beim Serial Erstellen diese Schritte zurückrechnen müssen. Die Schleife sieht in Programmcode so aus:
Damit ist die Bearbeitung des Usernames abgeschlossen und die Serial wird ausgelesen. Diese wird mit einer statischen Serial vergleichen (die natürlich nicht richtig ist :D) und im Falle der Übereinstimmung eine Meldung ausgegeben, dass man es eben so nicht machen sollte. Auch hier sieht man schön, über welche Methoden C++ Funktion wie strcmp intern arbeiten:
Nun wird die Länge der Serial geprüft (sie muss zwischen 8 und 10 Zeichen haben) und und gecheckt, ob der erste Buchstabe auch eine Zahl ist (Ein Blick in die Tabelle verrät wieso 48). Nun wird in einer Schleife gecheckt, ob auch wirklich alle Zeichen des Serial-Strings Zahlen sind. Wenn wir alle diese Checks überstanden haben, so wird die Zahl über sscanf von dem String zu einem Integer umgewandelt.
Und damit sind wir an dem Punkt angelangt, wo Username und Serial abgegleichen werden und aus ihnen das magische Resultat berechnet wird. Der erste Username-Buchstabe muss mit dem Modulo aus der Serial und dem ersten Eintrag des statischen Arrays (+1) von oben übereinstimmen. Mit “Username-Buchstabe” meine ich in Zukunft die veränderte Version, also die Buchstaben die von dem kleisten abgezogen wurden etc. In diesem Falle ist das:
userNameChar[0] == inputNumberSerial % (36 + 1)
Wenn diese Bedingung erfüllt ist, so geht der Serial-Check weiter. Es werden alle Buchstaben durchlaufen und geprüft:
userNameChar[i] == inputNumberSerial % (initValue1_0x24[i] + (i % 10) + 1)
Unser Rechenbeispiel macht das wie immer klarer:
Init-Value-Array: {36, 39, 40, 43, 48, 53, 54} Serial Nummer Random: 38457465 Username-Array: [0] 17 == 38457465 % (36 + (0) + 1) = 38457465 % 37 = 35 [1] 3 == 38457465 % (39 + (1) + 1) = 38457465 % 41 = 39 [2] 18 == 38457465 % (40 + (2) + 1) = 38457465 % 43 = 28 [3] 17 == 38457465 % (43 + (3) + 1) = 38457465 % 47 = 44
Diese Zahl war also denkbar schlecht gewählt Wenn alle diese Zahlen erfolgreich mit diesen Prüfsummen durchlaufen wurden, so ist die Serial und der Username gültig. Wenn auch nur eine nicht stimmt, so fliegen wir raus.
Nun, da wir wissen wir der Serialcheck aufgebaut ist, geht es an den Keygen. Ein Username zu generieren, auf welchen die Serial passt ist ohne Brute-Force nicht möglich (zumindest übersteigt es meine mathematischen Fähigkeiten, wer da eine Idee hat sollte sich bitte melden!). Daher habe ich mich an der Rückrichtung probiert: Ich generiere eine Serial und draus den Usernamen. Das Problem ist, dass wir auch hier noch eine Unbekannte zu viel drin haben: Der “kleinste Buchstabe” der am Anfang ermittelt wird und von allen anderen abgezogen wird. Auch hier saß ich lange dran, aber Gleichung “current = 2 - currentLowest” lässt sich nicht zurückrechnen. (Auch hier: Mathematiker vor!). Daher hab ich festgelegt, dass ich immer ein A ein meinem Username drinhaben möchte. Dies ist automatisch die kleinste Zahl und somit die gesuchte Unbekannte. Damit lässt sich der verkleinerte Username wieder zurückrechnen. Nehmen wir wieder unser Beispiel her.
Gegeben: Kleinster Buchstabe ist E = 69 Gegeben: Serial, die mit den jeweiligen Modulos nie Werte > 25 erreicht und ein E enthält [0] 17 -> calculatedModulo[i] - i + lowestChar = 17 - 0 + 67 = 84 = T [1] 3 -> calculatedModulo[i] - i + lowestChar = 3 - 1 + 67 = 69 = E [2] 18 -> calculatedModulo[i] - i + lowestChar = 16 - 2 + 67 = 83 = S [3] 17 -> calculatedModulo[i] - i + lowestChar = 20 - 3 + 67 = 84 = T
Uns bliebt leider nichts anderes übrig, als Random Serials zu bestimmen und diese auf die Modulo-Eigenschaften zu prüfen. Im Anschluss prüfen wir, ob diese Serial ein A enthält, sonst ist sie ungültig. Diesen beiden Anforderungen entspricht im Mittel jede 29te Serial, ist also vertretbar.
Der Code zum Generieren sieht also wie Folgt aus:
while (!validKeyFound) { randomNumber = random.Next(10000000, 99999999); validKeyFound = true; for (int i = 0; i < usernameCharArray.Length; i++) { var res = (initialData[i] + (i % 10 + 1)); var calculatedModulo = randomNumber % res; usernameCharArray[i] = (char)(calculatedModulo - i + 63); if (calculatedModulo > 25 || calculatedModulo < 5) { validKeyFound = false; break; } } if (validKeyFound) { validKeyFound = false; for (int i = 0; i < usernameCharArray.Length; i++) { if (usernameCharArray[i] == 'A') validKeyFound = true; } } } |
Fertig ist unser Keygen!
Download des Crackmes und des Keygens: CrackmeBundle (28)
cooler Beitrag ^^ und Applaus für deine Lösung (crackme sollte recht schwierig sein). Die Richtung mit Serial -> Benutzername ist eine sehr gute Idee. An die Richtung habe ich gar nicht gedacht. Sind die Screenshots vom IDA Decompiler? Wenn ja bringt der etwas? Ich arbeite nur mit der free-Version bzw. Demo-Version von IDA.
Tipp für “Benutzername->Serial”:
“Drei Chinesen mit dem Kontrabass…”
Ich arbeite auch viel mit dem sog. HexRay Plugin von IDA Pro 5.5 . Dies wandelt direkt ASM Code in eine C Variante um. Zwar ist der Code nicht schön (wirklich niemand würde so programmieren :D) aber gerade die schwirigen Operationen wie Bitshifting und Modulo kann man durch C Code besser untersuchen. Als “Freeware” Alternative gibt es Hopper Disassembler, welcher ähnlichen C Code erzeugt. Kannst Dir ja mal anschauen.
Greez
” Der “kleinste Buchstabe” der am Anfang ermittelt wird und von allen anderen abgezogen wird. Auch hier saß ich lange dran, aber Gleichung “current = 2 – currentLowest” lässt sich nicht zurückrechnen. ”
Kleinster Bubstabe == (not(Differenz)+1)+2
z.B:
Differenz == 0xBD
daraus folgt >> NOT(0xBD) == 0×42 => 0×42 + 0×01 == 0×43 => 0×43 + 0×2 == 0×45
ascii (0×45) == E
also ist der kleinste buchstabe im Namen das “E” !
viel Spass
doch der kleinste benutzte Buchstabe lässt sich mathematisch ermitteln.
https://github.com/streppelchen/tr4ceflow-Crackme-Keygenme-1
hab mich dann man an nem keygen versucht
für meinungen bin ich offen
Dein
initvalarr = [36,39,40,43,48,53,54]
modifizierst du einmal und erhälst das “moduloarray”. Das ist stets konstant.
Was du ab “#hier beginnen wir zu loesen” machst ist mir etwas rätselhaft.