GeekGame2 CTF Writeup
Ribom,总分2547,校内排名19
2022-11-28 更新
看了放出来的官方Writeup,发现有部分地方有根本性的理解错误。但毕竟原文是还原了一个当时思考的状态,所以把补充的内容都写在这里。
小北问答-MAC地址
之前不知道Wifi Positioning System,原来Mac地址还有这样的作用,涨知识了!
omnibox题
原来我没注意到,xssbot把scheme去掉丢进omnibox之后,是把原来的整个字符串拿去搜索的!我一直以为丢了就后面也丢了,所以一直写些 http:\\
这样的东西去绕过那个去除,实际根本不用绕!那个去scheme反而是友军!一个点的ip比较tricky这一点我已经通过源码发现了,真的残念。这个故事告诉我们读源码要仔细,不行就跑一跑。
当然js半问也是我见识短了,原来可以反引号转义(ascii值)出括号等号来,所以不用那个文档替换特性也能做。
nodejs
确实是个人知识储备不足了,但是flag2其实也不要啥特别nb的绕过,只要知道unescape
可以把url encode的字符解码就行了。这题真正难点在第三问nodejs沙箱。
19. 签到
跟去年签到神似的一道题,也是从一个PDF文件里读出乱码WingDing字体的字符。不同的是,这次字符并没有从页面溢出,并且从Adobe Acrobat
中会提示文件加密无法复制。但是我用另外一个开源软件SumatraPDF
在复制这个字符的时候没有遇到任何的问题,看来这个加密只是一个设置上的参数,数据本身并没有被加密。
复制出来的字符是
fa{ecm_badGeGmV! lgWloeAor@ekae2}
,很明显是两栏的栅栏密码,用现成的工具可以直接解出flag
1. 小北问答·极速版
挺喜欢这个随机题库+可即时验证正确答案的设计的,显著降低难度,建议下一届保留,虽然感觉下一届大家都知道里面的trick了
题库
Q: 每个 Android 软件都有唯一的包名。北京大学课外锻炼使用的最新版 PKU Runner 软件的包名是什么?
我是直接从安装包里把答案扒出来的。众所周知apk格式的安装包可以改成zip之后解压。解压之后有一个AndroidManifest.xml
,一看就是元文件,但是意外的是个二进制,所以用010 Editor
打开,发现里面有大量的2字节的Unicode字符,里面找可以找到package字段,可以看到后面跟了一串域名反写的东西cn.edu.pku.pkurunner
,刚好java的包名经常使用这样的格式。所以这就是答案了。
Q: 支持 WebP 图片格式的最早 Firefox 版本是多少?
打开必应搜索,输入firefox webp
,第四条记录标题就是Firefox 65即将添加对WebP格式图片的支持 附开启方法,虽然链接打不开,但是标题就告诉我们答案是65
。
Q: 北京大学某实验室曾开发了一个叫 gStore 的数据库软件。最早描述该软件的论文的 DOI 编号是 多少?
搜了一下gStore,首先搜到一篇知乎文章,标题是《gStore官网全新改版上线》,里面找到gStore官网gStore.cn。官网的【开发者资源】标签下有个【论文&专利】,把最早的一篇(2011年),题为《gStore: Answering SPARQL Queries Via Subgraph Matching》的文章拿去搜索,最后在researchgate里找到了这篇文章的DOI编号
Q: 视频 bilibili.com/video/BV1EV411s7vu 也可以通过 bilibili.com/video/av_____ 访问。下划 线内应填什么数字?
B站AV号转BV号的事件我有所耳闻,当时其实就有过这种转换工具出来,所以直接找一个用就行了,我用的是这个:https://tools.jixiaob.cn/bv2av/。顺便这个视频竟然是新宝岛啊。总好过Never gonna give you up🎵。
Q: 访问网址 “http://ctf.世界一流大学.com” 时,向该主机发送的 HTTP 请求中 Host 请求头的值 是什么?
主办方破费了啊,为了这个题专门买了个域名w
所以F12之后访问这个域名,看302跳转之前的记录就可以了,答案是ctf.xn--4gqwbu44czhc7w9a66k.com
。不过说起来这个host居然和地址栏输入的不一样,不知道是怎么做到的,可能和DNS有关?下一届会出dns相关的web题吗
Q: 在第一届 PKU GeekGame 比赛的题目《电子游戏概论》中,通过第 n 级关卡需要多少金钱?
这个题目开始出现随机肉鸽要素了
回答很简单,因为上一届题目有存档,所以看一下这个题后台和 关卡生成相关的源码,有一句GOAL_OF_LEVEL = lambda level: 300+int(level**1.5)*100
,从名字看应该是每关金币的生成函数。试一下发现确实是。
Q: 我有一个朋友在美国,他无线路由器的 MAC 地址是 d2:94:35:21:42:43。请问他所在地的邮编是 多少?
这个题我没做出来,并且我怀疑不可解。
MAC地址是链路层的地址,是网卡设备的唯一标识。据我查到的信息,MAC地址前三个字节表示网卡生产商的信息,后三个字节是由网卡商自行分配,原则上网卡生产商也不知道这个网卡会被装到哪个设备上或者部署在哪里。另外,我还查到第一个字节的倒数第二位标识了网卡的mac地址是全局的本地的,这个mac地址是本地的,也就是说除非同一个网络的设备可以通过arp获得mac地址,其他设备是拿不到的。最后,我还尝试查了mac地址相关的日志和数据库,没有查到,期待下有没有其他人找到了这样的数据库。
As last resort,我们还可以遍历可能的邮编。美国的邮编是5位数,好像有0开头,我们就假设有100000个可能,终端的限制是每30秒三次,因此最多需要1000000秒也就是11.6天,所以还是有一定的概率在比赛结束前遍历出来的……当然肯定不会这么做就是了。
当然二阶段拿到住址查邮编是很简单的,这里不再赘述了。
Q: 猜质数(两个数之间)
这个题给出的范围好像都是9位数,间隔在1000+。trick在于,多次测试后发现范围内的质数是不唯一的,一般都是8个。所以这个是原则上只有概率答对的题。遍历范围内质数可以用sympy
库的isprime
函数。
回答
正如二阶段提示所说,题库有8个题随机抽7个,其中有一个不会做,一个是纯概率,那么我们只需要大量尝试,当题库刚好没有MAC地址题,且猜质数猜对了就能获得flag,按二阶段提示是1/72概率。所以我用pwntools写了段python脚本,每十秒连接一次服务器答题,然后挂上nohup去睡大觉,第二天早上起来就能在输出日志里找到flag了。睡大觉也能拿到flag,美滋滋
根据flag的文本,看来MAC题就是做不出来,所以这是官方解法?
4. 编原译理习题课
这个题一阶段只做出flag12,二阶段补上了flag3
最先做出来的是flag2,因为g++并不是发现一个编译错误就退出编译的,所以有个非常简单的方法就是引入一个巨大的二进制文件,然后g++的报错日志里会包含二进制文件的所有内容。因为注意到题目用apt安装了python环境,所以我用的是#include "/usr/bin/python3"
flag1也不难,需要知道g++编译的时候,会把程序里出现的常量字符串打包进程序里,所以用类似char A[9000000] = "123123123";
声明一个巨大的常量字符串就可以了。值得注意的是这个字符串的初始化值不一定要真的有8M那么大,但不能是空字符串。
flag3我一开始陷入了个巨大的误区,就是觉得要构造一个巨复杂的表达式让g++解析的时候内存不足而报段错误,所以拿各种 宏套了半天没成果以及测试发现好像宏是不可以嵌套的。后来(周三)我其实想到了或许可以去找找别人报的bug,也找到了GNU bug tracker,但是可能是搜的方法不对以及连续熬夜人也不太清醒,硬是没找到相关的代码。第二阶段之后睡了一觉起来重新搜gnu bug tracker,这次只看c++, seg关键词并且按时间排序,终于找到了一个2022.4.18的会报SegFault的BUG(虽然最近回复是11.11),并且代码只有81Bytes:
void operator""_x(const char *, unsigned long);
static_assert(false, "foo"_x);
本地测试后果然报了SegFault,果断提交,拿到flag3
15. Flag Checker
这题给了个jar包,解压后发现只有一个GeekGame.class的类文件,根据题面找找JAVA8可用的反编译工具(我用的是Luyten),可以直接拿到反编译后的代码,确实如二阶段提示所说反编译质量很高,可读性很强。
源码里的一连串Unicode字符实在是很吸睛,所以我先从这里开始。看后面 的处理是逐字符和\u00ef
异或之后放进了一个scriptEngine里运行。用python模拟了这个过程,输出了一段JS代码。稍微查了一下确认了scriptEngine里跑的确实Javascript毕竟Java+scriptEngine==Javascript+Engine
接下来分析JS代码,因为代码不长,prettify后做些简单的变量替换可以很容易地理清逻辑。这是一个名为checkflag2
的函数,函数体里包含了一个数组a
,flag的第i
位字符等于checkflag2
函数本体的第a[i]
个字符(【函数的第?个字符】这个写法有点tricky,我在浏览器F12里跑了一下发现是针对最原始的那个字符串的),所以可以跑个模拟程序把flag跑出来,发现是flag2。
最后分析剩余部分,发现在actionPerformed
回调函数里有判断flag1的代码。flag1的内容在base64编码后,经过rot13函数处理要等于一个字符串。从rot13这个名字猜测是凯撒密码的一个著名变种:所有字母平移13位的密码。从源码来看,字母确实是这样处理的,数字则是平移5位,最终结果也没有+/=特殊字符。写一个模拟把flag跑出来即可。
另外actionPerformed
结尾的判断,似乎根据按下的按钮是不是flag2,可能会触发一个叫checkflag3的JS函数,莫非一开始这个题是有flag3的,因为考点重复了所以删掉了?
3. 智慧检测器
一开始看到这么长的源码根本看不下去,随便玩了玩也没什么思路,所以就暂时放置了。直到后来不死心再试试的时候发现了一个特性:如果输入两步,其中第二步是不合法的,那么虽然会提示不合法但是仍然会穿到墙里(最开始发现是用的最边界的墙,所以特别显眼)。
在本地用VSCode debug了源程序,处理指令的核心逻辑可以写成这样的伪代码,其中核心特性来自CurPos = NewPos
(源码422行):
NewPos = list(CurPos)
_move(newPos)
if _is_valid(newPos): # break if fail
CurPos = NewPos
else:
break
MoveCount += 1
Python基础小知识:list是传引用的,所以对list赋值之后,修改新的变量里的元素会影响原来的元素。如果要建立一个不受影响的list需要用构造函数, 切片或deepcopy建立一个新的拷贝。这个特性相当反直觉,以至于我每次用到数组拷贝的时候都会下意识地注意一下。但说起来这句同一个函数上面(385行)就有个正常的NewPos = list(CurPos)
,很难不认为是故意的
所以当第一次指令合法时,CurPos
的引用被传入了NewPos
,那么下一轮循环即使是非法指令,因为在前面对NewPos
赋值时已经改变了CurPos
,这里即使break出来也不会回退这个操作,于是就穿墙了。除此以外,因为这个break也顺便跳过了步数的计数器,所以穿墙这一次移动是免费的,相当于2倍的移动力。
有这个特性存在,第一问很简单,直接往头顶上穿,就会因为数组越界报IndexError。第二问则需要一点设计,因为地图有80层,连续穿79次墙后只剩余20步,而地图是55x55的,即使考虑穿墙也最 多只有40格的移动力,不一定能到达终点。在看源码注释的时候,我注意到起点和终点的位置并不是纯随机的,而是高概率分布在地图的两侧,因此前面的80步穿墙,我们的第一步要尽可能地向地图的对侧移动,这样在80步后我们有相当概率在终点的40步以内。
因为解法是概率性的,所以写了一个辅助脚本来帮助我做重复操作。具体来说就是在原来终端基础上写了个宏,连续进行n次向上的穿墙操作。有想过要不要把平面的走迷宫也写成算法的,但是想了想好麻烦,感觉处理不好贪心把自己贪死的情况,最终还是决定手操这部分,代码成型之后应该只roll了10次左右。
顺便一提,如果穿墙穿进了一个周围都被墙围住的位置,会报EOF错误。似乎如果一上来就穿79层触发概率很高,但是穿78层再手穿一层就有很大概率能穿上去,没有大样本测过也不知道是运气不好还是特性。总之这个题的flag文本精准地描述了这个游戏给我带来的印象。
21. 企鹅文档
第一次进题目环境看了一眼域名:这真的是腾讯文档?别又是出题组搞的专属环境
钓鱼网站吧。拿完flag之后看了一眼域名:这真的是腾讯文档?这权限控制真就控制了个寂寞啊?
大开眼界了。这个题告诉我们前端做权限控制是防君子不防小人的,不要用这个功能来处理敏感数据。
前半部分
第一问给了腾讯文档的链接,是个在线表格,我即使登录也只有只读权限,并且文档的主体部分(一个http链接,每行一个字符)是不可见的,当然也不能复制等等。开F12就可以看到与服务器的请求记录,我们特别关注的是涉及表格内容的条目,很容易发现有个opendoc请求,响应是个json,包含了title的【通过以下链接访问题目机密flag】的字符,以及下面很多行(包括被隐藏的行)的内容。然而仔细看会注意到这个请求是不完整的,只到60行,并且很容易看到请求里的两个参数startrow
和endrow
是控制返回的行数的,所以改成72重新发送请求即可拿到那个URL的完整内容。
当然我第一遍并不是这么做的,因为在做这个题的时候,F12有点问题,是之前开了筛选没关掉(UI太乱了也没发现),所以怎么也抓不到数据的包。最后我是拿出了
mitmproxy
这个中间人攻击软件,为了让这个软件抓到包还要删除浏览器对这个网站的HSTS缓存,最后终于是抓到包了,并且用这个软件的好处是可以在浏览器的请求发出之前修改数据包的内容,所以改参数非常简单。做完之后我才反应过来不对,F12没道理抓不到包,然后才发现我之前的配置错误,重新抓了一遍发现能抓到。绕了一大圈回到原点了属于是。
顺便一提,我确实从没注意过原来有个放大镜可以直接搜索,谢谢主办方(