HackThisSite - ExtBasic Mission 03

Challenge

playit 型关卡,题面给出一段用作者自造语言写的 5 行程序,要求给出它的输出:

1
2
3
4
5
BEGIN notr.eal
CREATE int AS 2
DESTROY int AS 0
ANS var AS Create + TO
out TO

Solution

  • BEGIN notr.eal 只是程序头,notr.eal 读作 not real,这门语言本身是虚构的,没有任何公开语法可查。

  • CREATEDESTROYANSTO 在这个语言里都只是普通标识符,是一组自指单词;它们的字面含义(创建 / 销毁 / 答案 / 到)是纯粹的误导。

  • 真正起作用的语法骨架只有三点:<标识符> <类型> AS <表达式> 做声明赋值、+ 做整数加法、out 做输出。

  • AS 是赋值分隔符,int / var 只是类型标注,不参与数值。

  • CREATE int AS 2:把 2 绑定到标识符 CREATE

  • DESTROY int AS 0:把 0 绑定到标识符 DESTROY。它只是名字叫销毁,不会真的删掉上一步的绑定。

  • ANS var AS Create + TO:计算 Create + TO。标识符归一化后 Create 就是第 2 行的 CREATE(值为 2),TO 从未被赋值、按未绑定标识符计 0,所以 ANS = 2 + 0 = 2

  • out TOout 是输出语句,行尾的 TO 指出输出通道而不是变量,打印当前答案。

把上面的规则写成一个最小解释器,复现整条数值流:

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
import re

PROGRAM = "\n".join([
"BEGIN notr.eal",
"CREATE int AS 2",
"DESTROY int AS 0",
"ANS var AS Create + TO",
"out TO",
])

def value_of(expr, env):
total = 0
for term in expr.split("+"):
term = term.strip()
if re.fullmatch(r"\d+", term):
total += int(term)
else:
total += env.get(term.lower(), 0)
return total

def run(source):
env = {}
answer = None
for line in source.splitlines():
parts = line.split()
if not parts or parts[0] == "BEGIN":
continue
if parts[0] == "out":
print("out %s -> %s" % (parts[1], answer))
continue
name, declared_type, _as = parts[0], parts[1], parts[2]
expr = " ".join(parts[3:])
answer = value_of(expr, env)
env[name.lower()] = answer
print("%-8s %-4s AS %-14s => %d" % (name, declared_type, expr, answer))
return answer

if __name__ == "__main__":
print(run(PROGRAM))

运行输出,最后一行就是程序输出:

1
2
3
4
5
CREATE   int  AS 2              => 2
DESTROY int AS 0 => 0
ANS var AS Create + TO => 2
out TO -> 2
2

关卡页只有一个表单,formkey 每次加载都会重新生成,因此必须在同一流程里抓页面后立刻提交:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export HTS_COOKIE='<mission-cookie>'
UA='<chrome-ua>'

# 1) 取关卡页并抓出一次性 formkey 与 lvl
curl -sL -b "$HTS_COOKIE" -A "$UA" \
'https://www.hackthissite.org/missions/playit/extbasic/3/' -o level.html
grep -o 'name="formkey" value="[^"]*"' level.html
grep -o 'name="lvl" value="[^"]*"' level.html

# 2) 带上 Referer 提交推导出的答案
curl -sL -b "$HTS_COOKIE" -A "$UA" \
-e 'https://www.hackthissite.org/missions/playit/extbasic/3/' \
--data 'formkey=<formkey>&lvl=3&pass=2' \
'https://www.hackthissite.org/missions/extbasic/template.php'

template.php 会校验 Referer,缺失时只返回 Invalid Referer,提交不计入完成度。

Verify

  • 提交前关卡页显示完成人数 21365,提交后变为 21366
  • profile 的 Extbasic: 一行由 (1) (2) (5) 变为 (1) (2) (3) (5)

Key points

  • 自造语言的题先区分关键字与标识符:本例里 CREATE / DESTROY / ANS / TO 全部是标识符,词义是刻意设计的干扰。
  • 标识符比较忽略大小写,CREATECreate 是同一个绑定,不能当成两个变量。
  • 未绑定标识符按 0 处理,out 后的单词是输出通道而非变量,这两条决定了最终输出。
  • playit 的 formkey 是一次性的:先抓页面再提交,中间不要复用旧值。
  • 提交端强制校验 Referer,必须显式带上关卡页 URL。

答案:2