[Solved][KeygenMe] Auf ein Neues

Hi,

um mal wieder ein wenig Wind in diesen Bereich zubringen, habe ich eine ältere Idee ausgegraben und sie in ein KeygenMe uebertragen.

Es ist in Delphi geschrieben und nicht gepackt.

Schwierigkeit kann ich immer schwer abschaetzen, würde aber auf mittel-schwer tippen.

Dann wünsche ich viel Spaß und lasst die Spiele beginnen.

Grüße,
redox
 
Zuletzt bearbeitet:
Hallo zusammen,

da hier ja kein so dramatisches Gedränge herrscht, melde ich mich mal wieder.

Erstmal einen herzlichen Dank an redox: Das crackme finde ich ganz ausgezeichnet. Und der Schwierigkeitsgrad: wenn man den äußerst ungewöhnlichen Programmierstil, der ohne jede direkte Loop (außer einer ganz dicken) und mit ganz wenig if's auskommt, erstmal begriffen hat, dann geht es ganz gut. Was mich interessieren würde: Gab es für diese super Idee irgendein Vorbild? Aber vielleicht ist das ja in manchen "Gegenden" üblich, nur an mir bisher vorbeigegangen.

Wie auch immer: ich poste hier mal einige "goodboy" serials. Das Tut muss noch etwas warten, auch aus Zeitgründen. Und wenn die Lösung gleich publik gemacht wird, ist ja auch irgendwie der Spass für andere weg.

-josh

Name: ABCDEFGHIJKLN
Serial: 1sssu-5AABBCCDDEEFFGGHHIIJJKKLLNN

Name: ZZZZZZZ
Serial: 1NNNQ-5ZZZZZZZZZZZZZZ

Name: zzzzzzzz
Serial: 1zzzz-5zzzzzzzzzzzzzzzz
 
Hi josh,

Erstmal einen herzlichen Dank an redox: Das crackme finde ich ganz ausgezeichnet. Und der Schwierigkeitsgrad: wenn man den äußerst ungewöhnlichen Programmierstil, der ohne jede direkte Loop (außer einer ganz dicken) und mit ganz wenig if's auskommt, erstmal begriffen hat, dann geht es ganz gut. Was mich interessieren würde: Gab es für diese super Idee irgendein Vorbild? Aber vielleicht ist das ja in manchen "Gegenden" üblich, nur an mir bisher vorbeigegangen.

Freut mich, wenn es dir Spaß gemacht hat!

Hier mal ein paar Hintergrund Infos:
Das ganze ist eine Virtuelle Maschine. Sie hat 8 normale Register (r0-r7), einen Instruction Counter und einen Stack Pointer. Sowie ein paar Flags. Die Idee ist also keineswegs neu.

Der spannende Teil beginnt an Adresse 0x00409183, hier wird die VM erstellt.

Na wer schafft es nun einen Disassembler für den Bytecode zu schreiben?

Grüß,
redox
 
Hi redox,

danke für die Info! Bei "virtueller Maschine" hat es natürlich klick gemacht! Daher der "ungewöhnliche Programmierstil": der Sequencer der virtuellen Maschine!
Anbein einige Infos dazu, was ich herausgefunden habe:

Es sind 256 verschiedene Bytecodes möglich, die aber nicht alle genutzt wurden. Der "Bytecode" ist ein Index in eine Sprungtabelle. Ich will jetzt nicht alle Bytecodes auflisten, nur ein paar:

Die Nomenklatur ist: Bytecode, Wert, ... Wert. Es sind Einbyte bis 6-Byte Befehle vorhanden. Einige Beispiele (Zahlen sind alle hex):
Code:
[FONT=Courier New]00, i,j,k,l     CALL (i,j,k,l)
01, i[s,t]      XOR Rs, Rt        ; XOR Register, s, t sind Low und High Nibble von i
38, i[s,t]      ADD Rt, string[Rs];Addiere den Wert von String an der Stelle Rs zu Rt
55, i[s,t]      XOR Rt, string[Rs]
88, i, j        SETF Ri, j       ;Set Zero Flag F0 (Bit0), >= Flag F1 (Bit1), < Flag F2 (Bit2) aus Vergleich Ri, j
90, i,j,k,l,m   XOR Ri, DWORD(j,k,l,m)
CC, i, j        SUB Ri, j
D4, i           JSetF2, i        ;JMP i if F2 is set
E8, i, j        ADD Ri, j
F1, i           POP Ri           ;TOS--> Ri, SP->SP+1 (vier Byte)
FF              EXIT

etc...

[/FONT]
Das Programm beginnt mit der Längenüberprüfung des Serial (Länge des Name und Serial Strings liegen bereits auf dem Stack):
Code:
[FONT=Courier New]F1,1           POP R1
F1, 5          POP R5
01, 33         XOR R3, R3
01, 44         XOR R4, R4
88, 5, B       SETF     R5, B    ; R5 hat Länge des Serial, Min Länge == 0x0b
D4, 59         JSF2, $+059        ;Springe 59 weiter, falls F2 gesetzt ist, also wenn die Länge < 0x0b ist
[/FONT] 
...
...

[FONT=Courier New]59 Plätze weiter:
00, d0,6f,40,00    CALL 406fd0        ;Show "badboy"
FF        EXIT[/FONT]
Das "Programm", d.h. die Tabelle mit den in der Reihenfolge abzuarbeitenden Bytecodes, liegt übrigens in Adresse 40a7ec.

