|
| 1 | +import base64 |
| 2 | +import OpenSSL |
| 3 | +import os |
| 4 | +import time |
| 5 | +import fcntl |
| 6 | +import signal |
| 7 | +import tempfile |
| 8 | +import hashlib |
| 9 | +import atexit |
| 10 | +import subprocess |
| 11 | +from datetime import datetime |
| 12 | + |
| 13 | +tmp_path = "/dev/shm/hackergame" |
| 14 | +tmp_flag_path = "/dev/shm" |
| 15 | +conn_interval = int(os.environ["hackergame_conn_interval"]) |
| 16 | +token_timeout = int(os.environ["hackergame_token_timeout"]) |
| 17 | +challenge_timeout = int(os.environ["hackergame_challenge_timeout"]) |
| 18 | +pids_limit = int(os.environ["hackergame_pids_limit"]) |
| 19 | +mem_limit = os.environ["hackergame_mem_limit"] |
| 20 | +flag_path = os.environ["hackergame_flag_path"] |
| 21 | +flag_rule = os.environ["hackergame_flag_rule"] |
| 22 | +challenge_docker_name = os.environ["hackergame_challenge_docker_name"] |
| 23 | +readonly = int(os.environ.get("hackergame_readonly", "1")) |
| 24 | + |
| 25 | +with open("cert.pem") as f: |
| 26 | + cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read()) |
| 27 | + |
| 28 | + |
| 29 | +def validate(token): |
| 30 | + try: |
| 31 | + id, sig = token.split(":", 1) |
| 32 | + sig = base64.b64decode(sig, validate=True) |
| 33 | + OpenSSL.crypto.verify(cert, sig, id.encode(), "sha256") |
| 34 | + return id |
| 35 | + except Exception: |
| 36 | + return None |
| 37 | + |
| 38 | + |
| 39 | +def try_login(id): |
| 40 | + os.makedirs(tmp_path, mode=0o700, exist_ok=True) |
| 41 | + fd = os.open(os.path.join(tmp_path, id), os.O_CREAT | os.O_RDWR) |
| 42 | + fcntl.flock(fd, fcntl.LOCK_EX) |
| 43 | + with os.fdopen(fd, "r+") as f: |
| 44 | + data = f.read() |
| 45 | + now = int(time.time()) |
| 46 | + if data: |
| 47 | + last_login, balance = data.split() |
| 48 | + last_login = int(last_login) |
| 49 | + balance = int(balance) |
| 50 | + last_login_str = ( |
| 51 | + datetime.fromtimestamp(last_login).isoformat().replace("T", " ") |
| 52 | + ) |
| 53 | + balance += now - last_login |
| 54 | + if balance > conn_interval * 3: |
| 55 | + balance = conn_interval * 3 |
| 56 | + else: |
| 57 | + balance = conn_interval * 3 |
| 58 | + if conn_interval > balance: |
| 59 | + print( |
| 60 | + f"Player connection rate limit exceeded, please try again after {conn_interval-balance} seconds. " |
| 61 | + f"连接过于频繁,超出服务器限制,请等待 {conn_interval-balance} 秒后重试。" |
| 62 | + ) |
| 63 | + return False |
| 64 | + balance -= conn_interval |
| 65 | + f.seek(0) |
| 66 | + f.truncate() |
| 67 | + f.write(str(now) + " " + str(balance)) |
| 68 | + return True |
| 69 | + |
| 70 | + |
| 71 | +def check_token(): |
| 72 | + signal.alarm(token_timeout) |
| 73 | + token = input("Please input your token: ").strip() |
| 74 | + id = validate(token) |
| 75 | + if not id: |
| 76 | + print("Invalid token") |
| 77 | + exit(-1) |
| 78 | + if not try_login(id): |
| 79 | + exit(-1) |
| 80 | + signal.alarm(0) |
| 81 | + return token, id |
| 82 | + |
| 83 | + |
| 84 | +def generate_flags(token): |
| 85 | + functions = {} |
| 86 | + for method in "md5", "sha1", "sha256": |
| 87 | + |
| 88 | + def f(s, method=method): |
| 89 | + return getattr(hashlib, method)(s.encode()).hexdigest() |
| 90 | + |
| 91 | + functions[method] = f |
| 92 | + |
| 93 | + if flag_path: |
| 94 | + flag = eval(flag_rule, functions, {"token": token}) |
| 95 | + if isinstance(flag, tuple): |
| 96 | + return dict(zip(flag_path.split(","), flag)) |
| 97 | + else: |
| 98 | + return {flag_path: flag} |
| 99 | + else: |
| 100 | + return {} |
| 101 | + |
| 102 | + |
| 103 | +def generate_flag_files(flags): |
| 104 | + flag_files = {} |
| 105 | + for flag_path, flag in flags.items(): |
| 106 | + with tempfile.NamedTemporaryFile("w", delete=False, dir=tmp_flag_path) as f: |
| 107 | + f.write(flag + "\n") |
| 108 | + fn = f.name |
| 109 | + os.chmod(fn, 0o444) |
| 110 | + flag_files[flag_path] = fn |
| 111 | + return flag_files |
| 112 | + |
| 113 | + |
| 114 | +def cleanup(): |
| 115 | + if child_docker_id: |
| 116 | + subprocess.run( |
| 117 | + f"docker rm -f {child_docker_id}", |
| 118 | + shell=True, |
| 119 | + stdout=subprocess.DEVNULL, |
| 120 | + stderr=subprocess.DEVNULL, |
| 121 | + ) |
| 122 | + for file in flag_files.values(): |
| 123 | + os.unlink(file) |
| 124 | + |
| 125 | + |
| 126 | +def create_docker(flag_files, id): |
| 127 | + cmd = ( |
| 128 | + f"docker create --init --rm -i --network none " |
| 129 | + f"--pids-limit {pids_limit} -m {mem_limit} --memory-swap -1 --cpus 1 " |
| 130 | + f"-e hackergame_token=$hackergame_token " |
| 131 | + ) |
| 132 | + |
| 133 | + if readonly: |
| 134 | + cmd += "--read-only " |
| 135 | + |
| 136 | + if challenge_docker_name.endswith("_challenge"): |
| 137 | + name_prefix = challenge_docker_name[:-10] |
| 138 | + else: |
| 139 | + name_prefix = challenge_docker_name |
| 140 | + |
| 141 | + timestr = datetime.now().strftime("%m%d_%H%M%S_%f")[:-3] |
| 142 | + child_docker_name = f"{name_prefix}_u{id}_{timestr}" |
| 143 | + cmd += f'--name "{child_docker_name}" ' |
| 144 | + |
| 145 | + with open("/proc/self/cgroup") as f: |
| 146 | + for line in f: |
| 147 | + if "/docker/" in line: |
| 148 | + docker_id = line.strip()[-64:] |
| 149 | + break |
| 150 | + prefix = f"/var/lib/docker/containers/{docker_id}/mounts/shm/" |
| 151 | + |
| 152 | + for flag_path, fn in flag_files.items(): |
| 153 | + flag_src_path = prefix + fn.split("/")[-1] |
| 154 | + cmd += f"-v {flag_src_path}:{flag_path}:ro " |
| 155 | + |
| 156 | + cmd += challenge_docker_name |
| 157 | + |
| 158 | + return subprocess.check_output(cmd, shell=True).decode().strip() |
| 159 | + |
| 160 | + |
| 161 | +def run_docker(child_docker_id): |
| 162 | + cmd = f"timeout -s 9 {challenge_timeout} docker start -i {child_docker_id}" |
| 163 | + subprocess.run(cmd, shell=True) |
| 164 | + |
| 165 | + |
| 166 | +if __name__ == "__main__": |
| 167 | + child_docker_id = None |
| 168 | + flag_files = {} |
| 169 | + atexit.register(cleanup) |
| 170 | + |
| 171 | + token, id = check_token() |
| 172 | + os.environ["hackergame_token"] = token |
| 173 | + flags = generate_flags(token) |
| 174 | + flag_files = generate_flag_files(flags) |
| 175 | + child_docker_id = create_docker(flag_files, id) |
| 176 | + run_docker(child_docker_id) |
0 commit comments