Rating:

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:


12 BUILD_SLICE_2 2
14 BINARY_SUBSCR
16 CALL_FUNCTION_1 1 ''

L. 3 20 COMPARE_OP !=
22 POP_JUMP_IF_FALSE 28 'to 28'
28_0 COME_FROM 22 '22'

L. 4 28 CALL_FUNCTION_1 1 ''
30 POP_TOP
36 BINARY_SUBSCR
42 BINARY_SUBSCR
44 BINARY_XOR
50 BINARY_SUBSCR
52 BINARY_XOR

L. 5 56 COMPARE_OP !=
58 POP_JUMP_IF_FALSE 64 'to 64'
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:
python
if sum(password[0:3]) != 222:
exit(-1)
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:

python
password = [ord(x) for x in "HTB"]

if sum(password[0:3]) != 222:
exit(-1)
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:

bash
uncompyle6 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:

python
sum_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:

bash
uncompyle6 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' ','