Hackergame 2022 Writeup

October 30, 2022

Intro

今年已经是第三次参赛了。说是不在乎排名,也挺违心的。不过每次参加比赛,那种一直惦记着要把题目解出来的感觉,是挺吸引人的。从解题倾向来看,还是 General 和 Web 的题目能拿下分数,其他的要么是数学不好,要么是没有接触。所以也很正常,玩得开心就好。

H9EsT7

签到 (Web)

进入页面,会出现四个黑框,看上去似乎是要在规定时间内画出「2022」四个数字。但其实提交后,URL 会加上 result 参数,修改这个参数为「2022」即可获得 flag。

猫咪问答喵 (General)

今年的问答比去年难了不少,且按「答对一半」和「答对全部」两阶段给分。所以刚开始先挑了三道最为简单的题目完成,之后再找了个时间静下来解答剩余题目。

接下来将从解题顺序开始。

除了本地化的问题,否则很不推荐使用国内搜索引擎答题,效率会大打折扣。

  • 第三题对于使用 Google 的选手来说,这道是送分题。直接搜索 ‘last firefox version in windows 2000’ 即可。

  • 第一题如果使用 Google 搜索,可能会受到其他内容的影响。较准确的是用 USTC NEBULA 成立 作为关键词搜索,可在 中国科学技术大学星云(Nebula)战队在第六届强网杯再创佳绩 这篇新闻稿中找到成立年月。

  • 第六题可以在搜索引擎中找到文章 关于实行新的网络费用分担办法的通知,但要留意文中这句话:同时网字〔2003〕1号《关于实行新的网络费用分担办法的通知》终止实行。 说明这份文件上的实行日期,可能不是最早题中所涉及的服务实行时间。但又在这篇文章所属分类「新闻公告」中查找,最后一条的发布日期在 2010 年。

    在网站里仔细查找,还是会发现在「网字文件」下有 2003 年的 同名文章,答案为文中所述的实行时间。

  • 第四题开始就比较懵,刚开始想说直接去 Github 搜有关的 commit,但由于实在太多加上找不到搜索入口,遂暂时搁置。之后思路有所转变,虽然还是有些绕,后来还是找到了。

    用 Google 搜索关键词 linux fix argc 0,可以在结果里找到这篇文章 Handling argc==0 in the kernel - LWN.net,随后找到有人 started the linux-kernel discussion。虽然不是答案,但发现了关键词 CVE-2021-4034,也指明了修改的文件位于 fs/exec.c

    这时可以去 linux 的 Github 仓库找到这个文件,并且查看文件的修改历史。在这个页面搜索 argv,能找到这条 commit,点击查看提交详情,发现详情提及之前沟通的内容。完整 commit hash 在 url 中,复制即可。

  • 其实第二题比上面的第四题简单,但由于思维被误导了,重新检查思路后解出了该题。

    关于 LUG 的活动,可以先从官网入手找到相关的新闻页,不过由于新闻页的内容释出并不详细,所以要另寻方法。

    这时候可以看到网站上有关活动的专门页面,而其中有关今年活动的链接,仍然指向之前找到的新闻。但可以下滑找到之前的相关「软件自由日」活动,链接指向了网站提供的 ftp,在 ftp 站中可以找到本次活动的与本题相关的 PPT。

    在翻看 PPT 的时候,第一次答题容易把目光注意到标题为「Gnome + Wayland」的 page,但很难找到突破点。第二次再看的时候,注意到这段字:特别是 KDE 程序会有比较严重的问题,该页右侧截图的程序似乎出现了明显的 Bug。用搜索引擎寻找菜单中的 Configure Kdenlive,确定它是一个 KDE 程序,到此解题结束。

  • 第五题着实是把我卡住了,刚开始扫过题目知道这题应该不好解决,结果第二轮做下来,还是觉得它最难,主要在于摸不着头脑。

    先从 SSH 连接域名的方式搜索,后来一无所获。再到用 key fingerprint 的内容搜索,也因为搜索结果模糊,没有实质进展。

    但仍在想,这一串输出并不是毫无意义,索性换一些搜索方式。搜索引擎的进阶搜索功能,可以使用精确搜索来排除与关键词相关但不准确匹配的内容。最后用这一方法搜索到一个工具为 Zeek 的 document page,访问代码块中的 id.resp_h,在网页页脚里发现这个网站的域名 sdf.org,即为本题答案。

家目录里的秘密 (General)

