Contents

Cracking Marquire's CrackMe_V4_Marquire

This crackme will be a bit more difficult than the last one: Marquire’s CrackMe_V4_Marquire

PropertyValue
LanguageC/C++
PlatformWindows
Difficulty2.7
Quality4.0
Archx86

Initial run

### The goal of this crackme is to find the key! ##

Enter the key :

Asks for a key, exits if it’s false, and gives a message if it’s correct.

Initial analysis

            +-----+
            |Print|
            +--+--+
               |
          +----v------+
          | Read char <-----+
          |Check enter|     |
          +----+--+---+     |
              T|  |F        |
+---------+    |  |   +-----+------+
|Dead code<----+  +--->Encrypt char|
+----+----+           |Store char  |
     |                +------------+
     |
+----v---+  +-----+   +------------+
|Generate|  |Hash |   |input hash  |
|correct +-->input+--->    ==      |
|hash    |  |key  |   |correct hash|
+--------+  +-----+   +----+-+-----+
                          T| |F
              +-------+    | |     +-------+
              |Success<----+ +----->Failure|
              +-------+            +-------+

This is a very high level overview of what this program does, it’s simple enough to see all the logic in the main function.

Input string encryption

.text:00402160 asterisk_and_continue:
.text:00402160 lea     edx, [esi+3]    ; encryption key
.text:00402163 xor     dl, ds:400000h  ; 4Dh
.text:00402169 add     esi, 1          ; inc counter
.text:0040216C xor     eax, edx        ; char ^ 4Dh
.text:0040216E mov     [esp+esi+0A1h], al ; store encrypted char
.text:00402175 mov     dword ptr [esp], 2Ah ; '*'
.text:0040217C call    putchar

As you can see, the character inputted is xor encrypted before it’s stored.

Here’s the python code for this encryption:

def encrypt(input):
  encrypted = ""
  counter = 3
  for c in input:
    encrypted += chr(ord(c) ^ counter ^ 0x4d)
    counter += 1
  return encrypted

Input string hashing

.text:004023E3 hash_input:
.text:004023E3 movsx   esi, byte ptr [eax] ; get current char
.text:004023E6 add     eax, 1 ; inc char counter
.text:004023E9 imul    esi, edx ; mul char by edx
.text:004023EC add     edx, 0FFFFh ; inc edx by 0FFFFh
.text:004023F2 add     edi, esi ; add multiplied char to result
.text:004023F4 cmp     edx, 0CFFF3h ; only loop 13 times
.text:004023FA jnz     short hash_input
.text:004023FC cmp     ecx, edi ; compare result with correct hash
.text:004023FE mov     [esp], ebx
.text:00402401 jz      short success_caller
.text:00402403 call    failure

There are multiple things we can infer from this to aid the python code for this:

  • eax points to the current char in the encrypted string
  • the encrypted string’s characters are looped over one by one
  • edi is the hash result, as it’s used in the validation comparison and ecx isn’t mentioned in this snippet
  • the hash result is added to with the char multiplied by the multiplication counter
  • the multiplication counter is incremented by 0FFFFh each cycle
  • The hash only reads the first 13 characters, as 0CFFF3h / 0FFFFh == D, therefore the input string must have at least 13 characters.

Here’s the python code for this hash:

def hash(input):
  counter = 0
  result = 0
  for i in range(0, 13):
    result += ord(input[i]) * counter
    counter += 0xFFFF
  return result

Result comparison

.text:004023FC cmp     ecx, edi ; compare result with correct hash
.text:004023FE mov     [esp], ebx
.text:00402401 jz      short success_caller
.text:00402403 call    failure

This is quite simple, the input was correct if the hash is equal to the correct hash.

def validate(input_hash, correct_hash):
  return input_hash == correct_hash

Correct hash

The correct hash, as far as I can tell, does not depend on any external factors, so it will never change. Therefore, checking ecx at .text:004023FC cmp ecx, edi shows us that the correct hash is 0x931F6CE.

Brute force code

Now that we have all the parts to this, we can create a brute-force solution. You can download it here

import random
import string

def encrypt(input):
  encrypted = ""
  counter = 3
  for c in input:
    encrypted += chr(ord(c) ^ counter ^ 0x4d)
    counter += 1
  return encrypted

def hash(input):
  counter = 0
  result = 0
  for i in range(0, 13):
    result += ord(input[i]) * counter
    counter += 0xFFFF
  return result

def validate(input_hash, correct_hash):
  return input_hash == correct_hash

possible_chars = string.ascii_letters + string.digits + string.punctuation

while True:
  key = ''.join(random.choice(possible_chars) for i in range(13))
  valid = validate(hash(encrypt(key)), 0x931F6CE)
  if valid:
    print(key)

Success

### The goal of this crackme is to find the key! ##

Enter the key : *************
->right!

Example valid keys:

  • HJJe)lo\P_vFH
  • (XNl(WiAVtIBy
  • 1ARaaf@DeVSjt
  • X3fXDZEdiBR]y

According to the author, the original key was A_BIT_HARDER?, however where there are hashes, there are hash collisions.