The challenge input consists of a single .pyc file, which is a bytecode for a program run with python 3.8.
There are existing tools to decompile .pyc back to .py such as [decompyle3](https://pypi.org/project/decompyle3/) or [uncompyle6](https://pypi.org/project/uncompyle6/), but unfortunately both of these tools and some others we tested fail to decompile the given pyc file. The two mentioned ones print the JIT opcodes in human readable format and print `Parse error at or near 'None' instruction at offset -1`
We started off trying to fix the pyc file, maybe something was modified manually? But in the end we just took a look at the JIT opcodes manually. It contains a lot code blocks looking similar to this:
```                4  LOAD_GLOBAL              sum                6  LOAD_FAST                'password'                8  LOAD_CONST               0               10  LOAD_CONST               3               12  BUILD_SLICE_2         2                14  BINARY_SUBSCR                   16  CALL_FUNCTION_1       1  ''               18  LOAD_CONST               222
 L.   3        20  COMPARE_OP               !=               22  POP_JUMP_IF_FALSE    28  'to 28'               24  LOAD_GLOBAL              exit               26  LOAD_CONST               -1             28_0  COME_FROM            22  '22'
 L.   4        28  CALL_FUNCTION_1       1  ''               30  POP_TOP                         32  LOAD_FAST                'password'               34  LOAD_CONST               0               36  BINARY_SUBSCR                   38  LOAD_FAST                'password'               40  LOAD_CONST               1               42  BINARY_SUBSCR                   44  BINARY_XOR                      46  LOAD_FAST                'password'               48  LOAD_CONST               2               50  BINARY_SUBSCR                   52  BINARY_XOR                      54  LOAD_CONST               94
 L.   5        56  COMPARE_OP               !=               58  POP_JUMP_IF_FALSE    64  'to 64'               60  LOAD_GLOBAL              exit               62  LOAD_CONST               -1             64_0  COME_FROM            58  '58'
 L.   6        64  CALL_FUNCTION_1       1  ''
```
As we can see there seems to be a local variable `password` which is probably a string or list, maybe even the flag. First we get characters `0-3` from it and calculate their sum. This sum is required to be `222` or the function will call `exit(-1)`. Afterwards the first three characters are XORed and the result needs to be `94` or the function will call `exit(-1)`.In python this logic would look something like this:```pythonif sum(password[0:3]) != 222:    exit(-1)if (password[0] ^ password[1] ^ password[2]) != 94:    exit(-1)```
After the code above the pyc file repeats this process, always summing and xoring three, comparing the results to a constant.The start and end are always incremented by one such that in the second step `sum(password[1:4])` is calculated and so on.
At this point we just assumed the correct password is the flag we are looking for and we can confirm this is probably the case using:
```pythonpassword = [ord(x) for x in "HTB"]
if sum(password[0:3]) != 222:    exit(-1)if (password[0] ^ password[1] ^ password[2]) != 94:    exit(-1) print("the first three seem to be correct!")```
Running above program prints `the first three seem to be correct!`. This means we know the password we are looking for is porbably our flag as flags have the format `HTB{...}`.
Given the first three characters as `HTB` we can calculate the remaining letters one by one. After the `sum(password[0:3]) != 222` check the pyc file checks `sum(password[1:4]) != 273`. Thus we can calculate the forth character using `chr(273 - ord("B") - ord("T"))`. Then the fifth character using the next sum check and so on.In total there are 46 characters i.e. 46 `sum(password[i : i + 3]) == z` checks. We used the following bash/grep line to extract all constants `z`:
```bashuncompyle6 bamboozled.pyc | sed '/^[[:space:]]*$/d' | grep -E -B 2 'COMPARE_OP\s+!=' | grep -E -A 1 'CALL' | grep -E 'LOAD_CONST' | grep -Eo '\s[0-9]+$' | tr '\n' ','```
Which prints
```# file bamboozled.pyc# Deparsing stopped due to parse error 222, 273, 301, 356, 349, 341, 268, 262, 253, 305, 244, 202, 155, 158, 158, 156, 213, 258, 315, 257, 273, 218, 262, 245, 241, 256, 275, 321, 264, 196, 196, 247, 251, 270, 266, 309, 311, 259, 259, 241, 307, 263, 217, 210, 192, 265,```
Using this data one can trivially calculate the full flag:
```pythonsum_results = [ 222, 273, 301, 356, 349, 341, 268, 262, 253, 305, 244, 202, 155, 158, 158, 156, 213, 258, 315, 257, 273, 218, 262, 245, 241, 256, 275, 321, 264, 196, 196, 247, 251, 270, 266, 309, 311, 259, 259, 241, 307, 263, 217, 210, 192, 265]
result = "HTB"for i in range(1, len(sum_results)):    c = chr(sum_results[i] - ord(result[i]) - ord(result[i + 1]))    result += c
print("flag:", result)```
Which prints:```flag: HTB{pyth0n_d155453mbl3r5_a1nt_50_h4rd_t0_br34k!}```
We do not really need the constants from the XOR checks, but one can extract them using:
```bashuncompyle6 bamboozled.pyc | sed '/^[[:space:]]*$/d' | grep -E -B 2 'COMPARE_OP\s+!=' | grep -E -A 1 'BINARY_XOR' | grep -E 'LOAD_CONST' | grep -Eo '\s[0-9]+$' | tr '\n' ','```