SpookyCheck
Challenge
- CTF: Hack The Boo 2023
- Name: SpookyCheck
- Category: Reversing
- Difficulty: Medium
- Points: 350
- Description: My new tool will check if your password is spooky enough for use during Halloween - but watch out for snakes…
- Objective: Python .pyc Reversing
Files
Download: rev_spookycheck.zip
Writeup
The challenge file of check.pyc was identified as a byte-compiled Python file after analyzing it using the file utility.
file check.pyc
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
Analyzing the binary using strings, we can identify a SUP3RS3CR3TK3Y:
strings check.pyc
SUP3RS3CR3TK3Ys
len)
check.py
<listcomp>z
transform.<locals>.<listcomp>
enumerate
flags
transformr
CHECKr
checkr
__main__u
Welcome to SpookyCheck
Enter your password for spooky evaluation
Well done, you're spookier than most!
Not spooky enough, please try again later
bytearrayr
__name__
print
input
encode
<module>r
We can do dynamic analysis by importing the library and seeing what type of functions/variables are available in check.pyc.
python3
>>> import check
>>> print(check.CHECK)
bytearray(b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\')
>>> print(check.KEY)
b'SUP3RS3CR3TK3Y'
This file can be attempted to be decompiled to source-code using pycdc of Python Decompyle++. Unfortunately, it isn’t fully supported for Python 3.11.
pycdc check.pyc
# 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)
This file can be attempted to be disassembled to byte-code using pycdas of Python Decompyle++. This was successful.
pycdas check.pyc
check.pyc (Python 3.11)
[Code]
File Name: check.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 4
Flags: 0x00000000
[Names]
'KEY'
'bytearray'
'CHECK'
'transform'
'check'
'__name__'
'print'
'input'
'inp'
'encode'
[Locals+Names]
[Constants]
b'SUP3RS3CR3TK3Y'
b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\'
[Code]
File Name: check.py
Object Name: transform
Qualified Name: transform
Arg Count: 1
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 4
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'enumerate'
[Locals+Names]
'flag'
[Constants]
None
[Code]
File Name: check.py
Object Name: <listcomp>
Qualified Name: transform.<locals>.<listcomp>
Arg Count: 1
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 8
Flags: 0x00000013 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NESTED)
[Names]
'KEY'
'len'
[Locals+Names]
'.0'
'i'
'f'
[Constants]
24
255
74
[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
[Disassembly]
0 RESUME 0
2 LOAD_CONST 1: <CODE> <listcomp>
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 1: NULL + enumerate
18 LOAD_FAST 0: flag
20 PRECALL 1
24 CALL 1
34 GET_ITER
36 PRECALL 0
40 CALL 0
50 RETURN_VALUE
[Code]
File Name: check.py
Object Name: check
Qualified Name: check
Arg Count: 1
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 3
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'transform'
'CHECK'
[Locals+Names]
'flag'
[Constants]
None
[Disassembly]
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + transform
14 LOAD_FAST 0: flag
16 PRECALL 1
20 CALL 1
30 LOAD_GLOBAL 2: CHECK
42 COMPARE_OP 2 (==)
48 RETURN_VALUE
'__main__'
'🎃 Welcome to SpookyCheck 🎃'
'🎃 Enter your password for spooky evaluation 🎃'
'👻 '
"🦇 Well done, you're spookier than most! 🦇"
'💀 Not spooky enough, please try again later 💀'
None
[Disassembly]
0 RESUME 0
2 LOAD_CONST 0: b'SUP3RS3CR3TK3Y'
4 STORE_NAME 0: KEY
6 PUSH_NULL
8 LOAD_NAME 1: bytearray
10 LOAD_CONST 1: b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\'
12 PRECALL 1
16 CALL 1
26 STORE_NAME 2: CHECK
28 LOAD_CONST 2: <CODE> transform
30 MAKE_FUNCTION 0
32 STORE_NAME 3: transform
34 LOAD_CONST 3: <CODE> check
36 MAKE_FUNCTION 0
38 STORE_NAME 4: check
40 LOAD_NAME 5: __name__
42 LOAD_CONST 4: '__main__'
44 COMPARE_OP 2 (==)
50 POP_JUMP_FORWARD_IF_FALSE 88 (to 228)
52 PUSH_NULL
54 LOAD_NAME 6: print
56 LOAD_CONST 5: '🎃 Welcome to SpookyCheck 🎃'
58 PRECALL 1
62 CALL 1
72 POP_TOP
74 PUSH_NULL
76 LOAD_NAME 6: print
78 LOAD_CONST 6: '🎃 Enter your password for spooky evaluation 🎃'
80 PRECALL 1
84 CALL 1
94 POP_TOP
96 PUSH_NULL
98 LOAD_NAME 7: input
100 LOAD_CONST 7: '👻 '
102 PRECALL 1
106 CALL 1
116 STORE_NAME 8: inp
118 PUSH_NULL
120 LOAD_NAME 4: check
122 LOAD_NAME 8: inp
124 LOAD_METHOD 9: encode
146 PRECALL 0
150 CALL 0
160 PRECALL 1
164 CALL 1
174 POP_JUMP_FORWARD_IF_FALSE 13 (to 202)
176 PUSH_NULL
178 LOAD_NAME 6: print
180 LOAD_CONST 8: "🦇 Well done, you're spookier than most! 🦇"
182 PRECALL 1
186 CALL 1
196 POP_TOP
198 LOAD_CONST 10: None
200 RETURN_VALUE
202 PUSH_NULL
204 LOAD_NAME 6: print
206 LOAD_CONST 9: '💀 Not spooky enough, please try again later 💀'
208 PRECALL 1
212 CALL 1
222 POP_TOP
224 LOAD_CONST 10: None
226 RETURN_VALUE
228 LOAD_CONST 10: None
230 RETURN_VALUE
We can then generate the Python source-code from the byte-code:
KEY = b"SUP3RS3CR3TK3Y"
CHECK = b"\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\"
def transform(flag):
result = []
for i, f in enumerate(flag):
value = (f + 24) & 255
value ^= KEY[i % len(KEY)]
value -= 74
value &= 255
result.append(value)
return result
def check(flag):
return transform(flag) == CHECK
if __name__ == "__main__":
print("🎃 Welcome to SpookyCheck 🎃")
print("🎃 Enter your password for spooky evaluation 🎃")
inp = input("👻 ")
if check(inp.encode()):
print("🦇 Well done, you're spookier than most! 🦇")
else:
print("💀 Not spooky enough, please try again later 💀")
The following script was then used to reverse the flag that basically does everything in reverse-order to the original transform() function.
KEY = b"SUP3RS3CR3TK3Y"
CHECK = b"\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\"
def reverse_transform(result):
reversed_flag = []
for i, value in enumerate(result):
value += 74
value &= 255
value ^= KEY[i % len(KEY)]
value -= 24
value &= 255
reversed_flag.append(value)
return bytes(reversed_flag)
# Reverse the flag using the transform function
reversed_flag = bytes(reverse_transform(CHECK))
print(reversed_flag.decode('utf-8'))
python3 check_rev.py
HTB{mod3rn_pyth0n_byt3c0d3}
Flag: HTB{mod3rn_pyth0n_byt3c0d3}