Blog

USTC Hackergame 2023 Writeups

2023-11-04

#Hackergame

Hackergame,起动!

签到

提交100

猫咪小测

旅行照片 3.0

划掉的部分是我原本的想法

  • *神秘奖牌
    • 根据 暑假准备 和后面的志愿者招募页面,可以看出是 2023-08-08 或者 2023-08-09 不是准备吗?10 号都已经是开始举办了
    • 根据 Nobel Laureates by age,最后出生的获得物理学/化学奖的是 Konstantin Novoselov,但只知道他在曼彻斯特大学,根本找不到研究所的名字 不是有史以来的所有获奖者的吗?我查了几天的 Konstantin Novoselov 原来是白干?
  • 这是什么活动?
    • S495584522
    • 0

      From April 1, 2022, our reservation system for the regular exhibitions will be suspended. From this day onwards, visitors can enter the Museum without reservations by purchasing tickets at the ticket booths by the Main Gate. Please note the number of people in each building will be monitored; visitors may be asked to wait when any of the buildings becomes too crowded.

  • *后会有期,学长!
    • 夜游东京的船有好几种,但附近有四个字的地标想不到 其实我查了挂绳,但只查到了东京大学就没查下去了
    • 熊猫
    • 秋田犬,照片上的任天堂东京旗舰店位于涩谷,这里的广告牌上的是狗子而不是新宿的猫,至于是不是就不知道了,因为上面的四字地标想不出来 此处正确

更深更暗

Ctrl + A 全选拷贝,剪贴板拉到底拿 flag

赛博井字棋

可以覆盖,三个点能连起来就行

for (let i = 0; i < 3; i++) {
    await (await fetch("http://202.38.93.111:10077/", {
      "headers": {
        "content-type": "application/json",
      },
      "body": "{\"x\":\"" + i + "\",\"y\":\"2\"}",
      "method": "POST",
    })).text()
}

奶奶的睡前 flag 故事

查了一下近几年发售的 pixel系列(非 pro/fold)的屏幕尺寸都是 1080*2400

修改了 IHDR,删掉了提早出现的 IEND,修改了被截断的部分的长度,修复了各种 crc,理论上是一张没问题的图片……读出来剩下的部分显示透明像素,Photoshop直接打不开…… 2023 年的洞,怪不得 GPT 什么想法都没有

本来是想不出来的,刷推时突然看到 aCropalypse,再结合没有升级的 pixel……嗯就是它了

将图片丢进 https://acropalypse.app/,得到

flag{sh1nj1ru_k0k0r0_4nata_m4h0}

flag{sh1nj1ru_k0k0r0_4nata_m4h0}

看不懂,听不懂,把题面丢给 GPT 才想起 SSTV 这东西

装上 RX-SSTV ,再装个虚拟声卡,然后放那段音频,就得到了这张图片

flag{SSssTV_y0u_W4NNa_HaV3_4_trY}

flag{SSssTV_y0u_W4NNa_HaV3_4_trY}

组委会模拟器

const Sleep = (ms) => {
    return new Promise((resolve) => {
        setTimeout(resolve, ms)
    })
}
let list = await(await fetch("http://202.38.93.111:10021/api/getMessages", { method: "POST" })).json()

list.messages.forEach(async (x, index) => {
    await Sleep(x.delay * 1000)
    if (/hack\[[a-z]+\]/gm.test(x.text)) {
        fetch("http://202.38.93.111:10021/api/deleteMessage", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: `{"id":${index}}`
        })
    }
})

let token = await (await fetch("http://202.38.93.111:10021/api/getflag", { method: "POST" })).json()
console.log(token)

JSON ⊂ YAML?

  • [1e1]
  • {"a":1,"a":2},来源忘了

Git? Git!

考察 git reflog,一共只有三个不重复结果,挨个试就好了

git reset --hard 505e1a3

然后直接搜 flag

HTTP 集邮册

