-
Notifications
You must be signed in to change notification settings - Fork 0
/
user_wrapper
executable file
·312 lines (247 loc) · 9.74 KB
/
user_wrapper
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#! /usr/bin/env python3
import os
import sys
import re
import shutil
from collections import defaultdict
from pathlib import Path
from hashlib import sha256
##### CONSTANTS #####
SECRET = 'th1s_s3cr3t_pr0t3cts_th3_fl4gs'
ALIAS_RE = '^[a-z0-9_]{2,15}$'
ASURITE_RE = '^[a-z]{2,15}[0-9]{0,3}$'
ENROLLED = {
}
##### ASSIGNMENTS #####
class ChallengeSet:
def __init__(self, docker_name, binary_path_re, score_multiplier=1, docker_arguments=None, custom_challenges=True):
if not docker_arguments:
docker_arguments = []
self.docker_name = docker_name
self.binary_path_re = binary_path_re
self.score_multiplier = score_multiplier
self.docker_arguments = docker_arguments
self.custom_challenges = custom_challenges
def load(self):
global DOCKER_NAME
global BINARY_PATH_RE
global SCORE_MULTIPLIER
global DOCKER_ARGUMENTS
global CUSTOM_CHALLENGES
DOCKER_NAME = self.docker_name
BINARY_PATH_RE = self.binary_path_re
SCORE_MULTIPLIER = self.score_multiplier
DOCKER_ARGUMENTS = self.docker_arguments
CUSTOM_CHALLENGES = self.custom_challenges
HOMEWORKS = {
'hw1': ChallengeSet('hw1', '^/[a-zA-Z0-9/_\-\+\.]+$', 1, custom_challenges=False),
'hw2': ChallengeSet('hw2', '^/[a-zA-Z0-9/_\-\+\.]+$', 2, custom_challenges=False),
'hw3': ChallengeSet('hw3', '^/pwn/[a-z0-9_]+$', 3),
'hw4': ChallengeSet('hw4', '^/pwn/[a-z0-9_]+$', 4),
'hw5': ChallengeSet('hw5', '^/pwn/(babypwn|babykey)/[a-z0-9_]+$', 3),
'hw6': ChallengeSet('hw6', '^/pwn/(babyshell)/[a-z0-9_]+$', 4),
'hw7': ChallengeSet('hw7', '^/pwn/[a-z0-9_]+$', 2),
}
##### GLOBAL SESSION INFO #####
alias = None
asurite = None
##### USER CONTROLS #####
def login():
alias = input("Hacker Alias: ")
asurite = input("ASURITE: ")
if not re.match(ALIAS_RE, alias):
raise Exception(f"Hacker Alias must match: {ALIAS_RE}")
if not re.match(ASURITE_RE, asurite):
raise Exception(f"ASURITE must match: {ASURITE_RE}")
users = dict()
for path in Path.home().iterdir():
if path.is_dir() and ':' in path.name:
existing_alias, existing_asurite = path.name.split(':', 1)
users[existing_alias] = existing_asurite
current_asurite = users.get(alias)
if current_asurite == asurite:
return alias, asurite # User exists, successful login
elif alias not in users.keys() and asurite not in users.values():
user_path = Path.home() / f'{alias}:{asurite}'
user_path.mkdir()
(user_path / 'logs').mkdir()
(user_path / 'solves').mkdir()
if CUSTOM_CHALLENGES:
try:
challenge = list((Path.home() / 'challenges').iterdir())[0]
shutil.move(str(challenge), str(user_path / 'challenges'))
except:
raise Exception("Error: Yan's fault")
else:
(user_path / 'challenges').mkdir()
return alias, asurite # User successfully created
elif alias in users.keys():
raise Exception("Hacker Alias already registered!")
elif asurite in users.values():
raise Exception("ASURITE already registered!")
else:
raise Exception("???")
def show_scoreboard():
user_scores = {user_asurite[0]: (SCORE_MULTIPLIER * len(solves)) for user_asurite, solves in solves().items()}
user_grades = grades({user_asurite[0]: (SCORE_MULTIPLIER * len(solves)) for user_asurite, solves in solves().items() if user_asurite[1] in ENROLLED})
rank_length = max(len(str(len(user_scores))), len('Rank'))
alias_length = max(*(len(user) for user in user_scores), len('Hacker'))
score_length = max(*(len(str(user_scores[user])) for user in user_scores), len('Score'))
grade_length = max(*(len(str(user_grades[user])) for user in user_grades), len('Grade'), len('GUEST'))
print()
print("=" * 20 + " SCOREBOARD " + "=" * 20)
print(f"{'Rank':>{rank_length}} {'Hacker':<{alias_length}} {'Score':>{score_length}} {'Grade':>{grade_length}}")
for i, alias in enumerate(reversed(sorted(user_scores, key=lambda k: user_scores[k]))):
grade = user_grades.get(alias, "GUEST")
print(f"{i+1:>{rank_length}}. {alias:<{alias_length}} {user_scores[alias]:>{score_length}} {grade:>{grade_length}}")
print()
def solve_challenge():
challenge = challenge_path()
base_challenge = os.path.basename(challenge)
flag = challenge_flag(base_challenge)
# Kill any old docker container
filters = ['created', 'paused', 'exited', 'dead']
cmd = 'docker ps -aq ' + ' '.join(f'--filter "status={f}"' for f in filters) + f' --filter "name={DOCKER_NAME}_{alias}" | xargs -r docker rm'
os.system(cmd)
cmd = f'docker ps -q --filter "name={DOCKER_NAME}_{alias}" | xargs -r docker kill'
os.system(cmd)
challenges = str(Path.home() / f'{alias}:{asurite}' / 'challenges')
# Start new docker container
docker_arguments = [
('--name', f'{DOCKER_NAME}_{alias}'),
('--env', f'PASSWORD={asurite}'),
('--env', f'FLAG={flag}'),
('--env', f'BINARY_FILE={challenge}'),
('--mount', f'type=bind,source={challenges},destination=/challenges,readonly'),
('--mount', f'type=bind,source=/shared,destination=/shared,readonly'),
('--publish', '22000-25000:22'),
('--cap-add', 'SYS_PTRACE'),
('--cpus', '0.5'),
('--memory', '500m'),
('--memory-swap', '-1'),
('--pids-limit', '100'),
# ('--device-write-bps', '/dev/sda:10kb')
]
docker_arguments += DOCKER_ARGUMENTS
docker_arguments = ' '.join(f'{param} {value}' for param, value in docker_arguments)
os.system(f'docker run -td --rm {docker_arguments} {DOCKER_NAME}')
os.system(f'docker ps --filter="name={DOCKER_NAME}_{alias}"')
submitted_flag = input("Flag: ")
if submitted_flag == flag:
print("Correct Flag!")
(Path.home() / f'{alias}:{asurite}' / 'solves' / base_challenge).touch()
else:
print("Wrong Flag!")
def show_challenges():
challenges = Path.home() / f'{alias}:{asurite}' / 'challenges'
for challenge in challenges.glob('**/*'):
if challenge.is_file():
print(f"/pwn{str(challenge)[len(str(challenges)):]}")
##### UTILITY FUNCTIONS #####
def challenge_flag(challenge):
return 'CSE466{' + sha256(f'{SECRET}+{alias}+{challenge}'.encode()).hexdigest() + '}'
def challenge_path():
challenge = input("Path to Binary: ")
if not re.match(BINARY_PATH_RE, challenge):
raise Exception(f"Path to Binary must match: {BINARY_PATH_RE}")
return challenge
def solves():
result = dict()
for path in Path.home().iterdir():
if path.is_dir() and ':' in path.name:
current_alias, current_asurite = path.name.split(':', 1)
result[(current_alias, current_asurite)] = list((path / 'solves').iterdir())
return result
def grades(user_scores):
below_grades = {
user: score
for user, score in user_scores.items()
if score <= 70
}
above_grades = {
user: score
for user, score in user_scores.items()
if score > 70
}
above_score_users = defaultdict(list)
for user, score in above_grades.items():
above_score_users[score].append(user)
clusters = {
(score, score): above_score_users[score]
for score in sorted(above_score_users)
}
while len(clusters) > 40:
closest_low = None
closest_high = None
closest_distance = None
prev_cluster = None
for cluster in sorted(clusters.keys(), key=lambda k: k[0]):
if closest_low is None:
closest_low = cluster
elif closest_high is None:
closest_high = cluster
closest_distance = closest_high[0] - closest_low[1]
else:
current_distance = cluster[0] - prev_cluster[1]
if current_distance < closest_distance:
closest_low = prev_cluster
closest_high = cluster
closest_distance = current_distance
prev_cluster = cluster
new_cluster = (closest_low[0], closest_high[1])
new_cluster_data = [*clusters[closest_low], *clusters[closest_high]]
del clusters[closest_low]
del clusters[closest_high]
clusters[new_cluster] = new_cluster_data
above_grades = dict()
score = 110
for cluster in reversed(sorted(clusters.keys(), key=lambda k: k[0])):
cluster_data = clusters[cluster]
for user in cluster_data:
above_grades[user] = score
score -= 1
return {**below_grades, **above_grades}
##### MENU #####
def menu():
global alias
global asurite
alias, asurite = login()
print()
print()
if asurite in ENROLLED:
print("You are enrolled in the course! The scoreboard reflects your grade!")
else:
print("You are a guest user! You will not be graded!")
print()
print()
while True:
print("1. Show Scoreboard")
print("2. Solve Challenge")
if CUSTOM_CHALLENGES:
print("3. Show Challenges")
print("0. Quit")
try:
choice = int(input("Choice: "))
except ValueError:
choice = 0
if choice == 1:
show_scoreboard()
elif choice == 2:
solve_challenge()
elif CUSTOM_CHALLENGES and choice == 3:
show_challenges()
elif choice == 0:
return
else:
print("Invalid Choice!")
print()
print()
def main():
hw = sys.argv[1]
HOMEWORKS[hw].load()
menu()
if __name__ == '__main__':
try:
main()
except Exception as e:
print(str(e))