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):

```C
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`:

```C
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):

```C
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:

```C
memcpy(aiStack8216,&DAT_00103560,0x2008);
```

and then does the actual validation:

```C
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:

```C
#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`):

```python 3
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).