Post

Hack The Boo 2023: SpookyCheck

Writeup for the “SpookyCheck” challenge created by Hack The Box for the Hack The Boo 2023 CTF.

For this challenge, a Python compiled file is provided: rev_spookycheck.zip

1
check.pyc: Byte-compiled Python module for CPython 3.11, timestamp-based, .py timestamp: Mon Sep  4 15:32:51 2023 UTC, .py size: 656 bytes

Using pycdc, we can try to decompile the file, but we encounter an error.

1
2
3
4
5
6
7
8
9
10
11
12
# Source Generated with Decompyle++
# File: check.pyc (Python 3.11)

KEY = b'SUP3RS3CR3TK3Y'
CHECK = bytearray(b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\')

def transform(flag):
    return enumerate(flag)()


def check(flag):
Error decompyling check.pyc: vector::_M_range_check: __n (which is 2) >= this->size() (which is 2)

Using pycdas, we can manually examine the bytecode instructions. The function that validates the flag is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[Disassembly]
0       RESUME                        0
2       BUILD_LIST                    0
4       LOAD_FAST                     0: .0
6       FOR_ITER                      54 (to 116)
8       UNPACK_SEQUENCE               2
12      STORE_FAST                    1: i
14      STORE_FAST                    2: f
16      LOAD_FAST                     2: f
18      LOAD_CONST                    0: 24
20      BINARY_OP                     0 (+)
24      LOAD_CONST                    1: 255
26      BINARY_OP                     1 (&)
30      LOAD_GLOBAL                   0: KEY
42      LOAD_FAST                     1: i
44      LOAD_GLOBAL                   3: NULL + len
56      LOAD_GLOBAL                   0: KEY
68      PRECALL                       1
72      CALL                          1
82      BINARY_OP                     6 (%)
86      BINARY_SUBSCR                 
96      BINARY_OP                     12 (^)
100     LOAD_CONST                    2: 74
102     BINARY_OP                     10 (-)
106     LOAD_CONST                    1: 255
108     BINARY_OP                     1 (&)
112     LIST_APPEND                   2
114     JUMP_BACKWARD                 55
116     RETURN_VALUE                     

In summary, the function transforms each byte of the flag before comparing it with the corresponding value from the CHECK variable like this:

1
2
3
4
5
byte += 24
byte &= 255
byte ^= key[index % len(key)]
byte -= 74
byte &= 255

We can then write a small script to reverse this operation in order to obtain the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
KEY = b'SUP3RS3CR3TK3Y'
CHECK = bytearray(b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\')

for index, byte in enumerate(CHECK):
    byte += 74
    byte &= 255
    byte ^= KEY[index % len(KEY)]
    byte &= 255
    byte -= 24
    byte &= 255
    print(chr(byte), end='')
    
print()
1
2
$ python3 decode_spookycheck.py
HTB{mod3rn_pyth0n_byt3c0d3}
This post is licensed under CC BY 4.0 by the author.