# Archventuretime
## TODO -> still WIP
Archventuretime is a reversing challenge, where you have to find obtain a license-key.

## First look
The challenge consists of binary and a Dockerfile. The Dockerfile installs various QEMU packages on an ubuntu system and then starts the binary. 
After starting the docker you are prompted with `Enter license key> `, so I entered a few random chars and, surprise, `[WARNING] Invalid format!`
 (Note: i changed the flag in `docker run -t` to `docker run -ti` to get inputs working).

# The main binary
So let's look at the included binary `chal`:
```
> file chal
chal: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, [...] , for GNU/Linux 3.2.0, stripped
```
Unfortunately `chal` is stripped so reversing it will be a bit harder. So let's get to work and decompile `chal` with ghidra. If we search for the `Enter license key> `, we find the following functions. I added some comments to it, with ideas I got from the initial look.
```c
undefined8 FUN_00101c48(void) {
  /* [...] <- variable definitions removed for readability */
  
  // read liscence key up to length 24d, and remove new lines
  printf("Enter license key> ");
  fgets((char *)&local_48,0x18,stdin);
  sVar1 = strcspn((char *)&local_48,"\n");
  *(undefined *)((long)&local_48 + sVar1) = 0;

  // call some function with the liscence key, not sure what it does yet
  FUN_001014a9(&local_48);
  
  // remove every 6th char from the key and save the result in local_28
  local_50 = 0;
  for (local_4c = 0; local_4c < 0x17; local_4c = local_4c  + 1) {
    if ((local_4c + 1) % 6 == 0) {
      local_50 = local_50 + 1;
    }
    else {
      *(undefined *)((long)&local_28 + (long)(local_4c - l ocal_50)) =
           *(undefined *)((long)&local_48 + (long)local_4c) ;
    }
  }

  // call a function, with the stripped liscence key
  FUN_001015dc(&local_28);

  // call a function with the liscence key
  FUN_00101920(&local_48);
  
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return * /
    __stack_chk_fail();
  }
  return 0;
}
```
We saw that the functions reads the key and then calls three different functions with it. So let's call the function `readKey` and take a look what the first called function does. We already know, that it takes the license-key as an argument, so I already renamed the parameter and added commands:
```c

void FUN_001014a9(char *key) {
  size_t sVar1;
  ushort **ppuVar2;
  int local_c;
  
  // ensure that the key is greater than 23d, else print a warning and exit
  // since we read keys with length up to 24d we know that the liscence key must exactly be 24d 
  // chars long
  sVar1 = strlen(key);
  if (sVar1 < 0x17) {
    puts(PTR_s_[WARNING]_Invalid_format!_0012a010) ;
                    /* WARNING: Subroutine does not return * /
    exit(1);
  }
  // loop through the key
  local_c = 0;
  do {
    if (0x16 < local_c) {
      return;
    }
    // check that every 6th char is a '-' 
    if ((local_c + 1) % 6 == 0) {
      if (key[local_c] != '-') {
        puts(PTR_s_[WARNING]_Invalid_format!_0012a01 0);
                    /* WARNING: Subroutine does not return * /
        exit(1);
      }
    }
    // check that every other char is uppercase and alphanumeric, if this is not the case
    // print a warning and exit the programm
    else {
      // __ctype_b_loc() returns a struct with informations about the char
      ppuVar2 = __ctype_b_loc();
      // char is not upper case?
      if (((*ppuVar2)[key[local_c]] & 0x800) == 0) {
        ppuVar2 = __ctype_b_loc();
        // char is not a number?
        if (((*ppuVar2)[key[local_c]] & 0x100) == 0) {
          puts(PTR_s_[WARNING]_Invalid_format!_0012a0 10);
                    /* WARNING: Subroutine does not return * /
          exit(1);
        }
      }
    }
    local_c = local_c + 1;
  } while(true);
}
```
So the method seems to check that the license-key is in the format "XXXXX-XXXXX-XXXXX-XXXXX", with X being an alphanumeric char. So let's call the method `checkFormat` and construct a key and input it into the binary.

