diff options
author | AnyUnderstanding | 2024-06-03 22:28:31 +0200 |
---|---|---|
committer | GitHub | 2024-06-03 22:28:31 +0200 |
commit | bcaae7797b1f2d1990b0a8ff5c7117d7362163a0 (patch) | |
tree | 00938c611efb7bd1432091133f88bbeb0ed9846f | |
parent | 4dd35a32edc18394e54ad9f116c21ea268bef8ab (diff) |
Create archventuretime.md (WIP)
-rw-r--r-- | archventuretime.md | 840 |
1 files changed, 840 insertions, 0 deletions
diff --git a/archventuretime.md b/archventuretime.md new file mode 100644 index 0000000..1145063 --- /dev/null +++ b/archventuretime.md @@ -0,0 +1,840 @@ +# 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} +``` |