USTC Hackergame 2022 Write Up

技术向约 1.6 千字

签到

日常改参数。

猫咪问答喵

  1. 中国科学技术大学 NEBULA 战队(USTC NEBULA)是于何时成立的喵?

    在这个页面中有这样一段介绍:

    星云战队(Nebula)

    中国科学技术大学“星云战队(Nebula)”成立于 2017 年 3 月,“星云”一词来自中国科学技术大学 BBS“瀚海星云”,代表同学们对科学技术的无限向往和追求。战队现领队为网络空间安全学院吴文涛老师,现任队长为网络空间安全学院李蔚林、童蒙和武汉。战队核心成员包括了来自网络空间安全学院、少年班学院、物理学院、计算机学院等各个院系的同学,充分体现了我校多学院共建网络空间安全一级学科的特点。战队以赛代练,以赛促学,在诸多赛事中获得佳绩。

    可知答案为 2017-03

  1. 22 年坚持,小 C 仍然使用着一台他从小用到大的 Windows 2000 计算机。那么,在不变更系统配置和程序代码的前提下,Firefox 浏览器能在 Windows 2000 下运行的最后一个大版本号是多少?

    Google 搜索 windows 2000 firefox 可以搜索到一个帖子:Last version of fireFox to work on Windows 2000?,可知答案为 12。

  2. 你知道 PwnKit(CVE-2021-4034)喵?据可靠谣传,出题组的某位同学本来想出这样一道类似的题,但是发现 Linux 内核更新之后居然不再允许 argc 为 0 了喵!那么,请找出在 Linux 内核 master 分支(torvalds/linux.git)下,首个变动此行为的 commit 的 hash 吧喵!

    关于这部分的限制在 fs/exec.c 文件下,那么 git blame 可知这部分是在 dcd46d8 中被修改的。

家目录里的秘密

VS Code 里的 flag

全局搜索 flag{ 可得:

rclone 里的 flag

rclone.conf 里可以找到一个密码,通过在 Google 上搜索可以找到 一份现成的解密代码

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "errors"
    "fmt"
    "log"
)

// crypt internals
var (
    cryptKey = []byte{
        0x9c, 0x93, 0x5b, 0x48, 0x73, 0x0a, 0x55, 0x4d,
        0x6b, 0xfd, 0x7c, 0x63, 0xc8, 0x86, 0xa9, 0x2b,
        0xd3, 0x90, 0x19, 0x8e, 0xb8, 0x12, 0x8a, 0xfb,
        0xf4, 0xde, 0x16, 0x2b, 0x8b, 0x95, 0xf6, 0x38,
    }
    cryptBlock cipher.Block
    cryptRand  = rand.Reader
)

// crypt transforms in to out using iv under AES-CTR.
//
// in and out may be the same buffer.
//
// Note encryption and decryption are the same operation
func crypt(out, in, iv []byte) error {
    if cryptBlock == nil {
        var err error
        cryptBlock, err = aes.NewCipher(cryptKey)
        if err != nil {
            return err
        }
    }
    stream := cipher.NewCTR(cryptBlock, iv)
    stream.XORKeyStream(out, in)
    return nil
}

// Reveal an obscured value
func Reveal(x string) (string, error) {
    ciphertext, err := base64.RawURLEncoding.DecodeString(x)
    if err != nil {
        return "", fmt.Errorf("base64 decode failed when revealing password - is it obscured? %w", err)
    }
    if len(ciphertext) < aes.BlockSize {
        return "", errors.New("input too short when revealing password - is it obscured?")
    }
    buf := ciphertext[aes.BlockSize:]
    iv := ciphertext[:aes.BlockSize]
    if err := crypt(buf, buf, iv); err != nil {
        return "", fmt.Errorf("decrypt failed when revealing password - is it obscured? %w", err)
    }
    return string(buf), nil
}

// MustReveal reveals an obscured value, exiting with a fatal error if it failed
func MustReveal(x string) string {
    out, err := Reveal(x)
    if err != nil {
        log.Fatalf("Reveal failed: %v", err)
    }
    return out
}

func main() {
    fmt.Println(MustReveal("tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ"))
}

跑一遍就出来了:

HeiLang

打开 VSCode,将 \| ([\d]+)\] = ([\d]+) 递归替换为 ] = $2\na[$1] = $2,然后执行脚本即可得到 flag:

Xcaptcha

