Keygenme-Analyse von Ra $ cal basierend auf der virtuellen Maschine

    0. Info


    KeygenMe Seite auf crackmes.de
    Crackme mit einfachem vm. Der Schlüsselüberprüfungsalgorithmus ist einfach, also Hauptziel - vm.
    Die Schwierigkeit von pcode wächst von Anfang bis Ende. Der erste Teil scheint ein Emulator zu sein, aber dann sieht es aus wie eine Maschine mit einer anderen Logik, Registern, Befehlen =)
    Viel Glück und viel Spaß.
    Schwierigkeitsgrad: 4 - erfordert spezielle Kenntnisse
    Plattform: Windows
    Sprache: C / C ++

    Beginnen wir unsere Recherche mit dem Code, der ausgeführt wird, wenn wir auf die Schaltfläche „Prüfen“ im Formular klicken. Da CrackMe auf einem regulären Dialogfeld basiert, betrachten wir zunächst die registrierte Fensterfunktion.


    Abb. 1: Der Teil des Dialogfensters, der für die Verarbeitung von WM_COMMAND verantwortlich ist.

    Durch Drücken der Taste wird eine Nachricht an die Anwendung gesendetWM_COMMAND mit der ID des Elements, dessen Ereignis aufgetreten ist. In diesem Fall werden die Eingabewerte mit der GetDlgItemTextA- API abgerufen. Anschließend wird die Funktion aufgerufen , die die eingegebenen Werte überprüft.


    Abbildung 2. Von einer Fensterfunktion aufgerufene Prüffunktion.

    Wenn wir uns die große Anzahl von NOP-Befehlen (403F3E-4035A9 = 995h) ansehen, können wir davon ausgehen, dass sich der Quellcode des Algorithmus, der virtuell war, ursprünglich hier befand und später überschrieben wurde.
    Die Funktion sub_4017C0 ist ein Wrapper für einen VM- Aufruf, dessen Hauptschleife sich in der Funktion sub_401890 befindet, deren Pseudocode unten dargestellt ist.

    sub_401890
    Abbildung 3. Die Hauptschleife einer virtuellen Maschine in der Funktion sub_401890.

    Alle diese Funktionen in switch / case sind Implementierungen von Befehlen.

    Der virtualisierte Code selbst wird ab Adresse 00407000 im Abschnitt .rvmpc gespeichert.

    1. Befehlsformat


    Das Ermitteln der Struktur einer virtuellen Maschine, der Architektur sowie des Formats der verwendeten Befehle ist die erste und wichtigste Aufgabe bei der Code-Devirtualisierung.
    Diese virtuelle Maschine ist ein Register, ein Stack dient zur temporären Speicherung von Variablen.
    Das Befehlsformat für diese virtuelle Maschine ist nicht fest und ziemlich redundant. Befehle aller Art haben einen gemeinsamen Header in der folgenden Form:
    OffsetTypGrößeBeschreibung
    +0Wort2Operationscode
    +2Dword4Team ID
    +6Byte1Argumentgröße
    +7Dword4Nächste Befehls-ID
    + BhDword4ist unbekannt

    Das Feld "Befehls-ID" enthält einen eindeutigen Wert für den gesamten Bytecode. Um zum nächsten Befehl zu gelangen, entnimmt die virtuelle Maschine dementsprechend den Wert aus dem Feld „ID des nächsten Befehls“ und sucht im gesamten Codearray nach dem Befehl, dessen ID mit der angegebenen übereinstimmt. Unter Verwendung von switch / case erfolgt dann die Interpretation jedes Befehls anhand des Werts des Felds „Operationscode“.

    Der Befehlssatz der betrachteten virtuellen Maschine ermöglicht den Betrieb mit internen Registern, systemeigenen Prozessorregistern und systemeigenem Code.

    OpcodeAktion
    0x00anrufen
    0x01anrufen
    0x02Bedingter Sprung
    0x03Dword drücken
    0x04Sub / Add ESP
    0x05Dword drücken
    0x06mov [esp], dword
    0x07jmp ID
    0x08mov [ebp +?], dword
    0x09gebürtig
    0x0Agebürtig
    0x0B-
    0x0CÄnderung des internen Flags dw4052AC
    0x0D-
    0x0E-
    0x0F-
    0x10mov REGn, dword-reg / mov REGn, dword [reg + X]
    0x11mov dword [addr], (reg / dword) / mov dword [REGn], (reg / dword)
    0x12Diverse Weiterleitungsbefehle mov
    0x13Diverse Weiterleitungsbefehle mov
    0x14MOV _32 [A], auspacken (REGn)
    0x15MOV REGn, packen (_32 [A])
    0x16XOR _32 [A] [i], _32 [B] [j]
    0x17mov [REGn], reg / mov reg, [REGn]
    0x18XOR _32 [A] [c: d], _32 [B] [e: f]
    0x19MOV _32 [A], 0
    Die Befehle 0x14 - 0x19 arbeiten mit einem zusätzlichen Speicherbereich, in dem bitweise Operationen mit 32-Bit-Zahlen ausgeführt werden können.

    2. Zerlegen des VM-Codes


    Nachdem wir das Format der Befehle beschrieben haben und wissen, welche Funktionen sie ausführen, lohnt es sich, einen Disassembler für diese virtuelle Maschine zu schreiben. Befehle können sich vollständig vom Befehlssystem zuvor bekannter Architekturen unterscheiden, sodass Sie sie in verständliche Mnemoniken übersetzen können.

    Disassembler-Quellen mit der BeaEngine-Bibliothek (+ exe) Die disassemblierte Befehlsliste umfasste
    für mich 961 Zeilen.

    3. Devirtualisierung


    Dieser Schritt ist nicht erforderlich, wenn der virtuelle Code klein ist und Sie den Algorithmus verstehen können, ohne ihn in systemeigenen Code umzucodieren. Wie oben erwähnt, besteht der Prozess der Devirtualisierung darin, den Code für die virtuelle Maschine in den Code für den nativen Prozessor umzucodieren. Zu diesem Zweck muss der Disassembler anstelle der Mnemonics für die virtuelle Maschine mindestens ein Mnemonic für den nativen Prozessor ausgeben. Dann sollten Sie den resultierenden Code kompilieren. Diese Operation bietet Unterstützung für vorhandene Tools - Debugger, Dekompilierer usw.

    Im Prinzip habe ich den Algorithmus größtenteils durch eine Disassembler-Auflistung zerlegt, aber ich habe mich gefragt, wie sich HexRays verhalten würde, wenn wir die Auflistung im nativen x86-Code erfassen würden. Zu diesem Zweck habe ich alle internen Register im Befehl mnemonics entfernt, einen Teil des Codes, der mit Bitwerten arbeitet, neu geschrieben und alles in exe kompiliert. Ich habe kein perfektes Verhalten erreicht, daher gibt es einige logische Fehler im unten dekompilierten Code.

    Die resultierende Quelle auf x86 asm
    Source wird mit FASM gesammelt .

    Also zum Beispiel ein paar Teams
    MOV REGn, EBP-x
    MOV reg, [REGn]
    in eins verwandeln
    MOV reg, [EBP-x]
    

    und Rückwärtsbewegungsbefehle
    MOV REGn, EBP-x
    MOV [REGn], reg
    biegen Sie in
    MOV [EBP-x], reg

    Das Design der Ansicht ist vor allem reduziert:
    MOV REG9, DWORD PTR [ebp-E0]
    MOV eax, DWORD PTR [REG9]
    MOV [REG2], eax
    MOV _32[1], 0
    MOV _32[0], unpack(REG2)
    XOR _32[0][0:7], _32[1][18:1F]
    XOR _32[0][8:F], _32[1][10:17]
    XOR _32[0][10:17], _32[1][8:F]
    XOR _32[0][18:1F], _32[1][0:7]
    MOV REG2, pack(_32[1])
    MOV eax, [REG2]
    MOV REG9, DWORD PTR [ebp-E0]
    MOV DWORD PTR [REG9], eax
    Auf dem x86-Assembler sieht das folgendermaßen aus:
    MOV eax, DWORD PTR [ebp-E0]
    BSWAP eax
    MOV DWORD PTR [ebp-E0], eax

    Dann setze ich das Hex-Rays-Plugin auf die kompilierte EXE und erhalte die Quelle wie im Bild unten:

    dekompilierter Code
    Abb. 4. Der dekompilierte Code wurde für die x86-Architektur neu erstellt.

    4. Umkehrung des Algorithmus


    Dies ist der langweiligste Teil der Studie, da der Algorithmus nichts Interessantes ist und auf XOR und zyklischen Verschiebungen basiert.

    Der Schlüssel lautet wie folgt:
    SSSS-11111111HHHHHHH2222222222 - ??? Wobei
    :
    SSSS ein numerischer Wert in einem Dezimalzahlensystem ist;
    11111111, 22222222, HHHHHHH - Werte in einem Hexadezimalzahlensystem;
    ??? - beliebiger Wert, beliebige Länge

    Algorithmus:
    1. Wir betrachten die Summe der Zeichen des eingegebenen Namens
      for ( i = 0; i < nLenName; i++ )
          dwNameSum += name[i]
      
    2. Vergleichen Sie den empfangenen Wert mit dem ersten Block des Schlüssels.
    3. Lesen Sie den Hash im Namen des Computers
      for ( j = 0; j < nCompNameLen; j++ ) {
        dwHashCompName ^= j ^ szCompName[j];
        dwHashCompName = __ROL__(dwHashCompName, 3);
      }
      
    4. Aus den Abschnitten 11111111, 22222222 und HHHHHHHH ergibt sich die Binärdarstellung (hexdec) - ein Analogon von dwHash1, dwHash2, dwHash3.
    5. Für jeden der Werte führen wir die folgende Operation aus:
      dwHashN = dwHashN xor dwHashCompName xor htonl(dwHashCompName)
    6. Der Schlüssel-Hash wird wie folgt berechnet:
      dwHashSerial = dwHash3 xor dwHash1 xor htonl(dwHash1) xor dwHash2 xor htonl(dwHash2) xor dwHashCompName xor htonl(dwHashCompName)
    7. Der Hash im Namen wird durch den Algorithmus berechnet:
      for ( idx = 0; idx < nLenName; idx++ ) {
          if ( idx % 2 )
              dwHashName ^= 0x4F620AEC ^ (idx + dwHash1) ^ szName [idx];
          else
              dwHashName ^= 0x4F620AEC ^ (dwHash2 - idx) ^ szName[idx];
          dwHashName = __ROL__(dwHashName, idx);
      }
      

    Alles , was wir tun müssen - ist es, einen Zufallswert für die 2 Einheiten 1 und 2. Dann betrachten dwHashName und dwHashSerial unter dwHash3 0. Und nur nach der Formel des fehlenden Wert zu zählen , hat zu generieren dwHash3 = dwHashName ^ dwHashSerial

    Und natürlich das Ergebnis der Studie:



    Download - EXE und Quellcode keygen

    PS Yandex kämpft mit direkten Links. Wenn 404 angezeigt wird, öffnen Sie die Links in einem neuen Tab.

    Jetzt auch beliebt: