0%

mssCTF 2020 赛后总结

这真的是我打过最惊险,最一波三折的一场比赛,尤其是决赛的最后十分钟,简直是我打过比赛中最紧张刺激的最后时刻。。

由于这次比赛中,组委会要求我们全程录屏,所以我才能在比赛过去一周后复盘,写下这篇心得。。

mssCTF 2019

先说下去年的情况吧。

去年我初赛排名大概80+,没有入围决赛。但学校有一个推荐名额,我就去西安参加了决赛。

决赛上午一道题也没有写出来,并且坐在我旁边的刘星宇同学上午直接AKPPC(做出了所有的编程题),冲到了Rank1。

好在下午做出来了两道PPC,之后从Rank16-掉到Rank19。。(记住这个19名,后面要考)

而第16名正好也是1000分,只是比我做得快了一点,也就是说,我只需要再做一道题,就可以把他挤下去,拿到三等奖(前16名就有奖)。

再说说今年和去年的不同吧。

今年的决赛在线上举行,使用双机位+录屏来监考,全程不允许连接外网。而上次比赛直接在体育场里用局域网,外面还放了信号屏蔽器,两位选手就有一位志愿者监考。去年的比赛是可以申请去用组委会提供的连接外网的机器的,只是时间有限制(貌似是30min)。

这次的比赛分为上下两个半场,从9点到17点,中午2个小时的休息时间,把上下午两个阶段分开,为了避免大家作弊,下午有新的题,不能继续做上午的题。

去年的比赛真的挺遗憾的,可能是我当时还不知道我离三等奖那么进,也可能是我看到所有的题目我都不会,我在比赛结束前1小时就基本放弃了,也没有过多关注排名,最后只拿了个「优秀奖」,和三等奖和5k元奖金“交臂失之”。

初赛

初赛实在9月13日举行的,共有200多名选手参加,据说还有11岁的选手。从上午9点到晚上6点一共9个小时。

上午,开赛一分钟后我拿到了 PPC-量子波动速读 一题的一血(第一个做出来的),然后非常出乎意料地拿到了短暂的榜一。之后我有写了两道PPC题,但都没有通过。

下午去学校写题,用binwalk把签到题解决了,然后一直在搞PPC。

在离比赛结束还有1小时时,我看到我的排名是30+,可能进不了决赛,之后我又用C++重新写了一遍那道PPC(之前是用Python写的)。最终在5:40,也就是离比赛结束还有十几分钟的时候A掉了这道题,成功挤进了前30。

我本来以为这已经够刺激的了,没想到后面还有更刺激的。。。

决赛前

决赛前一天,也就是周五(9月18日),我早早回到了家参加设备调试。调试过程中,我的设备非常完美地运行着。调试结束后,我非常手残地更新了一下系统,然后就出事了。。

系统中的桌面和顶部菜单栏都出了问题,启动失败。我的电脑自今年2月份重装系统以来,一直没有出过啥大问题,然而到了这关键时刻出锅。。还好除了某些操作稍微有些麻烦,所有其他软件和功能都没有受到影响,算是不幸中的万幸了吧。虽然不会影响到我第二天的比赛,但对我当时的心态是个很大的打击。

顺便说下,后面录屏上没有时间,就是因为这个组件坏了。

决赛上午

8点开始,选手们就陆续进入会议室,到比赛开始时,我们组里所有选手的设备都已经调试完毕。

比赛开始后,我发现平台上只放了一道PPC(也就是传统编程,是我最擅长的领域)题,这可怎么打。。

我和其他选手陆陆续续地A掉了这道题,之后我的排名为14,还是做得太晚了呀。之后,我并没有在上午解出新的题目,排名一路从最初的14掉到了19。

1.jpg

下午

下午的比赛非常有意思,我把它分成了几个不同的阶段,每个小标题的时间是比赛开始后经过的时间,也就是我录屏的持续时间,如40:00指的是比赛开始后40分钟。由于我们要求提前开录屏,录屏的时间会比比赛持续时间长2分钟,这对最后争分夺秒的时刻非常重要,我也懒得剪了,各位自己计算真实时间吧。

00:00–24:00 消失的PPC

下午开始时,放了两道PPC题,按照我的策略,先把题都看一遍再说。

“安全评估”那题,我一看是道图论,我就直接放弃,反而是第二道题“砍怪”看起来相当简单。

《砍怪》

我用大概15分钟打完了一个暴力,由于题面过于简单,我并没有再去看题,等到我写完之后,我才发现:这题没了?!

没错,这道题在我写完之后莫名其妙消失了。可能只是组委会不小心多放了一道题,但却浪费了我的20分钟。

上午,我在比赛开始后5分钟才开始看PPC题(之前在看Crypto),结束之后,我认为如果早点开始写的话名次还能上升。。结果下午组委会就出了个大锅,把我的心态搞崩了。。