在打开验证码页面的一瞬间将这段 JS 脚本塞进控制台里即可。

for (let i = 1; i <= 3; i++) {
  let raw_str = document.querySelector(`label[for="captcha${i}"]`).innerHTML;
  let match = /([\d]+)\+([\d]+)/.exec(raw_str);
  let sum = BigInt(match[1]) + BigInt(match[2]);
  document.getElementById('captcha' + i).value = sum.toString();
}

document.getElementById('submit').click();

旅行照片 2.0

照片分析

exiftool 一把梭。

LaTeX 机器人

纯文本

众所周知,LaTeX 有一个 \input 指令:

Flag 到手:flag{becAr3fu11dUd3c5a1b17ffa}

安全的在线测评

无法 AC 的题目

最开始我打算在程序里读文件,结果不知道为什么写挂了……

于是就立马想到了用汇编读文件,代码和动态数据差不多。

动态数据

查看评测机的源码可以发现 n = 5,那么可以用汇编在编译的时候读文件:

#include <stdio.h>
#include <string.h>

asm("staticInput: .incbin \"data/static.in\"");
asm("staticAnswer: .incbin \"data/static.out\"");
asm("dynamicInput0: .incbin \"data/dynamic0.in\"");
asm("dynamicInput1: .incbin \"data/dynamic1.in\"");
asm("dynamicInput2: .incbin \"data/dynamic2.in\"");
asm("dynamicInput3: .incbin \"data/dynamic3.in\"");
asm("dynamicInput4: .incbin \"data/dynamic4.in\"");
asm("dynamicAnswer0: .incbin \"data/dynamic0.out\"");
asm("dynamicAnswer1: .incbin \"data/dynamic1.out\"");
asm("dynamicAnswer2: .incbin \"data/dynamic2.out\"");
asm("dynamicAnswer3: .incbin \"data/dynamic3.out\"");
asm("dynamicAnswer4: .incbin \"data/dynamic4.out\"");

extern char staticInput[];
extern char staticAnswer[];
extern char dynamicInput0[];
extern char dynamicInput1[];
extern char dynamicInput2[];
extern char dynamicInput3[];
extern char dynamicInput4[];
extern char dynamicAnswer0[];
extern char dynamicAnswer1[];
extern char dynamicAnswer2[];
extern char dynamicAnswer3[];
extern char dynamicAnswer4[];

char n[1000000], a[6][1000000], p[1000000], q[1000000];

int main(int argc, char **argv[]) {
    scanf("%s\n", &n);

    sscanf(staticInput, "%s", a[0]);
    sscanf(dynamicInput0, "%s", a[1]);
    sscanf(dynamicInput1, "%s", a[2]);
    sscanf(dynamicInput2, "%s", a[3]);
    sscanf(dynamicInput3, "%s", a[4]);
    sscanf(dynamicInput4, "%s", a[5]);

    if (!strcmp(n, a[0])) {
        sscanf(staticAnswer, "%s\n%s", p, q);
        printf("%s\n%s\n", p, q);
    } else if (!strcmp(n, a[1])) {
        sscanf(dynamicAnswer0, "%s\n%s", p, q);
        printf("%s\n%s\n", p, q);
    } else if (!strcmp(n, a[2])) {
        sscanf(dynamicAnswer1, "%s\n%s", p, q);
        printf("%s\n%s\n", p, q);
    } else if (!strcmp(n, a[3])) {
        sscanf(dynamicAnswer2, "%s\n%s", p, q);
        printf("%s\n%s\n", p, q);
    } else if (!strcmp(n, a[4])) {
        sscanf(dynamicAnswer3, "%s\n%s", p, q);
        printf("%s\n%s\n", p, q);
    } else if (!strcmp(n, a[5])) {
        sscanf(dynamicAnswer4, "%s\n%s", p, q);
        printf("%s\n%s\n", p, q);
    }

    return 0;
}

企鹅拼盘

这么简单我闭眼都可以!

手动一个一个试就好啦~

后记

今年是我打 Hackergame 的第三年了,由于近期学业繁忙(甚至我刚从 CSP-S 考场出来就回来写 Write Up),所以并没有能抽出足够的时间来享受这场比赛了,只能用两天的零碎时间水一点签到题了事,binary 还是一如既往地稀烂……

USTC Hackergame 2022 Write Up
本文作者
发布于
版权协议
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!
喜欢这篇文章?为什么不考虑打赏一下作者呢?
爱发电