Tags: keygen reverse-engineering 

Rating: 5.0

Looking for where the entered key is read in, there is a function (offset 0x00101ab0) which is called from a function called in a loop from main and it reads input with wgetnstr. Fixing its stack frame to account for the different arrays:

undefined8        RAX:8          keyLen
undefined1        SIL:1          badFlag
undefined8        RAX:8          retVal
undefined8        Stack[-0x10]:8 local_10
undefined1[264]   Stack[-0x118   weirdArr
char[14]          Stack[-0x138   input_key  
undefined1[48]    Stack[-0x168   key_no_dashes

This function first validates the key format (output from ghidra):

  wgetnstr(stdscr,input_key,0xffffffff);
  keyLen = strlen(input_key);
  badFlag = false;
  sVar1 = 0;
  while (keyLen != sVar1) {
    if ((((int)sVar1 != 4) && ((int)sVar1 != 9)) &&
       (0x19 < (byte)(*(char *)(sVar1 + (long)input_key) + 0xbfU))) {
      badFlag = true;
    }
    sVar1 = sVar1 + 1;
  }
  if (((keyLen != 0xe) || (input_key[4] != '-')) || ((input_key[9] != '-' || (badFlag)))) {
    retVal = 0xffffffff;
  }

then copies all the non-dash characters to key_no_dashes:

    lVar2 = 0;
    iVar6 = 0;
    do {
      if (((int)lVar2 != 4) && ((int)lVar2 != 9)) {
        lVar5 = (long)iVar6;
        iVar6 = iVar6 + 1;
        key_no_dashes[lVar5] = (int)*(char *)(lVar2 + (long)input_key);
      }
      lVar2 = lVar2 + 1;
    } while (lVar2 != 0xe);

calls a function with weirdArr as a parameter, then maps all the characters in key_no_dashes using weirdArr (taking the element at the index corresponding to the character's value):

    puVar3 = key_no_dashes;
    do {
      puVar4 = puVar3 + 1;
      *puVar3 = (uint)weirdArr[(int)*puVar3];
      puVar3 = puVar4;
    } while ((uint *)input_key != puVar4);

and finally calls yet another function to determine whether the modified key is valid. This function allocates a large array on the stack and copies data from somewhere in memory into it:

memcpy(aiStack8216,&DAT_00103560,0x2008);

and then does the actual validation:

  piVar2_rdx = modifiedKey + 1;
  iVar1_rax = aiStack8216[*modifiedKey];
  do {
    if (*piVar2_rdx != aiStack8216[iVar1_rax]) {
      retVal = 0;
      goto LAB_00101a80;
    }
    piVar2_rdx = piVar2_rdx + 1;
    iVar1_rax = aiStack8216[iVar1_rax + 1];
  } while (piVar2_rdx != modifiedKey + 0xc);
  retVal = 1;

This first sets iVar1_rax from the value obtained from the first character. Then, in a loop, it checks the array at that position against the value obtained from the current character (*piVar2_rdx), then sets iVar1_rax to the value in the array at iVar1_rax + 1.

This means that for every first character (the value obtained from it), there is at most one key that is valid. Thus, the keys can be generated by trying all the possible first characters and seeing whether a valid key sequence results.

The values for the large array in the last function can be dumped from memory in gdb after the memcopy (dump memory), and the same could be done for weirdArr in the first function. However, code can be run to generate that array:

#include <stdio.h>

typedef unsigned char byte;
typedef unsigned long ulong;
typedef unsigned int uint;

char arr[270];

int main() {
  byte bVar1;
  uint uVar2;
  ulong uVar3;
  byte bVar4;

  uVar2 = 1;
  uVar3 = 1;
  do {
    bVar4 = (byte)uVar3;
    bVar1 = bVar4 * '\x02' ^ bVar4;
    if ((char)bVar4 < '\0') {
      bVar1 = bVar1 ^ 0x1b;
    }
    uVar3 = (ulong)bVar1;
    uVar2 = uVar2 * 2 ^ uVar2;
    uVar2 = uVar2 ^ uVar2 * 4;
    uVar2 = uVar2 << 4 ^ uVar2;
    if ((char)uVar2 < '\0') {
      uVar2 = uVar2 ^ 9;
    }
    bVar4 = (byte)uVar2;
    arr[uVar3] = (bVar4 << 1 | (char)bVar4 < '\0') ^ (bVar4 << 4 | bVar4 >> 4) ^ bVar4 ^
      (bVar4 << 2 | bVar4 >> 6) ^ (bVar4 << 3 | bVar4 >> 5) ^ 99;
  } while (bVar1 != 1);
  *arr = 99;

  for(int i = 0; i < 270; ++ i) {
    printf("%d ", (uint)(byte)arr[i]);
  }
  printf("\n");
}

(Only the first 256 bytes can matter.)

A full keygen (large array dumped in middump):

bigArr = []
with open("middump", "rb") as f:
    for i in range(0x2008 // 4):
        v = int.from_bytes(f.read(4), "little")
        bigArr.append(v)

# generated array
weirdArr = [99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

allowedSlice = weirdArr[ord('A'):ord('Z')+1]

revMap = {i: chr(ord('A')+c) for c, i in enumerate(allowedSlice)}

for initc in allowedSlice:
    try:
        key = ""
        key += revMap[initc]
        curri = bigArr[initc]
        for _ in range(11):
            key += revMap[bigArr[curri]]
            curri = bigArr[curri + 1]
        key = list(key)
        key.insert(4,'-')
        key.insert(9,'-')
        print("".join(key))
    except:
        pass

which generates precisely 10 keys:

ADGR-THFS-SZPF
BNQO-PSBL-SDHD
CROG-IURB-TNDE
IOXT-KDQL-VZFE
JIRX-YPMF-IZLO
QWMA-LSRT-PZKX
SXFH-MICO-PEWZ
WECN-UQWO-PQWX
XBUZ-CHUC-IKLY
YJFS-ZLMU-RVJT

Giving these to the server returns the flag darkCON{DRM_FR33_g4m35_4r3_EZ-T0_cR4ck}.

Original writeup (https://github.com/keyboard-monkeys/ctf-writeups/blob/main/2021-darkctf/reverse_cyberdark0x02_installer.md).