「Hackergame 2022」#3 Writup 囤囤囤 1
这篇 Writeup 写一下 Hackergame 2022 里刚囤 flag 时做的剩下一部分题:
微积分计算小练习、蒙特卡罗轮盘赌、二次元神经网络、光与影、片上系统、企鹅拼盘、火眼金睛的小 E
微积分计算小练习
小 X 作为某门符号计算课程的助教,为了让大家熟悉软件的使用,他写了一个小网站:上面放着五道简单的题目,只要输入姓名和题目答案,提交后就可以看到自己的分数。
点击此链接访问练习网站(没链接)
想起自己前几天在公众号上学过的 Java 设计模式免费试听课,本着前后端离心(咦?是前后端离心吗?还是离婚?离。。离谱?总之把功能能拆则拆就对啦)的思想,小 X 还单独写了一个程序,欢迎同学们把自己的成绩链接提交上来。
总之,因为其先进的设计思想,需要同学们做完练习之后手动把成绩连接贴到这里来:
点击此链接提交练习成绩 URL(没链接)
点进第一个链接,随便做一遍,得到成绩分享页面 /share?result=...
,然后将链接贴到第二个链接里,会自动读取出名字和成绩。
读取的过程是用 selenium 打开一个浏览器,GET login 然后将 flag 放入 cookie,在 GET 输入的 url(会替换掉 netloc 为 web,scheme 为 http),然后读取 #greeting 和 #score 的内容。
再看第一个链接,其 result 是可以构造的,相关逻辑:
1 | const queryString = window.location.search; |
也就是将 result base64 解码,: 前面的为分数,后面的为用户名,然后填写进去。这里就可以进行 xss。没学过 xss,所以想了半天插入一个 script tag 之后怎么让处在前面的它被运行,后来搜了搜才知道可以利用 onload onerror 这些事件来填写脚本。
所以 payload 就是 100:<img src=1 onerror="document.querySelector('#greeting').innerHTML=document.cookie">
,然后 base64 后作为 result 传入,再丢给第二个提交链接,得到 flag:flag{xS5_1OI_is_N0t_SOHARD_3c97784c1a}
蒙特卡罗轮盘赌
这个估算圆周率的经典算法你一定听说过:往一个 1x1 大小的方格里随机撒 N 个点,统计落在以方格某个顶点为圆心、1 为半径的 1/4 扇形区域中撒落的点数为 M,那么 M/N 就将接近于 π/4 。
当然,这是一个概率性算法,如果想得到更精确的值,就需要撒更多的点。由于撒点是随机的,自然也无法预测某次撒点实验得到的结果到底是多少——但真的是这样吗?
有位好事之徒决定借此和你来一场轮盘赌:撒 40 万个点计算圆周率,而你需要猜测实验的结果精确到小数点后五位。为了防止运气太好碰巧猜中,你们约定五局三胜。
看起来没什么其它漏洞,从伪随机入手,设置的随机种子为 time(0)+clock(),也就是当前时间戳加上程序运行到此处的 ticks 数。时间戳以秒为单位,波动不大,直接使用连接时的时间戳就可以。clock() 会有较大波动,从 0 开始枚举,将得到的值传入一个 C 程序中作为随机种子,模拟一下,看一看前两个是否能和正确结果对上。能对上则说明随机种子找对了,将后三个结果输回去即可完成。
1 | import time |
运行得到 flag:flag{raNd0m_nUmb34_a1wayS_m4tters_……}
哦对了,有一个很坑的点是 mac 上的 gcc 其实是 clang 的 alias,而 clang 和 gcc 的随机数有区别,在 mac 上跑的话就一直爆不出来种子。在 Linux 上就可以一下爆出来。
二次元神经网络
天冷极了,下着雪,又快黑了。这是一年的最后一天——大年夜。在这又冷又黑的晚上,一个没有 GPU、没有 TPU 的小女孩,在街上缓缓地走着。她从家里出来的时候还带着捡垃圾捡来的 E3 处理器,但是有什么用呢?跑不动 Stable Diffusion,也跑不动 NovelAI。她也想用自己的处理器训练一个神经网络,生成一些二次元的图片。
于是她配置好了 PyTorch 1.9.1,定义了一个极其简单的模型,用自己收集的 10 张二次元图片和对应的标签开始了训练。
她在 CPU 上开始了第一个 epoch 的训练,loss 一直在下降,许多二次元图片重叠在一起,在向她眨眼睛。
她又开始了第二个 epoch,loss 越来越低,图片越来越精美,她的眼睛也越来越累,她的眼睛开始闭上了。
…
第二天清晨,这个小女孩坐在墙角里,两腮通红,嘴上带着微笑。新年的太阳升起来了,照在她小小的尸体上。
人们发现她时才知道,她的模型在 10 张图片上过拟合了,几乎没有误差。
(完)
听完这个故事,你一脸的不相信:「这么简单的模型怎么可能没有误差呢?」,于是你开始复现这个二次元神经网络。
目标看起来就是让模型生成的图片和预期几乎没有误差。试着多训练几轮,试图过拟合,记录一下 loss,发现降到 0.001+ 的时候就降不下去了,而需要的是 0.0005
看起来不可行。而且这是一道 web 类题,考虑用一些手段来让它认为我的输出是完全正确的。
搜索可以发现存的 .pt 文件中有使用 pickle 序列化存储的 .pkl 文件。而在读取的时候也会进行反序列化,这也就存在一个 pickle 反序列化的漏洞。
我们可以自己写一个恶意类然后打包到 data.pkl 压缩进 .pt 文件,在反序列化的时候就会执行其中的代码,比如:
1 | class Exploit(object): |
这个在本地测试的时候运行 infer.py 可以打通,但远程就不可以。所以可以猜测远程实际上从其它模块中调用了 infer 函数,如果没有正常返回,则会报错。
那么我们的思路就是让整个程序都可以正常运行,只是在反序列化的时候进行一些操作。根据源码可以知道最终会将模型输出的结果存放在 /tmp/result.json 中,然后在其它位置再读取这个文件,进行判断。而如果没有这个文件则会直接报错。
所以可以在 reduce 中将完全正确的结果先写入 /tmp/result.json 中。但如果这时直接 exit,则后面程序无法执行,会出现报错。所以还需要让后面完全正常运行。整个 infer 函数的逻辑大概如下:
1 | def infer(pt_file): |
我们输入的 pt 文件会在 torch.load 中进行反序列化,这时会写入 /tmp/result.json。而后面对于我们写入的威胁就是还会 json.dump 一次。所以首先需要将 json.dump 这个函数的作用抹除掉:__import__('json').dump=lambda x, y: 0
。但这还不够,因为参数中的 open 也会执行,以 w 方式打开文件的话会先直接清空文件,所以也需要抹掉 open 的作用。不过后面肯定还会需要使用 open 来读取文件,所以只能抹掉写入的部分:__builtins__['_open'] = open; __builtins__['open']=lambda x, y: 0 if y=='w' else __builtins__['_open'](x, y)
。
这样来讲我们的 exp 就是:
1 | class Exploit(object): |
但仅将这个打包后得到的 data.pkl 直接压缩进 pt 文件还是不行。因为模型就没法正常读取了,所以还需要对其进行一些修改。
pkl 文件实际存储的是一个构造好的虚拟机指令,pickle 反序列化时会执行它。看源码可以了解到有一个指令 0x2E 表示了结束返回。所以直接将生成的 data.pkl 末尾的 0x2E 去掉,然后直接接上一份正确的 data.pkl 内容即可完成构造。
构造好后上传 pt 文件,即可达到目标得到 flag:flag{Torch.Load.Is.Dangerous-……}
光与影
冒险,就要不断向前!
在寂静的神秘星球上,继续前进,探寻 flag 的奥秘吧!
打开发现是一个 WebGL 渲染的场景,其中 flag 的内容被挡住了。所有内容都是在前端的,存下来就可以本地调试。
发现其中的主要场景渲染代码都在 fragment-shader.js 中。可以发现由一些 sdf 组成,最终的场景也是由几个 sdf 结果取 min 而来的。
看起来 t5SDF 的代码最短,可能是施加的遮盖。所以将 sceneSDF 中 t5 相关的部分删掉,再打开页面运行即可看到完整 flag:flag{SDF-i3-FuN!}
片上系统
最近,你听说室友在 SD 卡方面取得了些进展。在他日复一日的自言自语中,你逐渐了解到这个由他一个人自主研发的片上系统现在已经可以从 SD 卡启动:先由“片上 ROM 中的固件”加载并运行 SD 卡第一个扇区中的“引导程序”,之后由这个“引导程序”从 SD 卡中加载“操作系统”。而这个“操作系统”目前能做的只是向“串口”输出一些字符。
同时你听说,这个并不完善的 SD 卡驱动只使用了 SD 卡的 SPI 模式,而传输速度也是低得感人。此时你突然想到:如果速度不快的话,是不是可以用逻辑分析仪来采集(偷窃)这个 SD 卡的信号,从而“获得” SD 卡以至于这个“操作系统”的秘密?
你从抽屉角落掏出吃灰已久的逻辑分析仪。这个小东西价格不到 50 块钱,采样率也只有 24 M。你打开 PulseView,把采样率调高,连上室友开发板上 SD 卡的引脚,然后接通了开发板的电源,希望这聊胜于无的分析仪真的能抓到点什么有意思的信号。至于你为什么没有直接把 SD 卡拿下来读取数据,就没人知道了。
引导扇区
听说,第一个 flag 藏在 SD 卡第一个扇区的末尾。你能找到它吗?
操作系统
室友的“操作系统”会输出一些调试信息和第二个 flag。从室友前些日子社交网络发布的终端截图看,这个“操作系统”每次“启动”都会首先输出:
LED: ON
Memory: OK或许你可以根据这一部分固定的输出和引导扇区的代码,先搞清楚那“串口”和“SD 卡驱动”到底是怎么工作的,之后再仔细研究 flag 到底是什么,就像当年的 Enigma 一样。
第一部分直接使用 PulseView 软件读取 binary 文件,得到信号,然后添加 SD card(SPI mode)解码器,将几个信号接上,就可以在 MOSI data 中看到 flag
dump 出来然后转换即可得到 flag:flag{0K_you_goT_th3_b4sIc_1dE4_caRRy_0N}
第二部分试图逆向后面的 RISCV 指令,但完全看不出什么有意义的东西,怀疑是数据搞错了,懒得修,罢了。
企鹅拼盘
这是一个可爱的企鹅滑块拼盘。(觉得不可爱的同学可以换可爱的题做)
和市面上只能打乱之后拼回的普通滑块拼盘不同,这个拼盘是自动打乱拼回的。一次游戏可以帮助您体验到 16/256/4096 次普通拼盘的乐趣。
每一步的打乱的方式有两种,选择哪一种则由您的输入(长度为 4/16/64 的 0/1 序列)的某一位决定。如果您在最后能成功打乱这个拼盘,您就可以获取到 flag 啦,快来试试吧wwwwww
第一部分输入只有四个 bit,直接手动试就能试出来答案是 1000,flag:flag{it_works_like_magic_……}
第二部分输入有 16 个 bit,可以用代码爆破一下,将题给代码中的主逻辑复制出来,枚举输入跑一下:
1 | import json |
爆破出结果为 0010111110000110,flag:flag{Branching_Programs_are_NC1_……}
第三部分太复杂了,应该爆破不出来,毕竟这是一道 math 题,开摆。
火眼金睛的小 E
小 E 有很多的 ELF 文件,它们里面的函数有点像,能把它们匹配起来吗?
小 A:这不是用 BinDiff 就可以了吗,很简单吧?
只做了右手就行的第一部分,也就是两次达到 100% 正确。拖进 IDA 中硬看,找 CFG 图以及汇编代码比较类似的函数即可,时限也很长,不用着急,很容易就能找到相似的函数。提交拿到 flag:flag{easy_to_use_bindiff_……} (笑死,根本没用 bindiff)
第二部分要求一个小时内完成 100 题中的 40 题,第三部分要求三小时内完成 200 题中的 60 题,不想做,开摆。
剩下的一些题,general 差一个 OJ 第二问不知道咋做,区块链也不知道咋做,感觉很神奇。web 差一道题,可能是 SQL 注入?反正我本来也不会,就不做了。其它的就是 binary/math 了,等着赛后看 writeup 学习学习。
「Hackergame 2022」#3 Writup 囤囤囤 1