Nur eins ist irgendwie unklar: Einige Befehle (z.B. 0x38 und 0x55 als 2-Byte Befehle) greifen auf den (zusammengehängten) String NameSerial zu. Woher weiss die virtuelle Maschine bei der Ausführung dieser Befehle, auf welches Memory sie zugreifen soll, d.h. wo dieser String liegt?? Habe ich was übersehen?


Grüße
-josh
 
Super, das sieht doch sehr gut aus!

Was du heraus gefunden hast ist soweit alles korrekt.

Der Opcode "00" ist ein "Native"-Call, also ein X86-Call. Es gibt auch noch einen anderen Opcode, der einen Call innerhalb der VM ausführt. Er ist für den Serial Algorithmus nicht gerade irrelevant ;).

Nur eins ist irgendwie unklar: Einige Befehle (z.B. 0x38 und 0x55 als 2-Byte Befehle) greifen auf den (zusammengehängten) String NameSerial zu. Woher weiss die virtuelle Maschine bei der Ausführung dieser Befehle, auf welches Memory sie zugreifen soll, d.h. wo dieser String liegt?? Habe ich was übersehen?

Dem Konstruktor der VM werden unter anderen die Größe des RAMs und des Stacks übergeben (Der Stack liegt in den Letzen X Bytes des RAMs). Kurz nach dem Erstellen der VM werden Name und Serial an Adresse 0 des VM-RAMs geschrieben. Intern greifen die Opcodes 0x38 und 0x55 relativ auf den VM-RAMs zu.

In der VM sieht es also so aus, also würde an Adresse 0, der erste Buchstabe des Namens liegen.

Anschließend werden diese Adressen inkl. länge auf den Stack gepusht.

Ich finde es erstauntlich, dass du gültige Name/Serial-Kombination gefunden hast, ohne zu wissen das es sich um eine VM handelt. Respekt! Hast du den Algorithmus verstanden oder wie bist du an diese Kombinationen gekommen.

Hast du den Anti-Olly Trick bemerkt? Tipp: Es ist auch ein Opcode der VM :P.

Grüße,
redox
 
Der Algorithmus den ich benutzt habe, ist folgender (ohne die Längenprüfung):
Code:
[FONT=Courier New]bool checkSerial(byte[] name, byte[] serial)
{
     if (((serial[0] + serial[5]) ^ serial[6]) != 0x6b)
         return false;

     int sum1 = 0;
     for (int i = 1; i < 5; i++)
         sum1 += serial[i];
     sum1 *= 2;

     int sumName = 0;
     for (int i = 0; i < name.Length ; i++)
         sumName += name[i];

     if (sum1 != sumName)
         return false;

     int sumSerial2 = 0;
     for (int i = 7; i < serial.Length; i++)
         sumSerial2 += serial[i];

     if (sumSerial2 != (sumName * 2))
         return false;
     return true;
}[/FONT]
Den Algorithmus herauszufinden, war auch nicht so schwer. Es war bald klar, dass es sich um einen rein tabellengesteuerten Code handelte. Tabelle = Sequenzprogramm der virt. Maschine. Jeder Schritt der Sequenz besteht im Ausführen einer relativ kurzen Funktion (ein Bytecode). Rückgabewert jeder Bytecodefunktion ist die Differenz in der Sequenztabelle auf den nächsten Bytecode. Damit kann man sich z.B. automatisch beim Ablauf des Programms vom Disassembler eine Sequenz aller Bytecodes erstellen lassen. Da sieht man z.B. sofort, wo Schleifen ablaufen und welche Funktionen (Bytecodes) beteiligt sind. Im Debugger kann man auch recht bequem die Inhalte der "Register" verfolgen, da die Adressen ja immer konstant sind. Man sieht Schritt für Schritt, dass z.B. Counter gezählt werden, oder Summen von Speicherstellen gebildet werden. Dass die andere relevante Datenstruktur allerdings ein Stack ist, darauf bin ich nciht gekommen, obwohl es auch nicht so fernliegend ist.

Trotzdem: Auf den Trichter mit dem allgemeinen Nutzen der einzelnen Funktionen als Befehle einer virt. Maschine bin ich erst nach dem "Klick" durch deinen Hinweis gekommen.
Hast du den Anti-Olly Trick bemerkt? Tipp: Es ist auch ein Opcode der VM :P.
Nein, habe ich nicht. Ida hat zwar ab und zu etwas von einer Exception gemeldet, die habe ich weggeklickt, und es ging normal weiter. Vielleicht wegen dem IdaStealth Plugin. Was war das für ein Trick?

Grüße
-josh
 
IDA lässt sich davon nicht beeindrucken. Es ist ein spezieller Anti-Olly Trick, mittels OutputDebugString wird ein String ausgegeben, dieser wird von Olly als Format String interpretiert wird. Was passiert, wenn man OutputDebugString("%s%s%s%s%s%s%s%s%s") aufruft, kannst du dir sicher denken ;).
 
IDA lässt sich davon nicht beeindrucken. Es ist ein spezieller Anti-Olly Trick, mittels OutputDebugString wird ein String ausgegeben, dieser wird von Olly als Format String interpretiert wird. Was passiert, wenn man OutputDebugString("%s%s%s%s%s%s%s%s%s") aufruft, kannst du dir sicher denken ;).
Ok, damit wäre dann Bytecode AD ebenfalls geklärt!
 
Zurück
Oben