运维出来挨打(

当时,那道最简单的 Crypto 已经有几个人做出来了,我如果坚持上午的策略,先看 Crypto ,再看 PPC ,也许可以比其他人快一些。

27:50–39:48 送分题&签到题

在心态受到打击,之后又看了不到四分钟另一道PPC后,我决定暂时搁置PPC,去写其他题。

我瞄准两道做的人最多的 Crypto 和 Reverse ,当时比赛开始了28分钟,到比赛开始40分钟时,我已经完成了这两道题,冲到了排行榜第7名。

Crypto-easy_encrypt

easy_encrypt整场比赛中做出来人数第二多的题,也是最简单的一道 Crypto。

稍有密码学经验的同学,看到下面的输出msYs__s1fc_1tHnf@C{pe1p}T里包含括号、下划线、和分散的 mssctf 字样,就一定知道这是个栅栏加密。

我先是解读了一下这个脚本,发现这个加密只改变了每个字符的位置,字符的集合是不变的。

于是我把脚本下载下来,用abcdefghijklmnopqrstuvwxy这个长度为25的字符串替代长度为25的flag加密,之后看每个字符移动到的位置,就可以通过密文反推出明文:

Screenshot_20200926_110731.jpg

本来想写脚本,后来懒得写了干脆手算

Reverse-Hello

Hello是比赛中通过最多的题,是一道Reverse,40位选手中,共有32人通过了这道题,80%的通过率,想想就相当可怕。

其实只要有一点计算机常识,就可以做出来这道送分题,简直是“有手就行”。甚至在我看来,比赛结束后计算md5值都比这个技术含量高。

直接下载附件,用记事本打开(我这里用的是linux上的Kate),就可以看到flag。

Screenshot_20200926_111437.jpg

当然,Windows下可能会用编码错误导致无法查找flag,没关系,把它放到IDA中也可以一眼看见flag。

这道题简单到了什么程度,我上张图的时间为38:45,这张图的时间为39:40。也就是说,我在1分钟内提交了两个flag。

去年、今年、初赛、复赛。我从来没有在比赛中做过这种明文保存 flag 的 Reverse 题,这次这道题真的是一点难度都没有。

39:48–1:30:00 看题+摸鱼

做完这两道题后,我处在第7名的位置,再看时间还多,也没啥做题的动力,于是各种发呆和看题。

在这不到1小时的时间里,我把所有题看了个遍,能下载的附件都下载了回来,创建了一个又一个web和pwn实例,还几次去看排行榜、PPC评测记录和选手们的脸。。

3.jpg

1小时22分时,我的排名从7掉到了12。而这时我也把题看得差不多了,我意识到我只有完成那道“安全评估”,才能挤进前16。

1:30:00–2:05:00 Dijkstra解决安全评估

其实这道题我一开始以为是一道最小生成树,于是在我做出那两道水题后就在本机和书上找类似的题。我们这次比赛是允许使用本机资源的,也就是说,如果你做过这道题,或者你保存着这道题的题解,那么你可以直接提交。比赛开始5分钟内就有选手通过了这道题,估计他们用的就是这种方法。

我真的是对图论一窍不通,什么都不会写,于是我的策略就是拿别人类似题的代码修改。在知道这并不是一道最小生成树而是单源最短路径的时候,我将目光转向了Dijkstra算法。

我翻开了《算法竞赛进阶指南》,并且在本地找到了与之匹配的代码库:

Screenshot_20200926_163830.jpg

Screenshot_20200926_163850.jpg

当时我还没有考虑什么堆优化,我一直认为这些优化算法过于复杂,会导致出了问题很难排查。于是我把别人写的dijkstra复制了下来。

简单地改了改,主要体现在输入格式的不同和对“路由器”设定的变动,这点后面再说。

总之,大约比赛开始2小时后,我提交了代码。

提交之后,我意识到,这个规模的数据根本不能使用邻接矩阵存储,需要上邻接表+堆优化Dijkstra,于是我又复制粘贴了堆优化的核心代码,并且针对核心代码改main函数。

2:05:00–2:40:00 堆优化

说道这里,可以介绍一下这道题和它的解法了。

题目链接:https://mssctf.xidian.edu.cn/challenges#%E5%AE%89%E5%85%A8%E8%AF%84%E4%BC%B0-6

其实这题的建模过程并不难。题目大致就是给定n个点和m条带权边,并且有o个“路由器”,每个路由器都连接了数个点,任意两个连接着的点都存在一条权值为b的边,求从s点出发的单源最短路径。

这道题和一般的最短路模板题不一样的是:增加了“路由器”这个设定。

我对这个设定的解决方法是:把每个路由器都看作是一个点,每条与它相连的边的权值为b/2。这样,对于每个不是路由器的普通节点,可以实现与题目描述相同的效果。

那么b如果不被2整除怎么半呢?用double吗?

很简单,直接把所有的边权都×2,最后再除以2就可以了,根据题目描述,结果一定是整数。

这样,这道题就成为了一道普通的单源最短路径的模板题。

回到比赛,我在2小时5分之后就开始写对于堆优化代码的main函数,由于有特殊的数据结构,我只好“照葫芦画瓢”,但得益于我的代码能力,我花了不到30分钟改好了代码,并且通过了样例。在2小时35分时,我第一次提交了代码。

然后它就RE了。。之后我又把数据规模又开大了一些,WA。

4.jpg

之后反复看了几遍代码,又调试了几次,但并没有发现什么问题。

当时离比赛结束,只有不到20分钟了。

2:40:00–3:00:00 反!杀!

现在看看这段的录屏,都会觉得刺激。在比赛结束前4分钟从19挤到16,简直是太惊险了。

为什么我会把2小时40分钟作为一个分界点呢?因为在那之后,我改变了策略,开始疯狂提交代码。

Screenshot_20200926_165057.jpg

由于我并不清楚准确的问题,只能挨个去试。

当时我精神紧绷,一直在看表,这份紧张也影响了我后面的发挥。

在2小时50分时,我把代码中的int都改成了longlong,这下干脆连样例都过不了了,还白白浪费了时间,

Screenshot_20200926_173448.jpg

在55分时,我发现了一个最大的问题:变量重复定义。我在for循环外定义了一个x,又在循环中定义了一个x用于计数,还在循环内修改了它。

我立马把1分钟前提交的没有修改longlong的代码拷贝下来,开始修改。

但由于我过于紧张,忽略了被注释掉的freopen,导致我以为我的本机出了问题。

于是,我干脆直接在提交框里修改:

Screenshot_20200926_174308.jpg

正当我提交之后,惊喜出现了。弹出的并不是我期待的“Added to Judge Queue”,而是“You have already solved this”。

当时的我真的惊呆了。

“你已经解决了这道题”,因为平台不能查看提交的具体代码,我当时并不知道我的哪次提交通过了这道题。直到现在,我才明白,重复定义并不会给程序造成什么影响,for循环内外两个变量应该是相互独立的,真正的罪魁祸首是:评测机并不会忽略行末空格

当时我修改代码,去掉行末空格时,根本没有报要AC的心态,就是随便改一改,OI系列比赛一般都是会忽略行末空格的,没想到这个比赛没有这样做,赛后组委会还特别向那些栽在这个点上的选手解释道歉,那些提交了几十次没有AC的选手应该也是因为这个。

总之,这个在OI中没有人关注的点,竟成为了我解这道题的关键,其它的难度都不大,就看细节上的处理。

我当时怎么也不会想到

当时,我第一反应是去看排行榜,一看16名,真是太惊险了。

5.jpg

我长舒一口气,静静地观察排行榜,等待比赛的结束。

赛后

父母在看见我在比赛快要结束时还在第19名,就没有再关注排名了。比赛结束后,我口中的16名,着实让他们吃了一惊。

据一位看了B站排名直播的同学说,结束前4分钟我冲进了排行榜前16,还是最后一个拿到分的选手。

在我之后的PPC提交就全是RE和WA了,这也许就是心态的差距吧。

第二天的线上闭幕式,说好要办一个小时,但最终只持续了20多分钟。会上,我看到冯校长在现场,还是组委会邀请到的“知名中学的领导”的第一位,顿时心潮澎湃。

顺便说下,这场闭幕式B站有录播,传送门:https://www.bilibili.com/video/BV1354y117rP

宣读获奖名单的时候,主持人差点把我给落下,这里吐槽一下。

Screenshot_20200920_101553.jpg

最终,学校的一位老师替我拿回来了奖状和大支票,我也在周二学校的信息课上见到了它们。

周四,我登上了学校公众号,编辑还特意把我的名字放在了标题上,文章在这里:https://mp.weixin.qq.com/s/jwPMs0CtaUzah5Di-07T3g

去年的比赛,我是第19名,差1分进前16得奖,当时的我并没有坚持到最后,早早地就放弃了。这次上午比赛,我也是第19名,下午在我做出来最后一道题之前也是19名,好在我坚持到了最后,用对细节的追求和心态弥补了去年的遗憾。这种感觉是非常奇妙的,“history repeats itself”,我真的无法想象如果我没有做出那道题,再次拿到第19名,之后会发生什么样的事。

比赛结束后,获得三等奖的喜悦很快就消散了,这次比赛的偶然性真是太强了。这次比赛中我最大的收获并不是这个三等奖,也不是5000元奖金(虽然确实挺多的哈哈),而是这次独特的经历。我第一次在比赛结束前4分钟有突破性的进展;第一次在最后时刻“临危不乱”,“扭转乾坤”;在我好几次为了一点点微小的希望而去掉行末空格中,第一次真正的看到希望……这次比赛的经历真是奇妙啊。