Post

Forgotten Lisence Key

This is a writeup of the reversing challenge Forgotten lisence key from the Security Valley CTF

Level: 3, Score: 30

Premise

Unfortunately, I forgot my license key. Fortunately, the program is somewhat chatty. Maybe together we can manage to recover the license key. Can you help me?

Link: https://github.com/SecurityValley/PublicCTFChallenges/tree/master/reversing/forgotten_license_key

Challenge files:

license license.exe

Observations

Simply running the program gives us the following output:

1
license: error: required flag --key not provided, try --help

So we add the –help flag and receive the following output:

1
2
3
4
5
usage: license --key=KEY [<flags>]

Flags:
  --help     Show context-sensitive help (also try --help-long and --help-man).
  --key=KEY  License key

As we can see, we need to supply a key. We can try running something like ./lisence --key=test, which gives us the following output:

1
wrong length

After some trial and error, we end up finding the correct length of the key, that being 9. However, when running ./lisence --key=aaaaaaaaa we get the output:

1
expect "-" at position 4

So we update our key and try again by running ./lisence --key=aaaa-aaaa, which gives us the output:

1
expect "7" at position 0

We update our key once again and try running ./lisence --key=7aaa-aaaa, which gives us the output:

1
expect "D" at position 6

We keep trying and run ./lisence --key=7aaa-aDaa, which gives us the output:

1
checksum for first license key block failed

Here we’ve reached the limit of what the program is willing to give us, so now another method is required.

Solution

If we investigate the file in the tool Exeinfo PE, it tells us that the program is written in GOlang. Moving forwards, we open the program in ghidra, run its analyzer and investigate further. At a glance, we find ourselves having a hard time getting further, but if we run the GO function renamer script for ghidra written by Dorka Patolay, we’re able to investigate more easily. If we look at the main.main function, we find the following content:

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
void main.main(void)
{
  undefined8 uVar1;
  undefined auVar2 [16];
  undefined local_38 [16];
  undefined local_28 [16];
  undefined local_18 [16];
  
  local_38 = (undefined  [16])0x0;
  while (&stack0x00000000 <= CURRENT_G.stackguard0) {
    runtime.morestack_noctxt();
  }
  gopkg.in/alecthomas/kingpin%2ev2.Parse();
  uVar1 = *DAT_006f0e30;
  if (DAT_006f0e30[1] == 9) {
    auVar2 = main.validateLicenseFormat();
    if (auVar2._0_8_ != 0) {
      (**(code **)(auVar2._0_8_ + 0x18))(auVar2._8_8_);
      local_28 = local_38;
      local_28._8_8_ = runtime.convTstring();
      local_28._0_8_ = &string___runtime._type;
      fmt.Fprintln(&io.Writer___runtime.itab,DAT_006f0e78,local_28,1,1);
      return;
    }
    auVar2 = main.loadStaticLicense(uVar1,9);
    runtime.slicebytetostring(0,auVar2._0_8_,auVar2._8_8_);
    local_38._8_8_ = runtime.convTstring();
    local_38._0_8_ = &string___runtime._type;
    fmt.Fprintln(&io.Writer___runtime.itab,DAT_006f0e78,local_38,1,1);
    return;
  }
  local_18._8_8_ = &PTR_DAT_005e5d18;
  local_18._0_8_ = &string___runtime._type;
  fmt.Fprintln(&io.Writer___runtime.itab,DAT_006f0e78,local_18,1,1);
  return;
}

We can see a call being made to a function named main.validateLisenceFormat which sounds promising, so we move into it to investigate further. Navigating to the main.validateLisenceFormat function, we find the following contents:

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
74
75
undefined  [16] main.validateLicenseFormat(char *param_1,uint param_2)