```
> ./chal
Enter license key> 12345-ABCDE-12345-ABCDE
[ERROR] Invalid license key!
```
Yaaay 🎉, a different error message. Since the error message isn't printed in `checkFormat` our key has the correct format now and we can continue to the next method call in the `readKey` function. Again, I already renamed the parameter of the function and added comments:

```c

void FUN_001015dc(char *strippedKey) {
   /* [...] <- variable definitions removed for readability */

  // change working directory to '/tmp'
  chdir("/tmp");
  
  // loop with 4 iterations
  for (local_d0 = 0; local_d0 < 4; local_d0 = local_d0 + 1 ) {
    lVar3 = (long)(int)local_d0;
    puVar1 = (&PTR_s_qemu-riscv64_-L_/usr/riscv64-li n_00129c80)[lVar3 * 3];
    __buf = (&PTR_DAT_00129c88)[lVar3 * 3];
    __n = *(size_t *)(&DAT_00129c90 + lVar3 * 0x18);
    local_a4 = 0x5858586b63656863;
    local_9c = 0x585858;
    iVar2 = mkstemp((char *)&local_a4);
    write(iVar2,__buf,__n);
    close(iVar2);
    chmod((char *)&local_a4,0x1c0);
    snprintf((char *)&local_98,0x80,"%s %s %s",puVar1, &local_a4,strippedKey);
    iVar2 = system((char *)&local_98);
    if (iVar2 != 0) {
      remove((char *)&local_a4);
      puts(PTR_s_[ERROR]_Invalid_license_key!_0012a0 18);
                    /* WARNING: Subroutine does not return * /
      exit(1);
    }
    remove((char *)&local_a4);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return * /
    __stack_chk_fail();
  }
  return;
}
```
Ok a lot is happening here, let's give names to the variables:
```c
void FUN_001015dc(char *strippedKey) {

  /* [...] <- variable definitions removed for readability */

  // change working directory to '/tmp'
  chdir("/tmp");

  // loop with 4 iterations
  for (idx = 0; idx < 4; idx = idx + 1) {
    idx_ = (long)(int)idx;
                    /* commandPrefix = qemu-riscv64 -L /usr/riscv64-linux-gnu */
    commandPrefix = (&PTR_s_qemu-riscv64_-L_/usr/r iscv64-lin_00129c80)[idx_ * 3];

	// read raw
    __buf = (&PTR_DAT_00129c88)[idx_ * 3];
    __n = *(size_t *)(&DAT_00129c90 + idx_ * 0x18);

	// create a file with name checkXXXXXX, with X being random chars 
	// and write __buf into it
    filename = 0x5858586b63656863;
    local_9c = 0x585858;
    fileDescriptor = mkstemp((char *)&filename);
    write(fileDescriptor,__buf,__n);
    close(fileDescriptor);

	// make the file executable
    chmod((char *)&filename,0x1c0);

	// execute the command qemu-riscv64 -L /usr/riscv64-linux-gnu filename strippedKey
    snprintf((char *)&local_98,0x80,"%s %s %s",commandPrefix,&filename,strippedKey);
    status = system((char *)&local_98);

	// if the previously executed command returns an error, print an error and exit
	
    if (status != 0) {
      // deltete the file
      remove((char *)&filename);
      puts(PTR_s_[ERROR]_Invalid_license_key!_0012a0 18);
                    /* WARNING: Subroutine does not return * /
      exit(1);
    }
    // delete the file
    remove((char *)&filenameTemplate);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return * /
    __stack_chk_fail();
  }
  return;
}
```
The function creates 4 new binaries, executes them with the license-key as an argument. If one binary fails, an error is printed and the `chal` exists, so we call the function `checkWithBinaries`. Before we dive deeper in the newly created binaries, let's take a quick look at the third function call in the `readKey` function:
```c
void FUN_00101920(void *param_1) {
    /* [...] <- variable definitions removed for readability */

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts(PTR_s_[CORRECT]_License_key_validated_001 2a020);
  printf("Decrypting product");
  fflush(stdout);
  for (local_130 = 0; local_130 < 3; local_130 = local_13 0 + 1) {
    sleep(1);
    putc(0x2e,stdout);
    fflush(stdout);
  }
  putc(10,stdout);
  putc(10,stdout);
  fflush(stdout);

/* [...] <- variable definitions removed for readability */


  // use AES to decrypt the Flag with the key
  iVar1 = FUN_00101828(&local_e8,0x50,&local_108,&l ocal_118,&local_98);
  *(undefined *)((long)&local_98 + (long)iVar1) = 0;
  puts(
      "Welcome to GPN CTF 2024!\n\n            ========= ==            \n        ===================        \n     -= ======================-     \n    ============ ===============    \n  -===============-==== =========-  \n  ===========::::::============= =  \n =============:=========::::====== \n==== ===========:::::::::::::=======\n===========::=-::: ::::::::::=======\n==========::::=-::::::::::========= \n=========::::::=-:-================\n====== ==::::::::=-:================\n ======::::::::::=-:== ============ \n  =====:::::::::::=-=============  \n  -=====:::::::::::=============-  \n    ========= ==================    \n     -================ =======-     \n        ===================        \n            ===========            \n\n"
      );
  puts((char *)&local_98);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return * /
    __stack_chk_fail();
  }
  return;
}
```
This function seems to decrypt the Flag with the license-key using the AES and print a nice message. As far as I can tell, the key decryption looks save. So we need to obtain the license-key by reversing the binaries created by the `checkWithBinaries` function.

