年轻人的第一届Hackergame — Some writeups for USTC Hackergame 2023

5个月前
5个月前
15.5k字
加载中
文章概述 Powered by Google Gemini Beta
Hackergame 2023 比赛包括 15 道题,题涉及内容广泛,包括逆向工程、Web 安全、密码学等领域。文章作者作为一名新手参加了比赛,在 5 天内完成了 7 道题,并提供了每道题的简要解法。 作者通过以下方法解决了问题: * **签到题:**使用麦克风授权获得 Flag。 * **猫咪小测验:**通过 Google 搜索和遍历找到答案。 * **更深更暗:**使用 OBS 录屏,观察 Payload 提交形式,模拟提交获得 Flag。 * **旅行照片 3.0:**通过 Google Lens 识别景点、使用 Python 爆破猜测日期,再搜索东京国立博物馆信息。 * **赛博井字棋:**使用 F12 检查 API,模拟 API 实现。 * **奶奶的睡前 flag 故事:**使用 CVE 漏洞和网站破解工具获得 Flag。 * **组委会模拟器:**使用 Python 线程和 delay 模拟撤回消息。 * **虫:**使用 SSTV 软件接收国际空间站图片。 * **JSON ⊂ YAML?:**对比 JSON 和 YAML 规范,找到浮点差异。 * **Git? Git!:**使用 Git reflog 比较提交差异。 * **HTTP 集邮册:**创建不同类型的 HTTP 请求并观察响应状态码。 * **Docker for Everyone:**使用 Docker 权限挂载指定卷获取 Flag。 * **惜字如金 2.0:**通过反惜字如金化和反referer化,重建字典并解密数据。 * **高频率星球:**将 asciinema 输出导入文件并使用 Node 运行。 * **低带宽星球:**使用无损图像压缩工具压缩图片。

请注意,本文发布时间为2023年11月4日14:01,此时活动已经结束,并不存在作弊和泄漏分享题目题解的问题。

TL;DR

这是我参加的第一届Hackergame,作为一个什么都不会什么都不知道的屑雪生(对于我自己来说)已经算可以了,因为7天的比赛里面有5天我是缺席的(在nbfls),下次可能就要大学再见辣,Hackergame!

题解正文

1. Hackergame,启动!

玩Hackergame玩的

