「Hackergame 2021」#2 Writup 开局上分篇 1
这里接上一篇,Writeup 的有:大砍刀、图之上、赛博厨房01、助记词1、p😭q
有些虽然偏后、分值高,但是总体并不难
FLAG 助力大红包
参与活动,助力抽奖!集满 1 个 flag,即可提取 1 个 flag。
恭喜你积攒到 0.5…… 个 flag,
剩余时间:10分00秒已有 0 位好友为您助力。
将如下链接分享给好友,可以获得好友助力,获得更多 flag:……
老并夕夕了,经过一些测试和看规则可以知道,ip 在同一 /8 网段的用户被视为同一用户,即 ip 地址的第一个点前面的数字不一样才是不同用户
再用虚拟机和手机试一下,发现每个用户增加的 flag 数量很小
所以推测需要200+个 ip 地址,肯定不会要真的转发,而且也很难凑出很多不在同一 /8 网段的 ip
于是在 BurpSuite 里面抓包可以看到,每次点击“助力”都会发送一个到助力链接的 POST,内容为 ip 地址
然后将其发送到 Repeater 中,尝试更改 ip 地址,得到的 Response 中说 “失败!检测到前后端检测 IPv4 地址不匹配”
所以仅仅更改 POST 内容的 ip 是不够的,而提供给检测的内容也仅仅是一个 POST,所以可以更改 POST 头,添加 X-Forwarded-For
然后使用 python 就可以循环发送 POST 并伪造 ip 地址得到256个助力了,刚好达到1个flag:
(要注意 sleep 一段时间,不然会出现操作过快拒绝的情况;也不要 sleep 过长,否则超过10分钟 flag 就无效了)
1 | import requests |
图之上的信息
小 T 听说 GraphQL 是一种特别的 API 设计模式,也是 RESTful API 的有力竞争者,所以他写了个小网站来实验这项技术。
你能通过这个全新的接口,获取到没有公开出来的管理员的邮箱地址吗?
题目信息给的很充分,用的是 GraphQL,要用其得到 admin 的邮箱
没接触过 GraphQL,所以直接必应(逃
查到了很多有用的东西:
- GraphQL官网:了解一下 GraphQL 是干什么的,要怎么用
- GraphiQL:一个进行 GraphQL 查询的 GUI
- 【安全记录】玩转GraphQL - DVGA靶场(上)- 知乎
- GraphQL Voyager:可视化现实 GraphQL 内省出的结构
简而言之,GraphQL 就是一个可以通过一次 query 请求查询多个资源的 API 模式,只要 网址/graphql?query=...
就可以实现查询
有些使用 GraphQL 的网站可以直接通过访问 网址/graphiql
得到查询的 GUI
但是本题中禁止了,但可以使用 GraphiQL 软件来进行查询
在第三个链接中可以了解到,可以利用 GraphQL 的内省查询来泄露出内部的结构,把其中的查询语句丢到 GraphiQL 中可以得到结果
1 | query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } }}fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef }}fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue}fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } }} |
然后把结果丢到 GraphQL Voyager 中就可以得到可视化的结构:
所以只需要根据 id query 一下 user 就可以了:
1 | query { user(id: 1) { privateEmail, } } |
赛博厨房
虽然这是你的餐厅,但只有机器人可以在厨房工作。机器人精确地按照程序工作,在厨房中移动,从物品源取出食材,按照菜谱的顺序把食材依次放入锅内。
机器人不需要休息,只需要一个晚上的时间来学习你教给它的程序,在此之后你就可以在任何时候让机器人执行这个程序,程序的每一步执行都会被记录下来,方便你检查机器人做菜的过程。
另外为了符合食品安全法的要求,赛博厨房中的机器人同一时间手里只能拿一种食物,每次做菜前都必须执行清理厨房的操作,把各处的食物残渣清理掉,然后回到厨房角落待命。
每天的菜谱可能不同,但也许也存在一些规律。
对机器人编程可以使用的指令有(n, m 为整数参数,程序的行号从 0 开始,注意指令中需要正确使用空格):
向上 n 步
向下 n 步
向左 n 步
向右 n 步
放下 n 个物品
拿起 n 个物品
放下盘子
拿起盘子
如果手上的物品大于等于 n 向上跳转 m 行
如果手上的物品大于等于 n 向下跳转 m 行赶紧进入赛博厨房开始做菜吧!
刚看题还是挺懵的,想了好半天才明白
简单说来就是,每天可以编写新的程序,但是只能运行一个之前编写过的程序
每个程序只有几种指令可以使用,需要在其中满足菜谱的顺序要求
而问题在于,编写程序后的第二天的菜谱可能会不同,导致前面编写的程序无法使用
所以就需要预测第二天的菜谱
Level 0
可以看到第 0 天的菜谱是 1, 0,也就是要在同一个程序中依次向锅(1,0)中放入 1 号食物(0,2)和 0 号食物(0,1)
随便编写程序保存,直接到下一天,可以发现菜谱发生了变化
多次尝试之后发现菜谱只有 0,0 / 0,1 / 1,0 / 1,1 四种
所以在第 0 天编写学习四个程序,到下一天就可以根据菜谱来执行了
例如程序 1,0 就可以编写为:
1 | 向右 2 步 |
只要正确了一天,就可以拿到 flag 了
Level 1
只有 1 个食物,菜谱是好多 0
同样随便编写程序保存进入下一天,发现菜谱没有变化,还是 73 个 0
所以这一关可能只是循环的教程
可用的指令中有一条 “如果手上的物品大于等于 n 向上跳转 m 行”
可以用它来达到循环的效果
只需要拿 73 个物品,然后循环放下直到手中没有了即可
1 | 向右 1 步 |
同样保存下一天执行就可以拿到 flag 了
剩下的两个看起来大概是通过源码来推测出菜谱的生成方法,然后编写相应的指令,太难了,不会qwq
助记词
题目有效内容:
你的室友终于连夜赶完了他的 Java 语言程序设计的课程大作业。看起来他使用 Java 17 写了一个保存助记词的后端,当然还有配套的前端。助记词由四个英文单词组成,每个用户最多保存 32 条。
你从他充满激情却又夹杂不清的表述中得知,他似乎还为此专门在大作业里藏了两个 flag:当访问延迟达到两个特殊的阈值时,flag 便会打印出来,届时你便可以拿着 flag 让你的室友请你吃一顿大餐。
下载到源码后翻一翻,有用的就只有 Phrase.java 和 Instance.java
其中 Phrase.java 定义了 Phrase,其中重载了 equals
方法,其中有:
1 | try { |
所以在每次比较相等的时候就会 sleep 20ms
而 Instance.java 的 post 方法中对于每次的输入,遍历输入的列表,然后逐个加进 HashMap 中
在加入 HashMap 的时候就涉及到判断是否相等
而最终会判断在完成前后的总的时间间隔是多少,如果大于 600ms 就提取出第一个 flag:
1 | var modified = 0; |
而在网页中添加条目的时候,一次只能添加一条,也就是一个 POST 里面只有一个 Phrase
但是源码中有一个循环,遍历整个 input,所以一个 POST 里的内容其实是一个列表
所以可以用 BurpSuite 获取 POST 然后更改一下内容再发送出去(先 random 一个,然后 add)
根据 flag 里的提示,正解(第二顿大餐)应该是使用哈希碰撞,但是不会
p😭q
学会傅里叶的一瞬间,悔恨的泪水流了下来。
当我看到音频播放器中跳动的频谱动画,月明星稀的夜晚,深邃的银河,只有天使在浅吟低唱,复杂的情感于我眼中溢出,像是沉入了雾里朦胧的海一样的温柔。
这一刻我才知道,耳机音响也就图一乐,真听音乐还得靠眼睛。
(注意:flag 花括号内是一个 12 位整数,由 0-9 数位组成,没有其它字符。)
虽然这题是在倒数第三题,还值 400pt,但你一说傅里叶我可就不困了嗷
下载题目包,有一个生成 gif 的 py 代码和那个 gif 文件
正好前面的电波也有一段音频,可以用那个带入到 generate_sound_visualization.py 中生成一个 gif,然后用这个来测试
再仔细看一看 generate_sound_visualization.py 这个文件
主要使用了 librosa
,于是就可以翻文档来看懂这个程序:
1 | y, sample_rate = librosa.load("flag.mp3") # 从mp3中读取数据和采样率 |
然后又通过一些 numpy
的骚操作来生成每一帧的数据,然后通过 array2gif
包的 write_gif 函数来生成 gif
所以主要思路就是把整个程序完整地逆过来
由于必应没有查到 gif2array
的包,所以读取 gif 就用了经典 PIL.Image
:
1 | from PIL import Image |
然后是解决那一大段 numpy 骚操作的逆骚操作(
但是数理基础这么差的我当然是不想仔细研究了,所以直接用电波那题的 radio.mp3 带入,看一看要得到的 spectrogram 是什么样子
输出得到的 spectrogram 是:
1 | [[-58. -48. -30. ... -58. -58. -58.] |
而转置过来是:
1 | [[-58. -58. -58. ... -58. -58. -58.] |
减去 min_db=-60 第一行正好是 2,第二行是 [12. 16. 20. … 18. 16. 14.]
再对应到生成的 gif 文件中,可以看出 gif 的第一帧每个矩形的高度都是 2
而第二帧每个矩形的高度也恰好是刚得出的那组数
所以要得到的 spectrogram 就是 gif 每一帧所有矩形的高度构成的矩阵的转置
再结合源码:
1 | numpy.array([ |
可以看出,每个矩形加上左边的空格正好是 4 个像素,所以每四列读取最后一列即可:
1 | spectrogramT = [] |
这样就得到了梅尔频谱图的数据,可以对 librosa 的部分进行逆过程了
翻 librosa 的文档,有 power_to_db
当然也就有 db_to_power
而且类似于 melspectrogram 函数在 librosa.feature 中,可以专门看 feature 部分的文档
翻到了 inverse 部分,可以看到有一个函数 librosa.feature.inverse.mel_to_audio
可以直接把梅尔频谱图专为音频数据,所以就用它了:
1 | y = librosa.feature.inverse.mel_to_audio( |
这样就完成了还原,最后是输出,但是并没在 librosa 中找到音频输出的函数,所以就用了经典 soundfile
:
1 | import soundfile as sf |
然后打开听就行了,题目说了是个 12 位数,所以剩下的就是英语听力了,翻译过来的数字就是 flag 了
基本上我觉得比较简单的也就这些了,剩下的令我破防的放下一篇_(:з」∠)_
Reference
「Hackergame 2021」#2 Writup 开局上分篇 1