{
  undefined8 *puVar1;
  undefined auVar2 [16];
  undefined auVar3 [16];
  undefined auVar4 [16];
  undefined auVar5 [16];
  undefined auVar6 [16];
  char *param_10;
  
  param_10 = param_1;
  while (&stack0x00000000 <= CURRENT_G.stackguard0) {
    runtime.morestack_noctxt();
  }
  if (param_2 < 5) {
                    /* WARNING: Subroutine does not return */
    runtime.panicIndex(4,param_2,param_2);
  }
  if (param_10[4] != '-') {
    puVar1 = (undefined8 *)runtime.newobject(&errors.errorString___runtime.structtype);
    puVar1[1] = 0x18;
    *puVar1 = &DAT_005ac18a;
    auVar6._8_8_ = puVar1;
    auVar6._0_8_ = &error___runtime.itab;
    return auVar6;
  }
  if (*param_10 != '7') {
    puVar1 = (undefined8 *)runtime.newobject(&errors.errorString___runtime.structtype);
    puVar1[1] = 0x18;
    *puVar1 = &DAT_005ac1a2;
    auVar5._8_8_ = puVar1;
    auVar5._0_8_ = &error___runtime.itab;
    return auVar5;
  }
  if (param_2 < 7) {
                    /* WARNING: Subroutine does not return */
    runtime.panicIndex(6,param_2,param_2);
  }
  if (param_10[6] != 'D') {
    puVar1 = (undefined8 *)runtime.newobject(&errors.errorString___runtime.structtype);
    puVar1[1] = 0x18;
    *puVar1 = &DAT_005ac1ba;
    auVar4._8_8_ = puVar1;
    auVar4._0_8_ = &error___runtime.itab;
    return auVar4;
  }
  if ((dword)((byte)param_10[1] + 0x37 + (dword)(byte)param_10[2] + (dword)(byte)param_10[3]) !=
      0x102) {
    puVar1 = (undefined8 *)runtime.newobject(&errors.errorString___runtime.structtype);
    puVar1[1] = 0x2b;
    *puVar1 = &DAT_005b14fc;
    auVar3._8_8_ = puVar1;
    auVar3._0_8_ = &error___runtime.itab;
    return auVar3;
  }
  if (7 < param_2) {
    if (param_2 < 9) {
                    /* WARNING: Subroutine does not return */
      runtime.panicIndex(8,param_2,param_2);
    }
    if ((dword)((dword)(byte)param_10[8] + (byte)param_10[5] + 0x44 + (dword)(byte)param_10[7]) !=
        0x11a) {
      puVar1 = (undefined8 *)runtime.newobject(&errors.errorString___runtime.structtype);
      puVar1[1] = 0x2c;
      *puVar1 = &DAT_005b195c;
      auVar2._8_8_ = puVar1;
      auVar2._0_8_ = &error___runtime.itab;
      return auVar2;
    }
    return ZEXT816(0);
  }
                    /* WARNING: Subroutine does not return */
  runtime.panicIndex(7,param_2,param_2);
}

In the early if statements, we find what looks to be the checks the program performs on the license key. These checks are done towards the variable param_10, so we rename it to license_key_arr to make investigations a little easier.

The lines checking if all variables in the key equate to 0x102 and 0x11a, we convert these to decimal format, and change the variables 0x37 and 0x44 to char format, giving us 7 and D respectively.

The result of these changes give us: if ((dword)((byte)license_key_arr[1] + '7' + (dword)(byte)license_key_arr[2] + (dword)(byte)license_key_arr[3]) != 258) and if ((dword)((dword)(byte)license_key_arr[8] + (byte)license_key_arr[5] + 'D' + (dword)(byte)license_key_arr[7]) != 282)

As we can see, we need to get the decimal values of the first key block to equate to 258, and 282 for the second key block.

We can write a bruteforcing script in python to help us with this. By taking the possible combinations of characters that, together with 7, make 258, and likewise for the second block that, together with D make 282, we can attempt to find the key.

Furthermore, we can look for the string SecVal to avoid “bad flags”.

In practice, this could look something along the lines of the following:

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
import os
import string

the_key = "7aaa-aDaa"
output = "checksum for first license key block failed"
alphabet = list(string.ascii_letters)
numbers = ['0','1','2','3','4','5','6','7','8','9']
all_chars = alphabet + numbers

block_1_1, block_1_2, block_1_3 = '', '', ''

block_2_1, block_2_2, block_2_3 = '', '', ''

possible_combos = []
possible_combos_2 = []

for i in range(len(all_chars)):
  for j in range(len(all_chars)):
    for k in range(len(all_chars)):
      block_1_1 = all_chars[i]
      block_1_2 = all_chars[j]
      block_1_3 = all_chars[k]
      if(ord(block_1_1) + ord(block_1_2) + ord(block_1_3) == 203):
        possible_combos.append(block_1_1+block_1_2+block_1_3)

for i in range(len(all_chars)):
  for j in range(len(all_chars)):
    for k in range(len(all_chars)):
      block_2_1 = all_chars[i]
      block_2_2 = all_chars[j]
      block_2_3 = all_chars[k]
      if(ord(block_2_1) + ord(block_2_2) + ord(block_2_3) == 214):
        possible_combos_2.append(block_2_1+block_2_2+block_2_3)

for i in range(len(possible_combos)):
  for j in range(len(possible_combos_2)):
    the_key = "7"
    the_key = the_key + possible_combos[i]
    the_key = the_key + "-"
    the_key = the_key + possible_combos_2[j][0]
    the_key = the_key + "D"
    the_key = the_key + possible_combos_2[j][1] + possible_combos_2[j][2]
    output = os.popen(f"./license --key={the_key}").read()
    if("SecVal" in output):
      print(output)
      print("Key is: " + the_key)
      break

The output if this script is: SecVal{[REDACTED]}

Key is: 7xxx-xDxx

Which gives us the key and the flag (which I’ve redacted from here)

Tools used:

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