Posts The First CrackMe of The Series - CrackMe1
Post
Cancel

The First CrackMe of The Series - CrackMe1


Filename: CrackMe1.exe

Architecture: x64

SourceGithub

MD5: ACAB0D5D36D2780C173B8C76CEE1B2F3

SHA-1: F428776AEFCA26CF1C64175260FC8FA75D8CCCD9


Before go to the executable, you can find all of my crackmes in my github repository (you can see  in the header of the post as a source link) as an executable and source code. You can look at the source code of the challenge but it will give you spoiler so I don’t recommend until the end of the post.

Most of the time, I will use Ghidra and Radare2 because they are free so you can reproduce the same steps with the same tools. But if you want to learn how to use Radare2, Ghidra or any tools in this post, I will not cover the usage of the tools right now. In addition to that, sometimes we can use the IDA Freeware.

Firstly, I can use the “file” tool to get basic information from the executable.

1
2
$ file .\CrackMe1.exe
.\CrackMe1.exe; PE32+ executable for MS Windows (console) Mono/.Net assembly

In addition to that, we can use the “rabin2” from radare2.

rabin2 usage

I will open the Ghidra and look at the assembly code. I will not use decompiler window before look at the assembly version of the code. After understanding the code(or after failing to understand, i hope this will be not the case) I will look at the pseudocode.

Our executable is approximately 55K so finding the main function is pretty easy as you can see below.

ghidra

Before diving into all of the functions I will look at the function call graph to understand big picture.

call graph

As you can see above, executable probably consists of 3 important functions which are main -> read_from_file -> validate_license. After that, I will look at the graph view of the read_from_file and validate_license functions to understand their size.

read_from_file,

call graph

validate_license,

I know they are not legible but don’t worry I’ll take a closer look at these graphs. As you can see above, my guess the heavy work is in the validate_license function probably this function is responsible more than one license control. Now, I will look at the assembly code of the main.

As we know, firstly the prologue part is coming, after that the address of the “license.txt” string is copied to _Argc which is probably the label assigned by Ghidra to ECX register and this variable is the first argument of the read_from_file function. What we know until this step, there is a function named read_from_file and it want probably one argument which is the filename to read of its content. Let’s look at the decompiler window of the main function.

Now, I will go to the read_from_file function.

As you can see from the graph, we should trace the green arrows to access validate_license function. Let’s go to step by step from the first box. After the prologue, there are 2 things that catch my attention.

00401583 LEA         RDX,[DAT_0040400c]

0040158a MOV       param_1,qword ptr [RBP + local_res8]

0040158e CALL      fopen

Signature of the fopen function,

1
FILE * fopen ( const char * filename, const char * mode );

Firstly, we see the [DAT_0040400c], this is probably the second argument of the fopen function because as a x64 Windows calling convention the first argument is copied to RCX and the second one to RDX. We know that the first argument of the fopen function is filename so param_1 is the name of the file to be read. As you can see above from the signature of the function, second argument is the mode. If we go to the address of the label DAT_0040400c, we can see the mode char variable. As you can see below, the file will be opened in read mode (“r”), this makes sense.

00401593 MOV    qword ptr [RBP + local_10],RAX

00401597 CMP     qword ptr [RBP + local_10],0x0

0040159c JNZ       LAB_004015b4

After that, the return value of the fopen function will be stored at RAX register and compare with the 0x0 value. This is probably controlling the file’s existence because fopen function return NULL if can’t find the specified file. If the program can’t find the file, prints the “Where is the file!” message to the console.

So we will create the license.txt file to be able to bypass this.

As you can see above, I created the license.txt and the error message is changed. So, now, we will analyze the LAB_004015b4 graph.

Signature of the fscanf function,

1
int fscanf(FILE *stream, const char *format, ...)

Signature of the fclose function,

1
int fclose(FILE *stream)

What we know until here is we have to create license.txt file in the same directory with executable. Our new goal is go to the validate_license function. There is couple of interesting things in LAB_004015b4,

004015b4 LEA       RDX=>local_78,[RBP + -0x70]

004015b8 MOV      RAX,qword ptr [RBP + local_10]

004015bc MOV      R8,RDX

004015bf LEA         RDX,[s_%[^_]_00404021]

004015c6 MOV       param_1,RAX

004015c9 CALL      fscanf

Firstly, we know that function parameters will be stored at RCX,RDX and R8 if there is 3 parameters as a x64 Windows calling convention.

RCX –> Filename

RDX –> Format

R8 –> Content of the file

You might be curious about what is format? If we go to the “s_%[^_]_00404021” label, we can learn the format specifier value.

