跳转至

*CTF Dead Game 题解 & 代码分析

星际玩家最后的倔强x By 国服第一探姬 (探姬#51276)
现在已经成亚服难民了x 探姬#31410

感觉本题作者预期的解应该是通关,不过通过银河编辑器解题也不算非预期,毕竟不难,难的是如今找到如今还在打星际的CTFer,并且他一定意义上熟悉银河编辑器。因为SC2兵种繁多,一个技能也会绑定很多演算体 触发器 脚本,因此让新手直接上手似乎不太可能,不过我还是打算讲讲怎么用银河编辑器直接算,说实话这很有意思ww(好吧其实是曾经有过一些优质mod和图产出却没拿到一血的mapmaker最后的尊严QaQ!

题面

通关法

还是需要银河编辑器,打开后点绿色的测试

虽然题目有防作弊检测,但是也就检测了泽叔的生命和护盾,以及玩家单位是否只有泽叔一个。

所以用作弊码就好了。

通关推荐使用这三个,追求速度可以开技能无冷却,老玩家开个无敌就够了,开图大可不必 www

god #上帝模式
hanshotfirst # 技能快速冷却
TookTheRedPill #开图 消除战争迷雾

然后跟着关卡走就行了。

银河编辑器解法【静态】

在后面写文的复现中 发现纯银河编辑器好像很难做到直接手撕 这个地方大家看个乐子

写这个方法是因为 做题的时候突然会想起自己之前做图和做mod然后拿自己做的图或者mod的后门调戏群友的快乐时光,不过遗憾的是后来大家都不打星际了,再加上现在国服无了,人也越来越少了(悲

任何一张星际争霸2游戏地图比较核心的地方就是触发器,而让每个地图作者头疼的也是触发器x

我们边讲边介绍x 问题不大

Map文件我们直接用银河编辑器打开即可。

图圈常说 买银河编辑器送星际争霸2 就得益于银河编辑器基于的GE是一种强大的游戏引擎 你甚至能用他播放视频(

很乱,很正常,要素比较多,不过不必担心,你只需要认识几个我们题解需要用到的几个部件:

分别对应 触发器 单位数据 文本信息

和大部分MISC题目一样,从文本入手是一个不错的解决方法,所以我们可以先尝试在Text选项中搜索flag或者ctf字样:

其实大概就知道要干什么了,找三块碎片(其实就是碰瓷末日预言关卡泽叔去三个萨尔纳加神殿收集预言碎片x) 才能获得flag。

但其实这里直接只看flag相关的文本有个坏处——容易被误导x

除了最后一块碎片可以人眼看出以外,其实其他碎片的外部指向都不可用,都只能依靠游戏内部要素拿到。

最后一块碎片 81!zZ@Rd —— 也就是Blizzard(暴雪)的CTF式写法ww

所以第一块和第二块碎片,显然是要依靠游戏内部运算的,所以这个时候,触发器就要登场了。

你可以根据text结果精确定位——很明显Flag有三个值,分别是:

  • flag_part1
  • flag_part2
  • flag_part3

当然,也可以直接用Find 找flag 全部Fuzz一遍:

可以看到在通关后的任务结束界面会显示解析到的flag——所以通关游戏也是不错的选择。

不过都到这了,我们不妨看看每个flag的part都是怎么计算的。

  • flag_part1

第一部分的表达式其实有点劝退的,主要是长——人有时候遇到长的东西,会下意识回避,不过不妨静下心来,慢慢看x

Variable -Set flag_part1 = (String({((Zeratul [162.87, 28.62] Maximum Life (Current))+(Zeratul [162.87, 28.62] Maximum Shields (Current)))*(Zeratul [162.87, 28.62] Maximum Shields (Current))*(Zeratul [162.87, 28.62] Shields Regeneration Rate (Current))+((Zeratul [162.87, 28.62] Maximum Life (Current))+(Zeratul [162.87, 28.62] Maximum Shields (Current)))*(Zeratul [162.87, 28.62] Shields Regeneration Rate (Current))+((Zeratul [162.87, 28.62] Movement Speed Max (Current))+(Zeratul [162.87, 28.62] Life Regeneration Rate (Current)))*(Zeratul [162.87, 28.62] Shields Regeneration Rate (Current))*10+2*(Zeratul [162.87, 28.62] Shields Regeneration Rate (Current))+(Zeratul [162.87, 28.62] Movement Speed Max (Current))+(Zeratul [162.87, 28.62] Life Regeneration Rate (Current))}))

其实把数据填入后就是小学加减乘除,而问题就在于,又臭又长的表达式让人走不到这一步,上面涉及到一些单位的数据 如生命值 护盾值,这个时候我们就要去 单位数据 Data里面去寻找了。

我们先整理一下,我们需要什么数据:

数据名称 数量 代换
Zeratul [162.87, 28.62] Maximum Life (Current) 2 记作 A
Zeratul [162.87, 28.62] Maximum Shields (Current) 3 记作 B
Zeratul [162.87, 28.62] Shields Regeneration Rate (Current) 4 记作 C
Zeratul [162.87, 28.62] Movement Speed Max (Current) 2 记作 D
Zeratul [162.87, 28.62] Life Regeneration Rate (Current) 2 记作 E

可以看到全部都是泽拉图这个单位的数据 包括 生命值 护盾值 护盾恢复速度 最大移动速度 生命回复速度

得到简化表达式:

Variable -Set flag_part1 = (String({((A)+(B))*(B)*(C)+((A)+(B))*(C)+((D)+(E))*(C)*10+2*(C)+(D)+(E)}))

要算的值也很简单:

((A)+(B))*(B)*(C)+((A)+(B))*(C)+((D)+(E))*(C)*10+2*(C)+(D)+(E)

接下来只需要去Data里面找泽叔的数据:

使用搜索功能即可:

要注意的是,星际2中不同场景的单位,单位名字虽然相同,但是背后的ID不是相同的 会附带单位的一些要素 所以要完全对应触发器的单位 一个字不差,这里触发器使用的是 Zeratul 的数据。

数据名称
Zeratul [162.87, 28.62] Maximum Life (Current) 300
Zeratul [162.87, 28.62] Maximum Shields (Current) 100
Zeratul [162.87, 28.62] Shields Regeneration Rate (Current) 2
Zeratul [162.87, 28.62] Movement Speed Max (Current) 3
Zeratul [162.87, 28.62] Life Regeneration Rate (Current) 0
```

((300)+(100))(100)(2)+((300)+(100))(2)+((3)+(0))(2)10+2(2)+(3)+(0) = 80867 ```

看吧,我就说小学加减乘除啦~

所以 flag_part1 的值我们就得到了 80867

注意:

因为作者没有使用行为升级的方式去更改默认数据,所以静态解析直接算值是可行的。

不过严格意义来讲 因为触发器使用的 (Current) 值 所以这样计算其实是不严谨的,因为我随时可以在游戏中动态的去调整生命值。

以及这里出现了一个小插曲 我把 Zeratul [162.87, 28.62] Movement Speed Max (Current)的值看成2了(所以还对着虚空找了老久的bug。 但在截图中也可以看出 Zeratul [162.87, 28.62] Movement Speed Max (Current) 的值就是 3,而后续也没有任何行为的影响。 也就是说,开局的表达式((A)+(B))(B)(C)+((A)+(B))(C)+((D)+(E))(C)10+2(C)+(D)+(E) 带入后是 ((300)+(100))(100)(2)+((300)+(100))(2)+((3)+(0))(2)10+2(2)+(3)+(0) = 80867 没有任何问题 而在测试中 Zeratul [162.87, 28.62] Life Regeneration Rate (Current) 的表现形式(即单位生命的回复)确实是 0 所以编辑器静态环境完全能够做出,对数据错误表示抱歉。 欢迎大家一起来玩星际呀~

  • flag_part2

同样我们用查找功能去找这个变量的触发器:

可以看到flag2的值有如下变化:

Variable -Set flag_part2 = {flag_part2"K"}
Variable -Set flag_part2 = {flag_part2(String((Zeratul count for player ijqvaelrggonccpy, counting Queued Or Better)))}       Variable -Set flag_part2 = {flag_part2(String({(Zeratul [162.87, 28.62] Maximum Life (Current))/300}))}
Variable -Set flag_part2 = {flag_part2(String({(Zeratul [162.87, 28.62] Maximum Shields (Current))/100}))}
Variable -Set flag_part2 = {flag_part2(String({((Zeratul [162.87, 28.62] Maximum Life (Current))+(Zeratul [162.87, 28.62] Maximum Shields (Current)))/80}))}

而每一个变化对应一个独立的触发器,所以我在前面有说到,记得注意通关顺序节奏,得跟着任务目标走,不然flag就错了x

结合我们之前拿到的数据,对应的值不难求出,唯一特殊的:

{flag_part2"K"}给flag_2添加字符K,这里是当追猎喊出 " For Shakuras! " (为了夏古拉斯)的时候触发,不过作者很鸡贼用爆蚊把追猎爆了,所以这波属于是棺材板呐喊了!

String((Zeratul count for player ijqvaelrggonccpy, counting Queued Or Better))应该是玩家泽叔的个数,在遇到巢虫领主时触发,为1。

这两稍不注意 1和K就互换了 我当时卡在这卡了老久,因为通关节奏太快了()导致flag一直不对nnd

然后后面就是计算了,

  • (Zeratul [162.87, 28.62] Maximum Life (Current))/300})) = 1
  • (Zeratul [162.87, 28.62] Maximum Shields (Current))/100})) = 1
  • ((Zeratul [162.87, 28.62] Maximum Life (Current))+(Zeratul [162.87, 28.62] Maximum Shields (Current)))/80 = 400 / 80 =5

