GeekGame 2022 Writeup
评价:上当了。
也没做出来几问,虽然运气好抢到了「小北问答 · 极速版」解出所有 flag 的一血。
请用右边的目录进行快速跳转喵。
[Tutorial] †签到†
PDF 有保护,找个在线的网站去掉之后打开复制全文并粘贴到 Word 里:
1 | 别急 别急 WELCOME ABOARD, ALL PLAYERS! GO TO GEEKGAME.PKU.EDU.CN AND SUBMIT THE FLAG: fa{hns4PaigGeGm! lgTak__lyn_ekae} |
重新组合一下:
1 | flag{Thanks_4_Playing_GeekGame!} |
[Tutorial] 小北问答 · 极速版
拿到了本题 Q2 & 解出所有 Flag 的一血,不过运气的成分比较大吧。
质数这道题纯粹是靠运气的(在这个范围内肯定不止一个……不过我有一种奇怪的感觉,就是在这个范围内取下标 -1 的正确率比 0 要高?),MAC 地址那问看不懂(现在知道是 BSSID 了),别的题答案都可以预设一下。运气好跑通了一次就过了这样。
其它的题不会有人不知道答案怎么找吧?(笑)
1 | 作答时间不超过 3 秒钟即可获得额外的 2 分。 |
[Misc] 编原译理习题课
不保证是预期解,感谢互联网,真是一点 C++ 都没动手写呢!
玩挺大
1 |
|
1 | flag{not-muCh-largeR-than-an-eLeCtron-app} |
玩挺长
1 |
|
1 | flag{shOrt volatile PRogRam; long long message;} |
玩挺花
1 | void f() |
1 | flag{Sorry-TO-inform-you-that-gnu-Is-not-unix} |
[Misc] Flag Checker
需要用到 jar 反编译工具,此处使用的是 Jadx。
Flag 1
1 | if ("MzkuM8gmZJ6jZJHgnaMuqy4lMKM4".equals(rot13(Base64.getEncoder().encodeToString(this.textField1.getText().getBytes("UTF-8"))))) { |
写作 ROT13,读作 ROT18,对上方字符串进行 ROT18 解码再 Base64 解码即可得到 flag。
1 | ROT18 Cipher: MzkuM8gmZJ6jZJHgnaMuqy4lMKM4 |
Flag 2
1 | Invocable engineByName = new ScriptEngineManager().getEngineByName("nashorn"); |
nashorn
是 JavaScript 引擎,用你喜欢的语言将这段代码还原为 JavaScript 代码。
1 | res = "\u0089\u009a\u0081\u008c\u009b\u0086\u0080\u0081Ï\u008c\u0087\u008a\u008c\u0084\u0089\u0083\u008e\u0088ÝÇ°ß\u0097\u008e×Ü\u008a\u0097ÝÆ\u0094\u0099\u008e\u009dÏ°ß\u0097ØÝÛ\u008dÒ´È\u008c\u0087\u008e\u009d¬\u0080\u008b\u008a®\u009bÈÃÈ\u0082\u008e\u009fÈÃÈÈÃÈ\u009c\u009f\u0083\u0086\u009bÈÃÈ\u009c\u009b\u009d\u0086\u0081\u0088\u0086\u0089\u0096ÈÃȬ\u0080\u009d\u009d\u008a\u008c\u009bÈÃȸ\u009d\u0080\u0081\u0088ÈÃÈ\u0085ÂȲÔ\u009d\u008a\u009b\u009a\u009d\u0081ÏÇ¥¼ ¡´°ß\u0097ØÝÛ\u008d´Û²²Ç°ß\u0097\u008e×Ü\u008a\u0097Ý´°ß\u0097ØÝÛ\u008d´Ü²²Ç°ß\u0097ØÝÛ\u008d´Ý²Æ´°ß\u0097ØÝÛ\u008d´Þ²²Ç\u0089\u009a\u0081\u008c\u009b\u0086\u0080\u0081Ç°ß\u0097\u008e×Ü\u008a\u0097ÜÆ\u0094\u009d\u008a\u009b\u009a\u009d\u0081Ï°ß\u0097\u008e×Ü\u008a\u0097Ü´°ß\u0097ØÝÛ\u008d´ß²²ÇßÆ\u0092ÆÆÒÒÏ¥¼ ¡´°ß\u0097ØÝÛ\u008d´Û²²Ç´ßÃÞÚÃÞÙÃÞØÃÜßÃÞßÚÃÞÙÃÜÞÃÞÙÃÙØÃÜÃÜÜÃÚÃÙßÃÛÃÞßÙÃÙÃÛÞÃßÃÞÃÙØÃÜÃÞÙÃÛÃÙÃÜÜÃÝÜݲ´°ß\u0097ØÝÛ\u008d´Þ²²Ç\u0089\u009a\u0081\u008c\u009b\u0086\u0080\u0081Ç°ß\u0097\u008e×Ü\u008a\u0097ÜÆ\u0094\u009d\u008a\u009b\u009a\u009d\u0081ÏÇ\u008c\u0087\u008a\u008c\u0084\u0089\u0083\u008e\u0088ÝÄÏ°ß\u0097ØÝÛ\u008d´Ý²Æ´°ß\u0097ØÝÛ\u008d´ß²²Ç°ß\u0097\u008e×Ü\u008a\u0097ÜÆ\u0092ÆÆаß\u0097ØÝÛ\u008d´Ú²Õ°ß\u0097ØÝÛ\u008d´Ù²Æ\u0092" |
结果如下,注意不要对该代码进行美化等操作,因为其用到了自身,具体见下。
1 | function checkflag2(_0xa83ex2){var _0x724b=['charCodeAt','map','','split','stringify','Correct','Wrong','j-'];return (JSON[_0x724b[4]](_0xa83ex2[_0x724b[3]](_0x724b[2])[_0x724b[1]](function(_0xa83ex3){return _0xa83ex3[_0x724b[0]](0)}))== JSON[_0x724b[4]]([0,15,16,17,30,105,16,31,16,67,3,33,5,60,4,106,6,41,0,1,67,3,16,4,6,33,232][_0x724b[1]](function(_0xa83ex3){return (checkflag2+ _0x724b[2])[_0x724b[0]](_0xa83ex3)}))?_0x724b[5]:_0x724b[6])} |
然后梳理代码逻辑(或者使用 JS 反混淆工具),实际为根据一定顺序从 checkflag2
自身中取出字符,连在一起就是 flag。
1 | 0, 15, 16, 17, 30, 105, 16, 31, 16, 67, 3, 33, 5, 60, 4, 106, 6, 41, 0, 1, 67, 3, 16, 4, 6, 33, 232] a = [ |
附一段也许零基础 JavaScript 的朋友也能看懂的反混淆版本,仅供理解逻辑。
其中 (checkflag2 + '')
实际就是以 str 返回自己的代码。
1 | function checkflag2(flag) { |
1 | flag{javascript-obfuscator} |
[Misc] 我用108天录了个音
要在极低码率下还能听那显然只能 opus 了。
压制参数:
1 | ffmpeg -i helang_phrase_05.wav -c:a libopus -b:a 6K -frame_duration 120 -application voip -cutoff 4000 -sample_fmt s16 -ar 8000 -ac 1 -fflags +bitexact -flags:v +bitexact -flags:a +bitexact -map_metadata -1 helang_phrase_05_s.ogg |
其中 frame_duration
参数 opusenc
和 ffmpeg
文档里都只写了 60 但实际上可以 120,然后改 opusenc 源码再编译这种变态的事情我实在是干不出来,所以还是喷出题人吧。
另外静音段奇怪地发现 32Kbps 的压缩效果好于 6Kbps……这是什么玄学?
能拿到一个 Flag 的文件列表:
文件名 | 大小 |
---|---|
10s_interval_100hz.ogg |
1651 Bytes |
10s_interval_100hz_0.5K.ogg |
643 Bytes |
helang_phrase_01.ogg |
1069 Bytes |
helang_phrase_02_s.ogg |
928 Bytes |
helang_phrase_03_s.ogg |
1074 Bytes |
helang_phrase_04_s.ogg |
922 Bytes |
helang_phrase_05_s.ogg |
1538 Bytes |
一番凹之后,我当时就很™不能理解出题人是怎么做到把静音段压到 0.5KB 的,1651 Bytes 已经是我尝试之后的最佳结果了。
直到读了题解之后发现原来码率也是建议范围,实际最低码率能 0.5Kbps,这样的话每段静音段就能再省 1KB 了,见表格中的 。10s_interval_100hz_0.5K.ogg
,比赛中使用的是 1651 Bytes 的版本
尝试的时候我也发现,1Hz WAV 也可以把静音段做到很小,但是 opus 最低支持 100Hz 输入重采样……
[Web] 企鹅文档
第 1 关
将 https://docs.qq.com/dop-api/get/sheet
的 startrow=61&endrow=72
改为 startrow=0&endrow=72
得到以下返回:
1 | {"retcode":0,"data":{"title":"企鹅文档","initialAttributedText":{"referenceData":"[]","text":[[[{"t":14,"v":5,"c":["BB08J2",14,0,73]},{"t":14,"v":5,"c":["BB08J2",2,0,1]}],[{"t":2,"v":5,"c":["BB08J2",[[10,67]],[[1,0,0.0]],"ROWS"]},{"t":2,"v":5,"c":["BB08J2",[[10,67]],[[2,0,1]],"ROWS"]},{"t":2,"v":5,"c":["BB08J2",[[0,0]],[[1,0,305.0]],"COLUMNS"]},{"t":2,"v":5,"c":["BB08J2",[[0,0]],[[2,0,0]],"COLUMNS"]}],[{"t":3,"v":5,"c":[["BB08J2",0,72,0,0],{"0":{"0":133,"2":[1,"通过以下链接访问题目机密flag:"],"3":0,"8":[[0,{"0":"#000000","1":"","2":10,"3":0,"4":0,"5":0}]]},"1":{"0":5,"2":[1,"h"],"3":1},"2":{"0":5,"2":[1,"t"],"3":1},"3":{"0":5,"2":[1,"t"],"3":1},"4":{"0":5,"2":[1,"p"],"3":1},"5":{"0":5,"2":[1,"s"],"3":1},"6":{"0":5,"2":[1,":"],"3":1},"7":{"0":5,"2":[1,"/"],"3":1},"8":{"0":5,"2":[1,"/"],"3":1},"9":{"0":5,"2":[1,"g"],"3":1},"10":{"0":5,"2":[1,"e"],"3":1},"11":{"0":5,"2":[1,"e"],"3":1},"12":{"0":5,"2":[1,"k"],"3":1},"13":{"0":5,"2":[1,"g"],"3":1},"14":{"0":5,"2":[1,"a"],"3":1},"15":{"0":5,"2":[1,"m"],"3":1},"16":{"0":5,"2":[1,"e"],"3":1},"17":{"0":5,"2":[1,"."],"3":1},"18":{"0":5,"2":[1,"p"],"3":1},"19":{"0":5,"2":[1,"k"],"3":1},"20":{"0":5,"2":[1,"u"],"3":1},"21":{"0":5,"2":[1,"."],"3":1},"22":{"0":5,"2":[1,"e"],"3":1},"23":{"0":5,"2":[1,"d"],"3":1},"24":{"0":5,"2":[1,"u"],"3":1},"25":{"0":5,"2":[1,"."],"3":1},"26":{"0":5,"2":[1,"c"],"3":1},"27":{"0":5,"2":[1,"n"],"3":1},"28":{"0":5,"2":[1,"/"],"3":1},"29":{"0":5,"2":[1,"s"],"3":1},"30":{"0":5,"2":[1,"e"],"3":1},"31":{"0":5,"2":[1,"r"],"3":1},"32":{"0":5,"2":[1,"v"],"3":1},"33":{"0":5,"2":[1,"i"],"3":1},"34":{"0":5,"2":[1,"c"],"3":1},"35":{"0":5,"2":[1,"e"],"3":1},"36":{"0":5,"2":[1,"/"],"3":1},"37":{"0":5,"2":[1,"t"],"3":1},"38":{"0":5,"2":[1,"e"],"3":1},"39":{"0":5,"2":[1,"m"],"3":1},"40":{"0":5,"2":[1,"p"],"3":1},"41":{"0":5,"2":[1,"l"],"3":1},"42":{"0":5,"2":[1,"a"],"3":1},"43":{"0":5,"2":[1,"t"],"3":1},"44":{"0":5,"2":[1,"e"],"3":1},"45":{"0":5,"2":[1,"/"],"3":1},"46":{"0":5,"2":[1,"p"],"3":1},"47":{"0":5,"2":[1,"r"],"3":1},"48":{"0":5,"2":[1,"o"],"3":1},"49":{"0":5,"2":[1,"b"],"3":1},"50":{"0":5,"2":[1,"_"],"3":1},"51":{"0":5,"2":[1,"k"],"3":1},"52":{"0":5,"2":[1,"A"],"3":1},"53":{"0":5,"2":[1,"i"],"3":1},"54":{"0":5,"2":[1,"Q"],"3":1},"55":{"0":5,"2":[1,"c"],"3":1},"56":{"0":5,"2":[1,"W"],"3":1},"57":{"0":5,"2":[1,"H"],"3":1},"58":{"0":5,"2":[1,"o"],"3":1},"59":{"0":5,"2":[1,"b"],"3":1},"60":{"0":5,"2":[1,"s"],"3":1},"61":{"0":5,"2":[1,"B"],"3":1},"62":{"0":5,"2":[1,"z"],"3":1},"63":{"0":5,"2":[1,"R"],"3":1},"64":{"0":5,"2":[1,"J"],"3":1},"65":{"0":5,"2":[1,"E"],"3":1},"66":{"0":5,"2":[1,"s"],"3":1},"67":{"0":5,"2":[1,"_"],"3":1},"68":{"0":5,"2":[1,"n"],"3":1},"69":{"0":5,"2":[1,"e"],"3":1},"70":{"0":5,"2":[1,"x"],"3":1},"71":{"0":5,"2":[1,"t"],"3":1},"72":{"0":133,"2":[1,"机密flag链接已经受到保护,只允许出题人访问"],"3":2,"8":[[0,{"0":"#000000","1":"","2":10,"3":0,"4":0,"5":0}]]}},{"0":[{"0":32321,"2":[0,"General"],"8":"left","11":"#000000","12":"","13":10,"14":0,"15":0,"16":0},{"0":0},{"0":32257,"2":[0,"General"],"11":"#000000","12":"","13":10,"14":0,"15":0,"16":0}],"1":[],"2":[]}]}]]],"attribs":""},"bad":2,"padType":"sheet","padSubId":"BB08J2","endrow":"72","wb":0,"maxcol":1,"spv":1,"maxrow":73,"header":[{"d":[{"id":"BB08J2","type":"","hidden":false,"name":"工作表1"}],"type":"ms"}],"padId":"WYlPTaBZyykj","ver":8,"rev":23,"startrow":"0","globalPadId":"300000000$WYlPTaBZyykj","pos":1,"sheetId":"BB08J2"}} |
拼接一下:
1 | obj = {"0":{"0":133,"2":[1,"通过以下链接访问题目机密flag:"],"3":0,"8":[[0,{"0":"#000000","1":"","2":10,"3":0,"4":0,"5":0}]]},"1":{"0":5,"2":[1,"h"],"3":1},"2":{"0":5,"2":[1,"t"],"3":1},"3":{"0":5,"2":[1,"t"],"3":1},"4":{"0":5,"2":[1,"p"],"3":1},"5":{"0":5,"2":[1,"s"],"3":1},"6":{"0":5,"2":[1,":"],"3":1},"7":{"0":5,"2":[1,"/"],"3":1},"8":{"0":5,"2":[1,"/"],"3":1},"9":{"0":5,"2":[1,"g"],"3":1},"10":{"0":5,"2":[1,"e"],"3":1},"11":{"0":5,"2":[1,"e"],"3":1},"12":{"0":5,"2":[1,"k"],"3":1},"13":{"0":5,"2":[1,"g"],"3":1},"14":{"0":5,"2":[1,"a"],"3":1},"15":{"0":5,"2":[1,"m"],"3":1},"16":{"0":5,"2":[1,"e"],"3":1},"17":{"0":5,"2":[1,"."],"3":1},"18":{"0":5,"2":[1,"p"],"3":1},"19":{"0":5,"2":[1,"k"],"3":1},"20":{"0":5,"2":[1,"u"],"3":1},"21":{"0":5,"2":[1,"."],"3":1},"22":{"0":5,"2":[1,"e"],"3":1},"23":{"0":5,"2":[1,"d"],"3":1},"24":{"0":5,"2":[1,"u"],"3":1},"25":{"0":5,"2":[1,"."],"3":1},"26":{"0":5,"2":[1,"c"],"3":1},"27":{"0":5,"2":[1,"n"],"3":1},"28":{"0":5,"2":[1,"/"],"3":1},"29":{"0":5,"2":[1,"s"],"3":1},"30":{"0":5,"2":[1,"e"],"3":1},"31":{"0":5,"2":[1,"r"],"3":1},"32":{"0":5,"2":[1,"v"],"3":1},"33":{"0":5,"2":[1,"i"],"3":1},"34":{"0":5,"2":[1,"c"],"3":1},"35":{"0":5,"2":[1,"e"],"3":1},"36":{"0":5,"2":[1,"/"],"3":1},"37":{"0":5,"2":[1,"t"],"3":1},"38":{"0":5,"2":[1,"e"],"3":1},"39":{"0":5,"2":[1,"m"],"3":1},"40":{"0":5,"2":[1,"p"],"3":1},"41":{"0":5,"2":[1,"l"],"3":1},"42":{"0":5,"2":[1,"a"],"3":1},"43":{"0":5,"2":[1,"t"],"3":1},"44":{"0":5,"2":[1,"e"],"3":1},"45":{"0":5,"2":[1,"/"],"3":1},"46":{"0":5,"2":[1,"p"],"3":1},"47":{"0":5,"2":[1,"r"],"3":1},"48":{"0":5,"2":[1,"o"],"3":1},"49":{"0":5,"2":[1,"b"],"3":1},"50":{"0":5,"2":[1,"_"],"3":1},"51":{"0":5,"2":[1,"k"],"3":1},"52":{"0":5,"2":[1,"A"],"3":1},"53":{"0":5,"2":[1,"i"],"3":1},"54":{"0":5,"2":[1,"Q"],"3":1},"55":{"0":5,"2":[1,"c"],"3":1},"56":{"0":5,"2":[1,"W"],"3":1},"57":{"0":5,"2":[1,"H"],"3":1},"58":{"0":5,"2":[1,"o"],"3":1},"59":{"0":5,"2":[1,"b"],"3":1},"60":{"0":5,"2":[1,"s"],"3":1},"61":{"0":5,"2":[1,"B"],"3":1},"62":{"0":5,"2":[1,"z"],"3":1},"63":{"0":5,"2":[1,"R"],"3":1},"64":{"0":5,"2":[1,"J"],"3":1},"65":{"0":5,"2":[1,"E"],"3":1},"66":{"0":5,"2":[1,"s"],"3":1},"67":{"0":5,"2":[1,"_"],"3":1},"68":{"0":5,"2":[1,"n"],"3":1},"69":{"0":5,"2":[1,"e"],"3":1},"70":{"0":5,"2":[1,"x"],"3":1},"71":{"0":5,"2":[1,"t"],"3":1},"72":{"0":133,"2":[1,"机密flag链接已经受到保护,只允许出题人访问"],"3":2,"8":[[0,{"0":"#000000","1":"","2":10,"3":0,"4":0,"5":0}]]}} |
1 | 通过以下链接访问题目机密flag: |
第 2 关
从提供的 HAR,用和第一关一样的思路重建像素画。注意要取 rev=1
的那次请求。
1 | sheet = content['data']['initialAttributedText']['text'][0][3][0]['c'][1] |
像素画结果示例(写题解的时候发现在 VSCode 右侧快速预览看着还挺方便):
1 | XXX |
1 | flag{ShouldBeSponsoredByTencent} |
[Web] 私有笔记
知识,与你分享
https://prob07-xxxxxxxx.geekgame.pku.edu.cn/index.php/%E9%A6%96%E9%A1%B5?action=rollback&from={{:Flag}}
1 | 回退失败 |
来我家做客吧
利用的是 Lilypond 相关的 RCE。
思路的话,可以是先从 CVE 列表查看发现很多插件有问题,然后去特殊:版本页查看安装的插件,发现有 Score 和 Lilypond。
(阅读官方题解之后才意识到确实可以直接传个 shell 上去,就不用和 Lilypond 斗智斗勇了)
参考资料:
- Extension:Score/2021 security advisory
- Extension:Score
- T257062 - Lilypond seemingly not subject to restrictions (CVE-2020-29007)
- lilypond-scheme-hacking/read_file.scm
1 | 我家里真的有 Flag2,不信你看: |
1 | flag{li1yp0nd_can_1ead_to_rce} |
[Algorithm] 381654729
本题的「累进可除数」是 16 进制版本除以其位数(10 进制)。
我的思路是枚举可能的字符组合,然后检查是否满足条件直到获得 24 位长的 flag。
1 | xor_in = 2511413510804014444370343823137024266846210390063420240191 |
1 | flag{founD_magiC_NUmber} |
总结
希望「我用108天录了个音」和「乱码还原」的出题人心里自己有点 ACDE 数。
(尤其是「乱码还原」的出题人,都没意识到自己会被喷么)
别的我菜。
GeekGame 2022 Writeup