什么签到题(bushi

我们打开题目,然后:什么启动?还要我录音?还要我的麦克风权限?去你的罢!

然而最后还是开启了麦克风权限

开启了麦克风权限,点击提交,可以看到浏览器地址栏多了/?similarity=显示为相似度,根据下面的提示说得不够标准哦,相似度大于 99.9% 才能拿到 flag。,可以知道直接query设置成99.9以上就可以成功拿到Flag辣!

最后点击获取Flag,然后是高仿某你说的对游戏的UI(x

EOF

2. 猫咪小测喵

1. 想要借阅世界图书出版公司出版的《A Classical Introduction To Modern Number Theory 2nd ed.》,应当前往中国科学技术大学西区图书馆的哪一层?(30 分)
提示:是一个非负整数。

不是ustc猫喵,不知道喵,遍历喵,得到12喵。

2. 今年 arXiv 网站的天体物理版块上有人发表了一篇关于「可观测宇宙中的鸡的密度上限」的论文,请问论文中作者计算出的鸡密度函数的上限为 10 的多少次方每立方秒差距?(30 分)
提示:是一个非负整数。

在arXiv翻遍了喵,找到了这篇论文喵,论文中的abstract(We find the most restrictive upper limit from the domains considered to be 10^23 pc^−3 , which ruffles the feathers of long-standing astrophysics theory.)中提示为23喵。

及格了喵,好耶喵!

3. 为了支持 TCP BBR 拥塞控制算法,在编译 Linux 内核时应该配置好哪一条内核选项?(20 分)
提示:输入格式为 CONFIG_XXXXX,如 CONFIG_SCHED_SMT。

网上翻到了这篇文章喵,在自己的Linux电脑或者是容器输入zcat /proc/config.gz | grep BBR,得到CONFIG_TCP_CONG_BBR

实测执行结果喵(Kernel版本为6.5.9-arch2-1喵)

chihuo2104@konnyaku ~> zcat /proc/config.gz | grep BBR
CONFIG_TCP_CONG_BBR=m
4. 🥒🥒🥒:「我……从没觉得写类型标注有意思过」。在一篇论文中,作者给出了能够让 Python 的类型检查器 MyPY mypy 陷入死循环的代码,并证明 Python 的类型检查和停机问题一样困难。请问这篇论文发表在今年的哪个学术会议上?(20 分)
提示:会议的大写英文简称,比如 ISCA、CCS、ICML。

直接上Google搜索喵,搜索到了这篇论文喵,又根据这篇论文搜索到作者的博客喵,在作者的博客中介绍了参与的大会(ECOOP)喵。

满分了耶喵!

EOF

3. 更深更暗

点进去,一看这么多的东西,肯定一直往下划啊,什么,有潜水艇???

不行,再来一次,这次我可是开了OBS录屏的!

成功搞到Flag!

EOF

4. 旅行照片 3.0

原来做这道题其实没有什么思路的,也是今天凌晨想到的思路。

第一张照片是诺贝尔奖章(用Google Lens搜了看),然后突然发现下面有个MMII,怀疑是罗马数字,遂搜索,得到2002,获得学长所在学校为东京大学。

于是在东京大学的诺贝尔奖获得者中搜索,得到了这个人出生最晚,其任职于東京大学宇宙線研究所(ICRR)

第二个也找不太到,于是使用python爆破了一下,爆破出来的答案是2023-08-10

爆破代码:

# encoding = utf8
# Powered by chihuo2104(c)2018-2023.
import random
import time
import urllib.parse

import requests
import base64

import urllib3

cookie = "Cookie喵"

for i in [7,8]:
 for j in range(1,32):
 ans = ""
 if j < 10:
 ans = base64.b64encode(("Answer1=2023-0" + str(i) + "-0" + str(j) + "&Answer2=ICRR").encode()).decode() + ".txt"
 else:
 ans = base64.b64encode(("Answer1=2023-0" + str(i) + "-" + str(j) + "&Answer2=ICRR").encode()).decode() + ".txt"
 resp = requests.post("http://202.38.93.111:12345/", headers={
 "cookie": cookie,
 }, data=ans)
 print(ans, str(i) + "-" + str(j), resp.status_code, resp.text)
 time.sleep(random.randint(1,5))

第一题完成!

既然拿到了日期2023-08-10,再结合Google Lens搜索第二张图,可以鉴定拍摄地点为上野公园大喷泉,在日文环境下搜索上野公園 大噴水 8月10日(木),可以得到帐篷里的活动是全国梅酒まつりin東京2023,其志愿者招募的网址链接是https://umeshu-matsuri.jp/tokyo_staff/,获得问卷编号S495584522

然后再搜索东京国立博物馆的票价就行了(但是最后我测试出来是0JPY(恼)

第二题完成!

第三题我只找到上野站卖的那个粉色海报上面是熊猫,还是太屑了...

EOF

5. 赛博井字棋

看到这井字棋我忍不住玩了30分钟,想人力打赢AI(x

然而最后还是开了F12...

打开F12->Networks看了下Payload,发现:

按下Reset时: POST发送了{"act": "reset"}

开始对局时候:POST发送了{x: "[下子X坐标]", y: "[下子Y坐标]"}

座标系如下:

(0,0)|(1,0)|(2,0)
(0,1)|(1,1)|(2,1)
(0,2)|(1,2)|(2,2)

然后我就想了,可不可以复写AI的落子呢,没想到成了!

打开postman(这么小工作量都不用请出python杀手锏),然后POST,先reset,然后再输入坐标(0,0),(1,1),(2,2),然后就取得了Flag!

EOF

6. 奶奶的睡前 flag 故事

其实去Google上面搜索Pixel screenshot hack就可以知道有个CVE漏洞(pixel的图片编辑器只是把png的END符往前面移了而没全删),然后使用这个网站,机型选择Pixel3就可以取得Flag了!

其实我原来还考虑的是IEDF破解没想到是这破玩意(恼

EOF

7. 组委会模拟器

其实我原来是想尝试自己一个一个点没的,后面人都眼花缭乱了,遂打开F12

打开F12,可以发现几个API:

  • /api/getMessages:获取信息列表,列表是json文件,里面包含了delay(消息几秒钟后发送),和消息内容,我们只要这几个就可以了。
  • /api/deleteMessage: 撤回符合要求的信息,payload是json({"id":[Message id])
  • /api/getflag: 获取flag(所有消息撤回后使用)

这个只需要使用python模拟一下就好了

之前用的是for循环delay什么的,反正很复杂,然后有些时候就会快,有些时候就会慢...

现在的代码用的是先循环消息数组,符合的消息开线程撤回,线程delay结束后自动请求,可以保证3s内撤回

代码如下:

# encoding = utf8
# Powered by chihuo2104(c)2018-2023.
import json
import math
import time
import datetime
import requests
from threading import Thread, activeCount


cookie = "cookie喵"

# post请求撤回
def postans(id, delay):
 print(id, delay)
 time.sleep(delay)
 jsondata = json.dumps(dict(id=id))
 resp2 = requests.post("http://202.38.93.111:10021/api/deleteMessage", headers={
 "Cookie": cookie,
 "Content-Type": "application/json",
 }, data=jsondata)
 print(id, resp2.text)

# 获取flag喵
def getFlag(delay):
 time.sleep(delay + 1)
 resp3 = requests.post("http://202.38.93.111:10021/api/getflag", headers={
 "Cookie": cookie,
 "Content-Type": "application/json",
 })
 print(resp3.text)


resp = requests.post("http://202.38.93.111:10021/api/getMessages", headers={
 "Cookie": cookie,
 "Content-Type": "application/json",
})
messages = json.loads(resp.text)
for i in range(0, len(messages["messages"])):
 # print(delay)
 # print(messages["messages"][i])
 if "hack[" in messages["messages"][i]["text"] and "]" in messages["messages"][i]["text"] and \
 messages["messages"][i]["text"].lower() == messages["messages"][i]["text"]:
 print(i, messages["messages"][i])
 t = Thread(target=postans, args=(i, messages["messages"][i]["delay"]))
 t.start()
t2 = Thread(target=getFlag, args=(messages["messages"][len(messages["messages"]) - 1]["delay"],))
t2.start()

EOF

8. 虫

根据题目中的描述这听起来像是一种通过无线信道传输图片的方式,如果精通此道,或许就可以接收来自国际空间站(ISS)的图片了。,网上搜得这种方式叫做SSTV。

PC端装的Linux,Wine跑RX-SSTV又跑不起来,遂作罢,然后发现手机版本也有这种APP(Robot36),下载了,下载好之后,播放这段声音,就可以获得了!

小插曲:我的Note12R甚至还打不开麦克风,于是拿出了我那遥遥领先的VivoX6s,发现麦克风坏了,没办法,只好拿出最为先进的SD410了,没想到它没有让我失望!

EOF

9. JSON ⊂ YAML?

翻了Yaml和Json各自的规范文件

但是我只看出了浮点的问题(Json的浮点1e-23会被yaml1.1解析成字符串)

EOF

10. Git? Git!

由于git的特性,提交了之后如果撤回了commit,没有执行git gc的话是不会彻底删除撤回的commit的,这时候我们只要看reflog进行比对即可。

我们先下载这个文件夹,然后使用git reflog读取拥有的ref,再对它进行diff

[git reflog的结果]

chihuo2104@konnyaku ~/D/ML-Course-Notes (main)> git reflog
036a4dd (HEAD -> main) HEAD@{0}: commit: test
ea49f0c HEAD@{1}: commit: Trim trailing spaces
15fd0a1 (origin/main, origin/HEAD) HEAD@{2}: reset: moving to HEAD~
505e1a3 HEAD@{3}: commit: Trim trailing spaces
15fd0a1 (origin/main, origin/HEAD) HEAD@{4}: clone: from https://github.com/dair-ai/ML-Course-Notes.git

我们看到了HEAD@{3}HEAD@{4},对它们进行diff,就可以获得flag辣!(其实你也可以几个HEAD都试试)

git diff HEAD@{3} HEAD@{4}

diff --git a/README.md b/README.md
index f629dae..8f13fd4 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A place to collaborate and share lecture notes on all topics related to machine
 
 ---
 
-### Machine Learning Specialization (2022)
+### Machine Learning Specialization (2022) 
 [Website](https://www.coursera.org/specializations/machine-learning-introduction) | Instructor: Andrew Ng
 
 <table class="tg">
@@ -16,7 +16,7 @@ A place to collaborate and share lecture notes on all topics related to machine
 <th class="tg-yw4l"><b>Notes</b></th>
 <th class="tg-yw4l"><b>Author</b></th>
 </tr>
- <!-- flag{TheRe5_@lwAy5_a_R3GreT_pi1l_1n_G1t} -->
+ 
 <tr>
 <td class="tg-yw4l">Introduction to Machine Learning</td>

EOF

11. HTTP 集邮册

点进去,就是创造HTTP请求的文本辣!

可以随便乱玩就能拿到的请求:

  1. 200 OK(直接发!)
GET / HTTP/1.1\r\n
Host: example.com\r\n\r\n
  1. 404 Not Found(随便找个没的页面)
GET /neko.miao HTTP/1.1\r\n
Host: example.com\r\n\r\n
  1. 400 Bad Request(乱写一通)
/ HTTP/1.1\r\n
Host: example.com\r\n\r\n
  1. 405 Method Not Allowed(乱改请求动词字符串)
POST / HTTP/1.1\r\n
Host: example.com\r\n\r\n
  1. 505 HTTP Version Not Supported(乱改http版本)
GET / HTTP/114514\r\n
Host: example.com\r\n\r\n

(你看5个不就有了吗) 6. 414 Request-URI Too Large(超长url链接)

GET /?yajuu=(超级长的恶臭字符)
Host: example.com\r\n\r\n

接下来是查文档才能拿到的请求

  1. 100 Continue
GET / HTTP/1.1\r\n
Host: example.com\r\n
Expect: 100-continue\r\n\r\n
  1. 206 Partial Content(Range获取部分内容)
GET / HTTP/1.1\r\n
Host: example.com\r\n
Range: bytes=114-514\r\n\r\n
  1. 304 Not Modified(缓存匹配)

    官方题解用的是If-Modified-Since,我做题的时候用的是If-Non-Match和html的那个ETag匹配

GET / HTTP/1.1\r\n
Host: example.com\r\n
If-None-Match: "返回的ETag"\r\n\r\n
  1. 412 Precondition Failed(缓存匹配不起了)
GET / HTTP/1.1\r\n
Host: example.com\r\n
If-Match: "yajuu"\r\n\r\n
  1. 413 Payload Too Large(服务器怀疑你请求太大了)
GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Length: 1145141919810\r\n
  1. 416 Range Not Satisfiable(超流了)
GET / HTTP/1.1\r\n
Host: example.com\r\n
Range: bytes=114514-1919810\r\n\r\n
  1. 无状态码(官方说是HTTP/0.9):
GET /r\n\r\n

[我的集邮结束]

接下来是官方题解的附加内容 14. 501 Not Implemented(在nginx中,Transfer-Encoding不支持除chunk外的所有内容)

GET / HTTP/1.1\r\n
Transfer-Encoding: gzip\r\n
Host: example.com\r\n\r\n

EOF

12. Docker for Everyone

这篇文章得知Docker用户有权限开容器之后开启容器的时候挂载卷的时候是可以以root权限挂载的

于是只要挂载flag所在卷即可获得Flag:

alpine:~$ docker run -it -v /flag:/flag --rm alpine cat /flag
flag{u5e_r00t1ess_conta1ner_c89d12d724_plz!}

所以为什么不用Podman呢(乐

EOF

13. 惜字如金 2.0

下载了Python小代码,代码如下:

#!/usr/bin/python3

# Th siz of th fil may reduc after XZRJification

def check_equals(left, right):
 # check whether left == right or not
 if left != right: exit(1)

def get_cod_dict():
 # prepar th cod dict
 cod_dict = []
 cod_dict += ['nymeh1niwemflcir}echaet']
 cod_dict += ['a3g7}kidgojernoetlsup?h']
 cod_dict += ['ulw!f5soadrhwnrsnstnoeq']
 cod_dict += ['ct{l-findiehaai{oveatas']
 cod_dict += ['ty9kxborszstguyd?!blm-p']
 check_equals(set(len(s) for s in cod_dict), {24})
 return ''.join(cod_dict)

def decrypt_data(input_codes):
 # retriev th decrypted data
 cod_dict = get_cod_dict()
 output_chars = [cod_dict[c] for c in input_codes]
 return ''.join(output_chars)

if __nam__ == '__main__':
 # check som obvious things
 check_equals('creat', 'cr' + 'at')
 check_equals('referer', 'refer' + 'rer')
 # check th flag
 flag = decrypt_data([53, 41, 85, 109, 75, 1, 33, 48, 77, 90,
 17, 118, 36, 25, 13, 89, 90, 3, 63, 25,
 31, 77, 27, 60, 3, 118, 24, 62, 54, 61,
 25, 63, 77, 36, 5, 32, 60, 67, 113, 28])
 check_equals(flag.index('flag{'), 0)
 check_equals(flag.index('}'), len(flag) - 1)
 # print th flag
 print(flag)

先把它反惜字如金化,反creat原则,获得代码如下:

#!/usr/bin/python3

# Th size of the file may reduce after XZRJification

def check_equals(left, right):
 # check whether left == right or not
 if left != right: exit(1)

def get_code_dict():
 # prepare the code dict
 code_dict = []
 code_dict += ['nymeh1niwemflcir}echaet'] # 0-23
 # l #41?
 code_dict += ['a3g7}kidgojernoetlsup?h'] # 24-47
 code_dict += ['ulw!ff5soadrhwnrsnstnoeq'] # 48-71
 # a #85?
 code_dict += ['ct{{l-findiehai{oveatas'] # 72-95
 # g #109?
 code_dict += ['ty9kxborszstguyd?!blm-p'] # 96-119
 check_equals(set(len(s) for s in code_dict), {24})
 return ''.join(code_dict)

def decrypt_data(input_codes):
 # retrieve the decrypted data
 code_dict = get_code_dict()
 output_chars = [code_dict[c] for c in input_codes]
 return ''.join(output_chars)

if __name__ == '__main__':
 # check some obvious things
 check_equals('create', 'cre' + 'ate')
 check_equals('referer', 'refe' + 'rer')
 # check the flag
 flag = decrypt_data([53, 41, 85, 109, 75, 1, 33, 48, 77, 90,
 17, 118, 36, 25, 13, 89, 90, 3, 63, 25,
 31, 77, 27, 60, 3, 118, 24, 62, 54, 61,
 25, 63, 77, 36, 5, 32, 60, 67, 113, 28])
 print(flag)

给你的初始字典都是长度23,所以你基本上无法运行这段代码,于是我们需要进行反referer原则。

根据代码,其实就是一个字典(长度120)然后取数组值,其实很简单,前5位(53,41,85,109)对应'f','l','a','g','{',最后一位28对应'}',从字典中读取,如果发现字典不对应,就在前面补一个辅音。

最后可以发现第一个字典有点问题,只要在不干扰6个固定字符的情况下添加双写辅音即可。

最后的代码是这样的:

#!/usr/bin/python3

# Th size of the file may reduce after XZRJification

def check_equals(left, right):
 # check whether left == right or not
 if left != right: exit(1)

def get_code_dict():
 # prepare the code dict
 code_dict = []
 code_dict += ['nymeh1niwemflcir}echaett'] # 0-23
 # l #41?
 code_dict += ['a3g7}kidgojernoetllsup?h'] # 24-47
 code_dict += ['ulw!ff5soadrhwnrsnstnoeq'] # 48-71
 # a #85?
 code_dict += ['ct{{l-findiehaai{oveatas'] # 72-95
 # g #109?
 code_dict += ['ty9kxborszstgguyd?!blm-p'] # 96-119
 check_equals(set(len(s) for s in code_dict), {24})
 return ''.join(code_dict)

def decrypt_data(input_codes):
 # retrieve the decrypted data
 code_dict = get_code_dict()
 output_chars = [code_dict[c] for c in input_codes]
 return ''.join(output_chars)

if __name__ == '__main__':
 # check some obvious things
 check_equals('create', 'cre' + 'ate')
 check_equals('referer', 'refe' + 'rer')
 # check the flag
 # 53 -> f 41
 flag = decrypt_data([53, 41, 85, 109, 75, 1, 33, 48, 77, 90,
 17, 118, 36, 25, 13, 89, 90, 3, 63, 25,
 31, 77, 27, 60, 3, 118, 24, 62, 54, 61,
 25, 63, 77, 36, 5, 32, 60, 67, 113, 28])
 print(flag)

运行就能获得flag辣!

EOF

14. 🪐 高频率星球

根据这篇文章,我们可以把asciinema输出的内容导入到一个文件:

asciinema cat asciinema_restore.rec > a.js

打开a.js,替换无关字符,使用node运行即可获得flag辣!

EOF

15. 🪐 低带宽星球

第一题做出来了,用的图片压缩器(完全无损)是G家的optipng无损压缩把5.8K->1.8K,网上的那些压缩都是有损压缩...

第二题原来想通过构造PNG文件骗过pyvips的,但是因为时间问题遂没实现。

EOF

看了官方题解的一些想法

1. 旅行照片 3.0

我其实是没有留意学长的STATPHYS28字样,所以我第五题也做不出来,原来是这样。然后0JPY的话原来是因为东京大学在东京国立博物馆常驻免费名单内(

第六题是我没看清,一直在翻上野站,而不知道是渋谷站。

2. 组委会模拟器 & 赛博井字棋

没想到可以直接用js完成(

3. JSON ⊂ YAML?

没想到json规范除了ecma404还有一个RFC 8259(

4. 惜字如金2.0

其实我也是想用遍历遍历出字典的( 但是后面感觉还是自己推理加枚举快(

5. 🪐 小型大语言模型星球

没想到第二题Accept是要枚举的((

第三题第四题得好好学学解法,攻击LLM是吧(

6. 🪐 低带宽星球

果然第二道题是攻击libvips( pillow:

但是不是空造一个PNG(

最后的赢家居然是JPEG XL(

7. Komm, süsser Flagge

  1. 嗯?还能这么玩??分包发送是真的6
  2. 人晕了...
  3. 看都看不懂呜呜呜

8. 为什么要打开 /flag 😡

看不懂呜呜呜,留着

9. 异星歧途

其实没时间熟悉这个游戏(((

woc好抽象,想到了最近在学的电控(

10. 微积分计算小练习 2.0

其实我已经XSS进了留言界面(就是用双引号),但是看不懂这个bot在干什么(

我甚至忘记了{} 也可以执行js脚本,草!悔恨终身

11. 逆向工程不需要 F5

草,根本不会用,留着

12. O(1) 用户登录系统

oh,原来是碰撞,也留着

13. 链上猎手

草这居然是区块链,也留着

14. It's MyCalculator!!!!! && 小 Z 的谜题 && 黑客马拉松 && 不可加密的异世界 2 && 旧日之痕

CVE、三维积木和密码学。没看懂,留着

End

孩子的屑题解上官方Writeup了😭

最后还是拿了2.9k分( 排名240左右,和SCI一个分数,SCI强强!

马上也要期中统考了,祝自己期中统考可以考出自己理想的成绩,Hackergame 2025见!

See Also

hackergameustc2023ctf信息安全计算机

Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v3.0.0-alpha.1