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.
1
2
$ 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
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ 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 see what type of functions/variables are available in check.pyc
.
1
2
3
4
5
6
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
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.
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
$ 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:
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
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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'))
1
2
$ python3 check_rev.py
HTB{mod3rn_pyth0n_byt3c0d3}
Flag: HTB{mod3rn_pyth0n_byt3c0d3}