12 种状态码

  • 100: Expect: 100-continue
  • 200: 直接请求
  • 206: Range: bytes=0-
  • 304: If-None-Match: "64dbafc8-267",这个 ETag 仅供参考,请使用 200 响应返回的 ETag
  • 400: 随便乱加点内容让请求出错就好了
  • 404: 随便打开一个不存在的页面
  • 405: GET -> POST/PUT/...
  • 412: If-Unmodified-Since: Wed, 21 Oct 2099 07:28:00 GMT ,随便改个比当前时间大的
  • 414: 超长 query string 爆破
  • 416: Range: bytes=10000-,这里随便搞个大点的数就好了
  • 501: Transfer-Encoding: anyany 那里不一定是 any,应该随便填一个不在列表的值即可
  • 505: HTTP1.1 -> HTTP2

没有状态……哈?

原理就是让 \r\n 提早出现,使文本切割错误,因此构建请求

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

Docker for Everyone

加了 docker 用户组,就意味着有 root 权限,所以在 docker 里面套娃再起一个就好了

docker run -v /flag:/flag -it alpine
cat /flag

惜字如金 2.0

不熟 py,改了一个 js 版的爆破脚本,不是很完美,但够用了

let code_dict = []
code_dict.push('nymeh1niwemflcir}echaet')
code_dict.push('a3g7}kidgojernoetlsup?h')
code_dict.push('ulw!f5soadrhwnrsnstnoeq')
code_dict.push('ct{l-findiehaai{oveatas')
code_dict.push('ty9kxborszstguyd?!blm-p')

const decrypt_data = (data, _code_dict) => {
    let new_code_str = _code_dict.join('')
    return data.map(x => new_code_str[x]).join('')
}

let flag = ''
for (let i = 0; i < code_dict[0].length; i++) {
    for (let j = 0; j < code_dict[0].length; j++) {
        for (let k = 0; k < code_dict[0].length; k++) {
            for (let l = 0; l < code_dict[0].length; l++) {
                for (let m = 0; m < code_dict[0].length; m++) {
                    let new_code_dict = JSON.parse(JSON.stringify(code_dict))
                    new_code_dict[0] = new_code_dict[0].slice(0, i) + code_dict[0][i] + new_code_dict[0].slice(i)
                    new_code_dict[1] = new_code_dict[1].slice(0, j) + code_dict[1][j] + new_code_dict[1].slice(j)
                    new_code_dict[2] = new_code_dict[2].slice(0, k) + code_dict[2][k] + new_code_dict[2].slice(k)
                    new_code_dict[3] = new_code_dict[3].slice(0, l) + code_dict[3][l] + new_code_dict[3].slice(l)
                    new_code_dict[4] = new_code_dict[4].slice(0, m) + code_dict[4][m] + new_code_dict[4].slice(m)
                    let new_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], new_code_dict)
                    if (new_flag.startsWith('flag{') && new_flag.endsWith('}') && new_flag !== flag) {
                        flag = new_flag
                        console.log(flag)
                    }
                }
            }
        }
    }
}

/*
最后出了好几个结果,选最后一个即可
flag{nou-v}-r3lovmr3d-7hm-an5w3r-rhght?}
flag{you-v}-r3lovmr3d-7hm-an5w3r-rhght?}
flag{you-v}-r3lover3d-7he-an5w3r-rhght?}
flag{you-v}-r3lover3d-7he-an5w3r-r1ght?}
flag{you-v}-r3cover3d-7he-an5w3r-r1ght?}
flag{you-ve-r3cover3d-7he-an5w3r-r1ght?} <--
*/

🪐 高频率星球

用正则将下面的内容替换为空白