Format value is the “%[^\n]”, this means fscanf function just reads the file’s first line. (Until to the new line character which is \n) So we know that, license.txt file should have just one line in it.

004015d5 CALL      fclose

004015da MOV       dword ptr [RBP + local_14],EAX

004015dd CMP       dword ptr [RBP + local_14],0x0

004015e1 JZ            LAB_004015f9

After that, fclose function will try to close the opened file, if the return value of the fclose isn’t 0x0, we will go to the validate_license function. So we will go to the LAB_004015f9,

004015f9 LEA          RAX=>local_78,[RBP + -0x70]

004015fd MOV        param_1,RAX

00401600 CALL      validate_license

[RBP+-0x70] kept the content of the file because R8 register pointed to that location. So, validate_license function will be called with just one parameter which is the content of the file.(content of the file = license key) This is the decompiler view of the read_from_file function, if you want to look at it. (Obviously, I replaced some of the variable names with their meaningful equivalents.)

Yeah, if you remember the graph view of the validate_license function, it may seem scary. Let’s look at it step by step.

Signature of the strlen function,

1
size_t strlen(const char *str)

We see some strings and 2 strlen function call in validate_license function.

00401646 MOV       param_1,qword ptr [RBP]=>local_res8

0040164a CALL      strlen

0040164f MOV       dword ptr [RBP + local_4c],EAX

param_1 is the license key read from the license.txt file. First strlen function gets the length of the license key and stores it in [RBP+local_4c].

0040164f MOV        dword ptr [RBP + local_4c],EAX

00401652 MOV       dword ptr [RBP + local_1c],0x0

00401659 MOV       param_1,qword ptr [RBP]=>local_res8

0040165d CALL      strlen

00401662 CMP        RAX,0x17

00401666 JZ            LAB_0040167e

After that, strlen function is called again with the same parameter(actually there was no need to do this, this could use the [RBP+local_4c] value but nevermind :) and gets the length of the license key. After that compares the license key with the 0x17 which is  23. So, we learn that the license key should be 23 character length.

To summarize the 4 graphs above, program take the first 7 bytes of the license key and compares with “#BEGIN#” one by one. So, we know that the license key should be started with #BEGIN# string. After this point, it will be very difficult to explain code with graph view due to the excessive amount of graphics so I will be using the decompiler view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
void validate_license(char *key)
{
  size_t length;
  ulonglong uVar1;
  char key_temp [10];
  int _length;
  char *end;
  char *begin;
  char *encoded;
  int _counter;
  int local_28;
  int reverse_counter;
  int counter;
  int another_counter;

  encoded = "QnhjsxjYj}y";
  begin = "#BEGIN#";
  end = "#END#";

  length = strlen(key);
  _length = (int)length;
  another_counter = 0;
  length = strlen(key);

  if (length != 0x17) {
    printf("The license key is not correct.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }

  counter = 0;
  while (counter < 7) {
    if (begin[counter] != key[counter]) {
      printf("The license key is not correct.");
                    /* WARNING: Subroutine does not return */
      exit(1);
    }
    counter = counter + 1;
  }

  reverse_counter = _length + -5;
  while (reverse_counter < _length) {
    if (end[another_counter] != key[reverse_counter]) {
      printf("The license key is not correct.");
                    /* WARNING: Subroutine does not return */
      exit(1);
    }
    another_counter = another_counter + 1;
    reverse_counter = reverse_counter + 1;
  }
  another_counter = 0;
  local_28 = 7;
  while (local_28 < 0x12) {
    key_temp[another_counter] = key[local_28];
    another_counter = another_counter + 1;
    local_28 = local_28 + 1;
  }

  _counter = 0;
  while( true ) {
    uVar1 = SEXT48(_counter);
    length = strlen(encoded);
    if (length <= uVar1) {
      printf("Congratz!");
      return;
    }
    if ((int)encoded[_counter] != (int)key_temp[_counter] + 5) break;
    _counter = _counter + 1;
  }
  printf("The license key is not correct.");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

I replaced some of the variable names with the more appropriate ones. (sorry for the bad namings :)

After controls the beginning of the license key, program controls the end of the key with “#END#” string. So, we know that license key will start with “#BEGIN#” and end with “#END#”.

As a result, program decodes the encoded string substracting 5 from their ascii value.

So, I can write the python code just gives us the license key like below.

1
2
3
4
5
6
7
8
encoded = "QnhjsxjYj}y"
end = "#END#"
license_key = "#BEGIN#"
for i in encoded:
    license_key += chr(ord(i)-5)

license_key += end
print(license_key)

The key is,

So, I will put this string into the license.txt.

This post is licensed under CC BY 4.0 by the author.