所以第二部分到没问题(

flag_part2 的值为 K_1115

  • flag_part3

其实在看Text能看出个大概,如果你是忠诚的暴黑,那么这个字符就是送分的玻璃渣。

当然如果你不是,我们依旧可以通过触发器去算()

提取出来内容如下:

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Xel'Naga Shrine [166.56, 111.52] Life (Current))%71}, {(Xel'Naga Shrine [166.56, 111.52] Life (Current))%71}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Food of a Thousand Feasts [179.44, 9.73] Life (Current))+1}, {(Food of a Thousand Feasts [179.44, 9.73] Life (Current))+1}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Super Warp Gate [181.00, 10.00] Life (Current))/10+13}, {(Super Warp Gate [181.00, 10.00] Life (Current))/10+13}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)*((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)}, {((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)*((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Void Seeker [69.01, 133.48] Life (Current))%69}, {(Void Seeker [69.01, 133.48] Life (Current))%69}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Zeratul [162.87, 28.62] Maximum Life (Current))/2-66}, {(Zeratul [162.87, 28.62] Maximum Life (Current))/2-66}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Zeratul [162.87, 28.62] Maximum Shields (Current))/2+4}, {(Zeratul [162.87, 28.62] Maximum Shields (Current))/2+4}))}

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Square root((Beacon (Protoss Large) [22.82, 96.07] Life (Current))))+9}, {(Square root((Beacon (Protoss Large) [22.82, 96.07] Life (Current))))+9}))}