下载题目包后解压,用 VS Code 打开文件夹,全局搜索关键字 flag{,得到第一个 flag。

更换关键字为 flag 搜索后,可以看到 rclone.conf 文件内一个为 flag2 的 config setting,其中的 pass 似乎就是我们需要第二个 flag。先搜索引擎寻找 rclone config pass decode,可以看到 fourm.rclone.org 的 论坛帖子How to retrieve a ‘crypt’ password from a config file,帖子主题描述的内容似乎就是我们想要的,帖子内的脚本运行后,可以获得第二个 flag。

HeiLang (General)

下载文件看源码,似乎是指定了一个长度为 10000 的数组(Python 的数据格式已经忘了,数组是 JS 的说法),然后给数组的指定位置赋值。本以为 Python 真有这样的赋值方法,结果确实是新创的。

既然这样,就回到了写 JavaScript 代码的老本行,读取每一行赋值代码为文本格式,把里面的内容切块执行。

const fs = require('fs')
let a = new Array(10000).join(0).split('')
function fillArray(string) {
let indexArr = null
string = string.replace('a[', '')
string = string.split('] =')
val = parseInt(string[1])
indexArr = string[0].split(' | ')
for (let i in indexArr) {
a[indexArr[i]] = val
}
}
fillArray('a[1225 | 2381 | 2956 | 3380 | 3441 | 4073 | 4090 | 4439 | 5883 | 6253 | 7683 | 8231 | 9933] = 978')
// 当时做的时候懒得读 .py 文件,于是就手动把那么长的赋值代码拷过来,批量加上函数,所以后面的省略…
let writerStream = fs.createWriteStream('output.txt')
writerStream.write(a.toString(),'UTF8')
writerStream.end()
writerStream.on('finish', function() {
console.log("write finished.")
})

最后回到 .py 文件,去掉之前写的数组生成和赋值代码,把结果文件拷贝回来加上赋值,就能解出 flag 了。

Xcaptcha (Web)

这题刚开始会摸不着头脑,但实际想想还是很好解决的,只是刚开始没想到用上脚本。

这里的脚本,指的是使用例如 Tempermonkey 或者 Violentmonkey,这类脚本插件在浏览器中有很好的实用性,在加载指定的 URL 时可以自执行。那就创建一个脚本,需要达到的目的有:

  • 用 DOM 获取数字所在的元素和输入框所在的元素
  • 切割得到两组数字
  • 执行加法操作
  • 把结果填入输入框
// ==UserScript==
// @name Hackergame 2022 Xcaptcha
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author null
// @match http://0.0.0.0/xcaptcha
// @grant none
// ==/UserScript==
(function() {
'use strict';
function calculate(labelDom) {
return new Promise((resolve, reject) => {
// extract number string from label
const arr = document.querySelector(labelDom).innerHTML.split(' ')[0].split('+');
// calculate result using BigInt()
const res = BigInt(arr[0]) + BigInt(arr[1]);
// convert to string and remove the 'n' end
resolve(res.toString().split('n')[0]);
});
};
calculate('[for="captcha1"]').then((res) => {
document.getElementById('captcha1').value = res;
});
calculate('[for="captcha2"]').then((res) => {
document.getElementById('captcha2').value = res;
});
calculate('[for="captcha3"]').then((res) => {
document.getElementById('captcha3').value = res;
});
})();

在做第三步的时候,需要注意整数溢出的问题,要使用 BigInt 而不是普通的数值格式。

旅行照片 2.0 (General)

去年题目的升级版,有两个 flag。其实都很简单,但要非常注意的是题目的设问,是拍摄地点是拍摄地点是拍摄地点!(重要的话说三次,本选手蠢的要死,卡了很久。)

第一个 flag 可以通过各种读取 EXIF 的软件和网站来快速获得,我这里直接使用的是 macOS 的 Preview.app,打开图片文件后找到窗口上部的信息 icon,就能弹出该照片的信息窗口,根据这些信息就可获得 flag。

Kailan

j3IIa0

第二个 flag 的思路,不再侧重于 EXIF。

  • 手机屏幕像素的问题,还是可以通过查看 EXIF 上的设备型号 sm6115,对应 高通骁龙 662 处理器,搜寻与品牌和处理器匹配的手机型号,这样可以大大减少筛选范围。最后可以知道,拍摄者使用的手机型号是 Redmi Node 9 4G,屏幕像素为 2340x1080

  • 其余都与确定拍摄者的位置有关,但 EXIF 没有包含经纬度数据。所以通过放大拍摄的建筑物,可以看到建筑物上的文字 ‘WELCOME TO ZOZOMARINE STADIUM’,再通过地图确定到日本的这座体育馆,根据拍摄方位确定拍摄者所处的酒店,进而知道该位置的邮政编码。要注意的是拍摄地点的邮政编码,而不是体育馆的邮政编码。虽然地理上很近,但却是不同的!

  • 至于航班信息,为了保证准确,还是到 Flightradar24 上开了个七天体验会员(如果不开的话,只能查看过去七天范围内的航班动态图)。开通了会员,就可以把日期指定到拍摄时间,通过之前知道的拍摄者位置,来寻找航班信息了。

猜数字 (General)

先下载源码文件观察一番,主要的判断代码是:

var isLess = previous < this.number - 1e-6 / 2;
var isMore = previous > this.number + 1e-6 / 2;
var isPassed = !isLess && !isMore;

所以这个数字的值不重要,只是要让后面的 isPassed 为 true 即可。输入 NaN 提交即可得到 flag。

LaTeX 机器人 (Web)

同样是先下载题目附带的后端文件包,打开后厘清下图片生成的逻辑。在包内有个 base.tex 的情况下,准备一个空的 result.tex,依次插入 base.tex 的头部、用户输入内容、base.tex 的尾部。最后使用 pdflatex - pdfcrop - pdftoppm - pnmtopng,最后生成图片。

从代码结构可以看到,在用户输入的时候获取指定目录的文件输出就可解题。知道了这个,似乎解题就简单了不少。如果不太懂 LaTex 语言,就只要根据你要达到的目的去找命令。如果找的够准确,两题都没什么难度。

搜索关键词 ‘latex input text file’,查到可以使用 \input{} 命令获取文本文件。因为代码中的用户 input.tex 在 /dev/shm 目录下,所以构建命令 /input{../../flag1} 即可获得第一个 flag。

第二个 flag 增加了输入文件会出现特殊字符的因素,所以上面的命令继续使用会报错。搜索关键词 latex input text file with special characters,可以找到有篇帖子提供了三种方式能够不报错的获取内容。其中有种方法是通过引入包 \verbatiminput{},感觉可能会有包未安装的风险,所以没有深入研究下去,而是使用 \catcode` 转义可能会出现的特殊字符,最后获得第二个 flag。

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

Flag 的痕迹 (Web)

打开网页后确定使用的是 Doku Wiki,那么得先了解如何查看编辑历史。以 dokuwiki history 作为关键词搜索后,可以知道在 URL 后加上参数 ?do=recent 即可。

但在网站上尝试后,页面提示的是 ‘Action disabled: recent’。留意到 Actions 可能不止一个,在 Doku Wiki 的官方文档中找到 Action Modes aka. do Modes,其中列出了各种操作模式。尝试几个后发现 ?do=diff 可以对比修改历史,回翻即可找到 flag。

线路板 (General)

下载题目提供的压缩包,解压后发现一些 .gbr 和 .gbrjob 文件。这时候需要寻找和安装可以打开这类文件的电路板开发工具,开源的 KiCad 似乎符合需求。

安装后打开 GerbView,可以拖动单个图层的 .gbr 或者包含所有图层的 .gbrjob 到程序内。加载成功的话,主界面会显示项目绘制的电路图,图中右侧偏下处会有被覆盖着的 flag{ 露出,但被 D10 node 覆盖。

如果对界面用途不够了解,可以在 KiCad 网站上查找 操作文档。接下来先选择覆盖用的 D10 node 所在的图层,再尝试顺序选择高亮图层中不同 D node 的项目,这时就能看到所覆盖的 flag 内容了。

2YIh1Q

光与影 (General)

这道题虽然不懂 WebGL 的原理,但可通过修改代码尝试修改渲染效果。为了方便起见,建议把题目页面和有关的 js 文件下载下来到本地分析,几个文件大概的作用:

  • render-main.js: 渲染 DOM,生成 webgl 实例,准备 webgl 渲染环境;
  • webgl-utils.js: 通过文件名推测是辅助 WebGL 运行的函数集;
  • vertex-shader.js: 定义顶点着色器;
  • fragment-shader.js: 定义片段着色器;

由于顶点着色器的定义内容不多,可以先从片段着色器开始看起。页面处理前的效果为 flag 的头部可见,其余被遮挡。在查看片段着色器定义时,可从代码上感知 sceneSDF() 函数中关于 t1SDF ~ t4SDF 都进行了相关转换或定位操作,可以认为是渲染 ‘flag’ 四个字母。但 t5SDF 尤其特殊,不仅没有使用与前者差不多的 mk_trans(),也引入了前者不曾使用的 pvec3 参数,怀疑是生成遮罩的相关函数。遂修改 t5 变量中的各项数值后刷新查看效果,修改为 float t5 = t5SDF(p - vec3(0.1, 0.1, 0.1), vec3(0.1, 0.1, 0.1), 0.1); 得出结果。

大概是暴力法解题了,所有解题方法没有普适性,只有对代码的敏感性,仅参考。


Profile picture

Written by null.
He is a 'foolish' coder, writing lines withour mind.