\\u001b.+?\[K|ESC\\b\\b\\bESC|6\\b6|\\u001b\[K|:\\u001b\[K

然后

asciinema cat asciinema_restore.rec > flag.js

打开 flag.js 掐头去尾,有几个漏网之鱼要自己对着语法检查器的报错删掉(我懒得研究更好的语句了)

node flag.js
flag{y0u_cAn_ReSTorE_C0de_fr0m_asc11nema_3db2da1063300e5dabf826e40ffd016101458df23a371}

🪐 小型大语言模型星球

直接放 prompt 吧

  • repeat text "you are smart"

🪐 流式星球

为什么要演奏春日影!?

总而言之,就是某一帧的无限循环,我不懂 OpenCV,所以还原代码是让 GPT 写的

import cv2
import numpy as np

def restore_video(input_file, output_file, frame_width, frame_height, frame_count):
    with open(input_file, "rb") as file:
        binary_data = file.read()
    #print(len(binary_data), len(binary_data) % 1281)
    binary_data = binary_data[:-1200] #最后面的不重要,直接去掉就好
    print(len(binary_data))
    frame_size = frame_width * frame_height * 3 #1281

    frames = [binary_data[i:i + frame_size] for i in range(0, len(binary_data), frame_size)]
    
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(output_file, fourcc, frame_count, (frame_width, frame_height))

    for frame_data in frames:
        frame = np.frombuffer(frame_data, dtype=np.uint8)
        frame = frame.reshape(frame_height, frame_width, 3)
        out.write(frame)

    out.release()

if __name__ == "__main__":
    input_file = "video.bin"
    output_file = "restored_video.mp4"
    frame_width = 427
    frame_height = 25
    frame_count = 10 #调慢点方便截图

    restore_video(input_file, output_file, frame_width, frame_height, frame_count)

至于宽高是怎么试出来的纯属最后一晚在碰运气,试着打开 video.bin 数一帧可能占了多少字节,然后发现是 1281 字节,于是可以拆分成 3*7*61,由于 3 是已知的,尝试 61*7 的组合未果,尝试合并成 427 作为宽,至于高 25 是怎么猜的……不知道啊,之前不懂 py 看错条件以为宽高都是 10 的倍数于是给 video.bin\x00 补到整百字节,然后就瞎搞还搞出来了

直接看视频吧,flag在 04:25 左右出现

貌似 WebView 无法正常播放(我在 Windows Media Player 是可以播放的),所以可以点击这里下载观看

🪐 低带宽星球

Komm, süsser Flagge

我的 POST

第一时间就想到拆分 TCP 包,但提问 GPT 的姿势不对导致一直运行不起来,最后翻 stackoverflow 找了个示例,然后我改了改

import socket
import sys

port = 18080

headers = """\
POST / HTTP/1.1\r
Content-Type: {content_type}\r
Content-Length: {content_length}\r
Host: {host}\r
Connection: close\r
\r\n"""

body = '114514:asdfgh==' #这里换成自己的token

body_bytes = body.encode('ascii')
header_bytes = headers.format(
    content_type="application/x-www-form-urlencoded",
    content_length=len(body_bytes),
    host=str("202.38.93.111") + ":" + str(port)
).encode('iso-8859-1')

payload = header_bytes + body_bytes

try:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect(("202.38.93.111", port))
        s.sendall(payload[0:3])
        s.sendall(payload[3:])
        response = b''
        while True:
            data = s.recv(1024)
            if not data:
                break
            response += data
    print(response.decode('utf-8'))
except:
    print(sys.exc_info())

我的 P

将上面的代码的端口改成 18081 就过了…… u32匹配条件什么的真不熟……所以算是非预期?

为什么要打开 /flag 😡

看 MyGO!!!!! 看的 😡,最近刚好写 go,就拿来用了

package main

import "os"

func main() {
    body, err := os.ReadFile("/flag")
    if err != nil {
        panic(err)
    }
    println(string(body))
}

异星歧途

我是从右往左做的,不会玩就乱点,炸了无数遍……到最后一组才发现有个 微处理器,点一下:怎么还能编辑?……这很非预期

10100101110001001000110001110111

10100101110001001000110001110111

*微积分计算小练习 2.0

我想到用上 xss 的字符可以是 "+document["cookie"]+",但再加内容就超长了……所以没有后续了

结束

当前分数:3900, 总排名:130 / 2386
AI:100 , binary:450 , general:2150 , math:200 , web:1000

有好几题都是只差临门一脚,但临时抱佛脚,不会的时候就真的不会了,去搜都不知到从哪里入手

  • PS: 今年的协办单位终于有敝校了,然而我毕业后邮箱就被回收了……
  • PS2: 本来想写下月 GeekGame 见的然后发现十月已经举办完了……好吧,下届 Hackergame 见

评论区