看着很吓人,其实确实很吓人(

利用 Substring 函数 直接从 table里面取值,所以看着表达式长 但前后都一样

以及最后的 Square root 平方根函数

记得我们的table么:

```

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[]^_`{|}~ ```

```json

{ "1": "0", "2": "1", "3": "2", "4": "3", "5": "4", "6": "5", "7": "6", "8": "7", "9": "8", "10": "9", "11": "a", "12": "b", "13": "c", "14": "d", "15": "e", "16": "f", "17": "g", "18": "h", "19": "i", "20": "j", "21": "k", "22": "l", "23": "m", "24": "n", "25": "o", "26": "p", "27": "q", "28": "r", "29": "s", "30": "t", "31": "u", "32": "v", "33": "w", "34": "x", "35": "y", "36": "z", "37": "A", "38": "B", "39": "C", "40": "D", "41": "E", "42": "F", "43": "G", "44": "H", "45": "I", "46": "J", "47": "K", "48": "L", "49": "M", "50": "N", "51": "O", "52": "P", "53": "Q", "54": "R", "55": "S", "56": "T", "57": "U", "58": "V", "59": "W", "60": "X", "61": "Y", "62": "Z", "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": "~" }

```

然后我们再去数据里找萨尔那加神殿(Xel'Naga Shrine)等缺失的数据:

数据名称
Xel'Naga Shrine [166.56, 111.52] Life (Current) 1500
Food of a Thousand Feasts [179.44, 9.73] Life (Current) 1
Super Warp Gate [181.00, 10.00] Life (Current) 500
Zeratul [162.87, 28.62] Movement Speed Max (Current) 3
Void Seeker [69.01, 133.48] Life (Current) 200
Zeratul [162.87, 28.62] Maximum Life (Current) 300
Zeratul [162.87, 28.62] Maximum Shields (Current) 100
Beacon (Protoss Large) 25

所以转换出来:

(Substring(table, {(Xel'Naga Shrine [166.56, 111.52] Life (Current))%71}, {(Xel'Naga Shrine [166.56, 111.52] Life (Current))%71})) = 9 ——"8"

(Substring(table, {(Food of a Thousand Feasts [179.44, 9.73] Life (Current))+1}, {(Food of a Thousand Feasts [179.44, 9.73] Life (Current))+1})) = 2 ——"1"

(Substring(table, {(Super Warp Gate [181.00, 10.00] Life (Current))/10+13}, {(Super Warp Gate [181.00, 10.00] Life (Current))/10+13})) = 63 ——"!"

(Substring(table, {((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)*((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)}, {((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)*((Zeratul [162.87, 28.62] Movement Speed Max (Current))+3)})) = 36 ——"z"

(Substring(table, {(Void Seeker [69.01, 133.48] Life (Current))%69}, {(Void Seeker [69.01, 133.48] Life (Current))%69})) = 62 ——"Z"

(Substring(table, {(Zeratul [162.87, 28.62] Maximum Life (Current))/2-66}, {(Zeratul [162.87, 28.62] Maximum Life (Current))/2-66})) = 84 ——"@"

(Substring(table, {(Zeratul [162.87, 28.62] Maximum Shields (Current))/2+4}, {(Zeratul [162.87, 28.62] Maximum Shields (Current))/2+4})) = 54 ——"R"

(Substring(table, {(Square root((Beacon (Protoss Large) [22.82, 96.07] Life (Current))))+9}, {(Square root((Beacon (Protoss Large) [22.82, 96.07] Life (Current))))+9})) = (5+9) ——"d"

得到第三部分的flag 81!zZ@Rd

最后拼接即可:*CTF{80867_K1115_81!zZ@Rd}

彩蛋

为什么换附件x

在第一次放题的时候,对最后一个字母的计算 出题人用的是

Variable -Set flag_part3 = {flag_part3(Substring(table, {(Square root((Beacon (Protoss Large) [22.82, 96.07] Life (Current))))+9}, {(Square root((Baneling (Burrowed) [30.75, 26.80] Life (Current))))+9}))}

emm 信标生命到毒爆虫,看样子还是对25气矿恋恋不忘呢x??

不过,最后生成的字符是8,emm 神奇的编辑器 ()

评论