## Four new binaries
At first, we actually need to obtain the binaries, which isn't trivial because the are instantly delted after their execution:
```c
void  checkWithBinaries(char  *strippedKey) {
    // [...]
	if  (status !=  0)  {  
		remove((char  *)&filename);
		puts(PTR_s_[ERROR]_Invalid_license_key!_0012a0 18); 
		exit(1); 
	}
	// [...]
}
```

 I can think of two good ways to achieve this:
* Debug `chal` with gdb and break right before `remove` gets called
* Patch the binary and change `remove` to `strlen` for example
 
I went with the second option, and created `chal_patched`,  executing it yields:
```
> ./chal_patched
Enter license key> 12345-ABCDE-12345-ABCDE
[ERROR] Invalid license key!
> ls /tmp | grep "check"
checkr5Rt4S
``` 
Voila, the first binary! Decompiling it with ghirda and searching a bit yields the following main function (I already renamed a few symbols):
```c
ulong main(int param_1,long param_2)

{
  undefined4 strLen;
  long check;
  ulong succ_;
  char *arg1;
  
  if (param_1 < 2) {
    succ_ = 1;
  }
  else {
    arg1 = *(char **)(param_2 + 8);
    strLen = getStrLen(arg1);
    sort(arg1,strLen);
    check = strcmp(arg1,"067889BBCKKMOPPUVWYY");
    if (check == 0) {
      succ_ = 0;
    }
    else {
      succ_ = 1;
    }
  }
  return succ_;
}
```
Sort is just a baisc quicksort implementation. So the binary just checks if the inputed liscence-key consists of the same chars as the corret liscence key in arbitrary order. With this we know all chars of our Key `067889BBCKKMOPPUVWYY` and can construct a new key bypassing the first binary:
```
> rm /tmp/check*
> ./chal_patched 
Enter license key> 06788-9BBCK-KMOPP-UVWYY
[ERROR] Invalid license key!
> ls /tmp/ | grep check
checkHhOuNh
checkJRzpc8
```
Nice! Our second binary, lets also analyze it with ghidra:
```c

undefined8 main(int param_1,long param_2) {

   /* [...] <- variable definitions removed for readability */
  
  if (param_1 < 2) {
    uVar1 = 1;
  }
  else {
    __s = *(char **)(param_2 + 8);
    strlen(__s);
    // loop through the key, split it in to 4 blocks of size 5
    for (local_24 = 0; local_24 < 4; local_24 = local_24 + 1) {
      local_20 = 0;
      local_1c = 0;
      for (local_18 = local_24 * 5; local_18 < (local_24 + 1) * 5; local_18 = local_18 + 1) {
       // check if the char is uppercase
       // if thats the case we add its value (ascii representation) and -0x41 to local_1c 
        ppuVar2 = __ctype_b_loc();
        if (((*ppuVar2)[__s[local_18]] & 0x800) == 0) {
          ppuVar2 = __ctype_b_loc();
          if (((*ppuVar2)[__s[local_18]] & 0x100) != 0) {
            local_1c = local_1c + __s[local_18] + -0x41;
          }
        }
        // check if the char is numeric
        // if thats the case we add its value and -0x30 to local_20
        else {
          local_20 = local_20 + __s[local_18] + -0x30;
        }
      }
      // check if the sum for every block matches some constant
      // numeric chars and uppercase chars are seperated into two diffrent sums
      if (local_20 != *(int *)(&DAT_00102010 + (long)loca l_24 * 4)) {
        return 1;
      }
      if (local_1c != *(int *)(&DAT_00102020 + (long)loca l_24 * 4)) {
        return 1;
      }
    }
    uVar1 = 0;
  }
  return uVar1;
}
```
So this method splits the Key into 4 Blocks of 5 and then sums the block seperatly for uppercase and numeric chars. For example:
```
Key: 06788-9BBCK-KMOPP-UVWYY (Note: the key is passed without the '-' to the binary)
Blocks:
// TODO = I made a mistake here somwhere with the indices
06788 -> sumNumeric = 0 + 6 + 7 + 8, sumUppercase = 0
9BBCK -> sumNumeric = 9, sumUppercase = B + B + C + K = 1 + 1 + 2 + 10
KMOPP -> sumNumeric = 0, sumUppercase = 11 + 13 + 14 + 15
UVWYY -> sumNumeric = 0, sumUppercase = 20 + 21 + 22 + 24 + 34
```
Then we take those sums and check each against a constant that we can obtain from the binary. With that we can write a script, which given all possible letters (from the first binary) puts out every possible key (Note that this script puts out some duplicates, but this won't be a problem):
```py
import itertools  
  
  
# the constants we got from the second binary  
num0 = 0  
num1 = 0x00000007  
num2 = 0x0000000E  
num3 = 0x00000011  
  
let0 = 0x0000003D  
let1 = 0x00000024  
let2 = 0x0000002C  
let3 = 0x00000032  
  
# all possbile letters and numbers (from the first binary)  
letters = list("BBCKKMOPPUVWYY")  
numbers = list("067889")  
  
  
def list_diff(l1, l2):  
    """  
 :return l1 without all elements in l2 """  lx1 = l1[:]  
    for i in l2:  
        if i in lx1:  
            lx1.remove(i)  
    return lx1  
  
  
def get_combinations_up_to_5(l):  
    result = []  
    for i in range(0, 6):  
        result += list(itertools.combinations(l, i))  
    return result  
  
  
def find_sets(l, k):  
    """  
 find sets up to length 5 which sum up to k """  result = []  
    combinations = get_combinations_up_to_5(l)  
    for i in combinations:  
        if sum(i) == k:  
            result.append(i)  
    return result  
  
  
def flatten(l):  
    result = []  
    for i in l:  
        if type(i) is list:  
            for j in i:  
                result.append(j)  
        else:  
            result.append(i)  
    return result  
  
  
def flatten_result(l):  
    result = []  
    for i in l:  
        result.append(flatten(i))  
    return result  
  
  
def combine(elem, l):  
    result = []  
    for i in l:  
        if i is list:  
            for j in i:  
                result.append([elem, j])  
        else:  
            result.append([elem, i])  
    return result  
  
  
def partition_subset_sum(l, sizes):  
    """  
 partitions l in to combinations up to length 5 which sum matches sizes[i] """  valid = find_sets(l, sizes[0])  
    if len(sizes) == 1:  
        return valid  
 result = []  
    for valid_set in valid:  
        new_l = list_diff(l, valid_set)  
        new_valid = partition_subset_sum(new_l, sizes[1:])  
        result += combine(valid_set, new_valid)  
  
    return flatten_result(result)  
  
  
def encode(l, n):  
    encoded = []  
    for i in l:  
        encoded.append(ord(i) - n)  
    return encoded  
  
  
def decode(l, n):  
    decoded = []  
    for i in l:  
        tuple_ = []  
        for j in i:  
            tuple_.append(chr(j + n))  
        decoded.append(tuple(tuple_))  
  
    return decoded  
  
  
number_subsets = [decode(i, 48) for i in partition_subset_sum(encode(numbers, 48), [num0, num1, num2, num3])]  
letter_subsets = [decode(i, 65) for i in partition_subset_sum(encode(letters, 65), [let0, let1, let2, let3])]
```
Lets add print statements and run the code
```py
for i in letter_subsets:  
    for j in number_subsets:  
        x0 = i[0] + j[0]  
        x1 = i[1] + j[1]  
        x2 = i[2] + j[2]  
        x3 = i[3] + j[3]  
        if len(x0) == 5 and len(x1) == 5 and len(x2) == 5 and len(x3) == 5:  
            print(x0, x1, x2, x3)
```
```
[('P', 'W', 'Y'), ('M', 'Y'), ('B', 'C', 'U', 'V'), ('B', 'K', 'K', 'O', 'P')]
[(), ('7',), ('6', '8'), ('8', '9')]
```
With that we can build a new liscence key, which sould get past the first and second binary:
```
[...]
('K', 'K', 'M', 'O', 'P') ('B', 'P', 'U', '0', '7') ('B', 'V', 'W', '6', '8') ('C', 'Y', 'Y', '8', '9')
```
Lets bring it into the right format:
```
KKMOP-BPU07-BVW68-CYY89
```
And check if it works:

```
> rm /tmp/check*
> ./chal_patched 
Enter license key> KKMOP-BPU07-BVW68-CYY89
[ERROR] Invalid license key!
> ls /tmp/ | grep check
checkiUZg6q
checklhvtqU
checktyOyll
```
Here we go, our third binary :). As always we put it into ghidra (I already renamed a few symbols)
```c

undefined4 .opd.FUN_1000074c(int argc,longlong arg s) {
  int len;
  char *alphaNumIndex;
  int notSucc;
  undefined4 uVar1;
  char *key;
  int idx;
  
  if (argc < 2) {
    uVar1 = 1;
  }
  else {
    key = *(char **)(args + 8);
    // strLen wraps strlen
    len = strLen(key);
    // loop through the key
    for (idx = 0; idx < len + -1; idx = idx + 1) {
      // get the index in the alphabet + numerbs in the key
      // AlphaNum points to ABCDEFGHIJKLMNOPQRSTUVWXYZ012345689
      // strChr wraps strchr
      alphaNumIndex._4_4_ = strChr(AlphaNum,key[idx]) ;
      
      // UNK_10000940 points to a array of intergers with only the last 8Bit set
      // So we have 36 sections of size 24.
      // 36 is the size of the alpahbet plus the numbers
      // we pass the start of the section (index by the current char) to charInRange and we 
      // also pass the next char into char in range
      notSucc = charInRange(&UNK_10000940 + (longlong)(alphaNumIndex._4_4_ - (int)AlphaNum) * 96,
                            key[(longlong)idx + 1],24);
      if (notSucc == 0) {
        return 1;
      }
    }
    uVar1 = 0;
  }
  return uVar1;
}

```
So we loop through the key, in `charInRange` we perform some sort of check and if returns 0 the program exits with an error. Next we examine `charInRange` (Symbols renamed):
```c

undefined4 charInRange(longlong prevCharAddr,int char,int x24) {
  int idx;
  
  idx = 0;
  while(true) {
    // x24 is always 24
    if (x24 <= idx) {
      // keep in mind 0 means error
      return 0;
    }
    if (char == *(int *)(prevCharAddr + (longlong)idx * 4) ) break;
    idx = idx + 1;
  }
  return 1;
}
```
`charInRange` checks if the next char is somwhere in the section. If that is not the case it returns an error. This is the next constraint for out liscence-key. Nice! The next python script. Lets start off by extracting the array out of the binary:
```py
integers = []
try:  
    with open("<pathToBinary>/third", "rb") as fin:  
        # 0x0000940 is the start address of the array
        # 0x00016C0 the end address
        fin.seek(0x0000940)  
        for i in range(int((0x00016C0 - 0x0000940) / 4)):  
            fin.read(3)  
            x = fin.read(1)  
            integers.append(x)  
  
        for i in range(0, len(integers), 24):  
            blocksOf24.append(integers[i:i + 24])  
except FileNotFoundError:  
    print("File 'third' not found.")  
    exit(1)
```
Let's also add some code to generate permutations of the previously generated blocks and check if they are within the constrains of the third binary:
```py
from itertools import combinations, permutations  
from second import letter_subsets, number_subsets
  
alphaNum = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")  
  
def getPermutations(l):  
    c = []  
    n = 5  
  for i in range(n - 1, n):  
        c.extend(permutations(l, i + 1))  
    return c  
  
def printAsKey(l):  
    for x in range(len(l)):  
        if (x + 1) % 5 == 0 and x != 19:  
            print(l[x] + "-")  
        else:  
            print(l[x])  
    print("\n")  
  
def getAsInput(l):  
    result = ""  
  for x in range(len(l)):  
        result += l[x]  
    return result  
  
def combineLists(l1, l2):  
    result = []  
    c00 = getPermutations(l1)  
    c1 = getPermutations(l2)  
    for xx in c00:  
        for y in c1:  
            result.append(list(xx) + list(y))  
    return result  
  
integers = []  
blocksOf24 = []  
  
try:  
    with open("third", "rb") as fin:  
        fin.seek(0x0000940)  
        for i in range(int((0x00016C0 - 0x0000940) / 4)):  
            fin.read(3)  
            x = fin.read(1)  
            integers.append(x)  
  
        for i in range(0, len(integers), 24):  
            blocksOf24.append(integers[i:i + 24])  
except FileNotFoundError:  
    print("File 'third' not found.")  
    exit(1)  
  
def checkRule(chr, nextChr):  
    index = alphaNum.index(str(chr))  
    block = blocksOf24[index]  
    if nextChr.encode() in block:  
        return True  
 else:  
        return False  
  
possibleCombinations = []  
  
for pN in number_subsets:  
    for pL in letter_subsets:  
        pC = [pN[0] + pL[0], pN[1] + pL[1], pN[2] + pL[2], pN[3] + pL[3]]  
        continueFlag = False  
 for x in pC:  
            if len(x) != 5:  
                continueFlag = True  
 break if continueFlag:  
            continue  
  possibleCombinations.append(pC)  
  
def check(permutations):  
    result = []  
    for possibility in permutations:  
        appendFlag = True  
 for i in range(4):  
            if not checkRule(possibility[i], possibility[i + 1]):  
                appendFlag = False  
 break if appendFlag:  
            result.append(possibility)  
    return result  
  
def checkBordersAndCombine(l1, l2):  
    result = []  
    for xx in l1:  
        for y in l2:  
            if len(y) == 10:  
                # this condition will make sense after we saw the fourth binary
                if not (y[8] == "8" and y[7] == "M" and y[5] == "Y"):  
                    continue  
 if checkRule(xx[-1], y[0]):  
                result.append(xx + y)  
    return result  
  
allCombinations = []  
for pC in possibleCombinations:  
    firstPermutations = check(getPermutations(pC[0]))  
    secondPermutations = check(getPermutations(pC[1]))  
    thirdPermutations = check(getPermutations(pC[2]))  
    fourthPermutations = check(getPermutations(pC[3]))  
    firstSecond = checkBordersAndCombine(firstPermutations, secondPermutations)  
    thirdFourth = checkBordersAndCombine(thirdPermutations, fourthPermutations)  
    allCombinations__ = checkBordersAndCombine(firstSecond, thirdFourth)  
    allCombinations.extend(allCombinations__)  
  
print(allCombinations[0])
```
Running the program yields:
```
('P', 'B', 'V', 'C', 'W', '7', 'B', 'K', 'K', 'P', '0', 'Y', '8', '6', 'U', 'Y', '9', 'M', '8', 'O')
```
Formating it correctly and inputing it into the chal again gives us:
```
> rm /tmp/check*
> ./chal_patched 
Enter license key> 06788-9BBCK-KMOPP-UVWYY
[ERROR] Invalid license key!
> ls /tmp/ | grep check
check0HCUhx
checkALAL1v
checkN8Dg5E
checkWGTL4q
```
Thats the fourth binary! You already know whats the next step, we put it into ghidra :)
(Symbols renamed and comments added)
```c

undefined8 FUN_00100754(int param_1,long param_ 2) {

  /* [...] <- variable definitions removed for readability */
  
  if (param_1 < 2) {
    uVar1 = 1;
  }
  else {
    key = *(char **)(param_2 + 8);
    strLen = strlen(key);
    for (idx = 0; idx < (int)strLen; idx = idx + 1) {
      // DAT_001008e8 points to an integer value with len 20
      // All values are 0xFFFFFFFF except those at index 8, 7 and 5
      // 8 -> 8
      // 7 -> M
      // 5 -> Y
      // An Integer with value 0xFFFFFFFF is smaller than 0 so we fail the first check
      // for index 8,7 and 5 we don't
      // => our key is 8 at index 8, M at index 7 and Y at index 5
      if ((0 < *(int *)(&DAT_001008e8 + (long)idx * 4)) &&
         ((uint)(byte)key[idx] != *(uint *)(&DAT_001008e8 + (long)idx * 4))) {
        return 9;
      }
    }
    for (kdx = 0; kdx < (int)strLen; kdx = kdx + 1) {
      for (udx = 0; udx < 10; udx = udx + 1) {
        index = ((long)kdx * 10 + (long)udx) * 16;
        uStack_c = (uint)((ulong)*(undefined8 *)(&DAT_0 0100938 + index) >> 32);
        if (uStack_c == (byte)key[kdx]) {
          index1 = (int)*(undefined8 *)(&DAT_00100940 +  index);
          local_4 = (uint)((ulong)*(undefined8 *)(&DAT_00 100940 + index) >> 0x20);
          if ((byte)key[index1] != local_4) {
            return 1;
          }
        }
      }
    }
    uVar1 = 0;
  }
  return uVar1;
}
```
This leaves us with 2 possibilties, reverse the second for-loop or bruteforce the key. With the previous constrains with have about 500k of of possible keys left (including duplicatess). At this point I was pretty hungry and wanted to take a break so I went for the second option and enjoyed some nice Gulasch at GPN:
```py
import subprocess  
import asyncio  
  
  
def background(f):  
    def wrapped(*args, **kwargs):  
        return asyncio.get_event_loop().run_in_executor(None, f, *args, **kwargs)  
  
    return wrapped  
  
  
@background  
def bruteforce(ix):  
    x = allCombinations[ix]  
    result = subprocess.Popen(  
        "qemu-aarch64 -L /usr/aarch64-linux-gnu <pathToBinary>/fourth " + getAsInput(x),  
        shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)  
    result.communicate()[0]  
    return_code = result.returncode  
    if return_code == 0:  
        print("Success")  
        print(getAsInput(x))  
  
        exit(0)  
    if ix % 100 == 0:  
        print(ix)  
  
  
for ix in range(len(allCombinations)):  
    bruteforce(ix)
```
With that I'am able to test about 7k keys per minute
```
500k / 7k per min = 70 min worstcase
```
Actually the key was found around the 10 minute mark.
```
[...]
71800
71900
Success
UPPBKK0Y7C6B8VWY9M8O
```
Nice, lets bring it into the correct format:
```
./chal 
Enter license key> UPPBK-K0Y7C-6B8VW-Y9M8O
[CORRECT] License key validated

Decrypting product...

Welcome to GPN CTF 2024!

            ===========            
        ===================        
     -=======================-     
    ===========================    
  -===============-=============-  
  ===========::::::==============  
 =============:=========::::====== 
===============:::::::::::::=======
===========::=-:::::::::::::=======
==========::::=-::::::::::=========
=========::::::=-:-================
========::::::::=-:================
 ======::::::::::=-:============== 
  =====:::::::::::=-=============  
  -=====:::::::::::=============-  
    ===========================    
     -=======================-     
        ===================        
            ===========            


GPNCTF{W0nd3rful!_Y0u're_2_cl3ver_f0r_th4t_l1cens3_ch3ck!_W3ll_d0ne_<3}
```