HackThisSite - Programming Mission 11

Challenge

Level 11 — Reverse Ascii Shift

页面给出一串随机生成的 % 分隔的 ASCII 码和一个 Shift 值。把每个码按 Shift 反向还原,得到的字符串就是答案(Decoded ASCII)。限时 3 秒

This string was randomly generated. It will not be recognizable text. You have 3 seconds to take the information from the website, and apply that to your algorithm.

Solution

  • 实例页 https://www.hackthissite.org/missions/prog/11/ 的正文里有三行:Generated String: 89%71%64%55%42%89%84%37%57%Shift: 3Decoded ASCII
  • 表单只有一个 solution 字段,submit (remaining time: 3 seconds)
1
2
3
4
5
6
Level 11
This string was randomly generated. It will not be recognizable text. You have 3 seconds to take the
information from the website, and apply that to your algorithm.
Generated String: 89%71%64%55%42%89%84%37%57%
Shift: 3
Decoded ASCII

% 只是分隔符,每个数字是一个被平移过的字符码。题面用词是 reverse(反向还原),所以先尝试 chr(code - shift);实测该方向被服务端直接接受:

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
#!/usr/bin/env python3
"""Solve HackThisSite Programming Mission 11."""

import html
import os
import re

import requests

BASE = "https://www.hackthissite.org"
LEVEL_URL = f"{BASE}/missions/prog/11/"
USER_AGENT = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
)


def make_session():
cookie = os.environ.get("HTS_COOKIE", "").strip().strip("'\"")
if not cookie:
raise SystemExit("HTS_COOKIE is not set")
client = requests.Session()
client.headers.update(
{
"User-Agent": USER_AGENT,
"Cookie": cookie,
"Referer": f"{BASE}/missions/programming/",
"Accept-Language": "en-US,en;q=0.9",
}
)
return client


def body_text(source):
source = re.sub(r"<script.*?</script>", "", source, flags=re.S)
source = re.sub(r"<br\s*/?>", "\n", source)
source = re.sub(r"</(?:p|div|tr)>", "\n", source)
source = re.sub(r"<[^>]+>", " ", source)
source = html.unescape(source)
return re.sub(r"\n\s*\n+", "\n", re.sub(r"[ \t]+", " ", source))


def parse(page_text):
section = page_text.split("Generated String:", 1)[1].split("Shift:", 1)[0]
codes = [int(value) for value in re.findall(r"\d+", section)]
match = re.search(r"Shift:\s*(\d+)", page_text)
if match is None:
raise SystemExit("shift value not found")
return codes, int(match.group(1))


def decode(codes, shift, direction):
return "".join(chr(code + direction * shift) for code in codes)


def verdict(response_text):
text = body_text(response_text).lower()
success = any(
marker in text
for marker in (
"congratulation",
"you have completed",
"completed this",
"successfully",
"correct",
"well done",
"mission accomplished",
"complete!",
)
)
failure = any(
marker in text
for marker in (
"wrong",
"incorrect",
"not correct",
"try again",
"failed",
"too late",
"time is up",
"sorry",
)
)
if success and not failure:
return True
if failure and not success:
return False
return None


def submit(client, answer):
response = client.post(
f"{LEVEL_URL}index.php",
data={"solution": answer, "submitbutton": "submit"},
headers={"Referer": LEVEL_URL},
timeout=30,
)
response.raise_for_status()
return verdict(response.text), response.text


def main():
client = make_session()
page = client.get(LEVEL_URL, timeout=20)
page.raise_for_status()
codes, shift = parse(body_text(page.text))
for direction in (-1, 1):
answer = decode(codes, shift, direction)
print(f"try shift {direction * shift:+d} -> {answer!r}")
ok, _ = submit(client, answer)
print("verdict:", ok)
if ok:
return
if direction == -1:
page = client.get(LEVEL_URL, timeout=20)
page.raise_for_status()
codes, shift = parse(body_text(page.text))
print("retry codes:", codes, "shift", shift)
raise SystemExit("both directions rejected")


if __name__ == "__main__":
main()