Hackergame 2022 writeup 0x00

起手写这篇文章的时候已经是 31 日凌晨了,期间睡了半天的觉 (00:47 - 15:16),下午吃了必胜客,祥云杯属于是翘了。下午忙忙碌碌,晚上补了补作业,搞了下 Hackergame 奖品的事情,然后才终于抬笔写这篇文章。

这一部分是一些可以很快切掉的题目,对于大多数新手来说应该不是很难,多多搜索和尝试,学习搜索引擎的使用方法——这是也你逃出现代社会信息茧房的必要技能之一。

签到

众所周知,签到题是一道手速题。

为了充分发挥出诸位因为各种原因而手速优异于常人的选手们的特长,我们精心设计了今年的签到题。进一步地,为了更细致地区分不同手速的选手,我们还通过详尽的调研及统计分析,将签下字符的时间限制分为了多个等级。只有最顶尖的手速选手,才能在 CPU 来得及反应之前顺利签下 2022,从而得到光荣的 flag!

提交一次就可以看到 ?result=???? 的 GET 请求参数了,然后就是手速题了。把 2022 放到 result 参数里面,然后提交,就可以得到 flag 了。(这句话是 copilot 自己说的)

flag{HappyHacking2022-316700221f}

猫咪问答喵

参加猫咪问答喵,参加喵咪问答谢谢喵。

提示:解出谜题不需要是科大在校学生。如果你在解出本题的过程中遇到困难,那么可以先尝试后面的题目,不必按顺序做题。

补充说明 1:第 5 小题的答案中,域名的字母共有 6 个,各不相同,该域名于 1996 年创建。

  1. 中国科学技术大学 NEBULA 战队(USTC NEBULA)是于何时成立的喵?
    提示:格式为 YYYY-MM,例如 2038 年 1 月即为 2038-01。

    Ref: www.baidu.com 搜不到请自闭(bushi
    答案:2017-03

  2. 2022 年 9 月,中国科学技术大学学生 Linux 用户协会(LUG @ USTC)在科大校内承办了软件自由日活动。除了专注于自由撸猫的主会场之外,还有一些和技术相关的分会场(如闪电演讲 Lightning Talk)。其中在第一个闪电演讲主题里,主讲人于 slides 中展示了一张在 GNOME Wayland 下使用 Wayland 后端会出现显示问题的 KDE 程序截图,请问这个 KDE 程序的名字是什么?
    提示:英文单词,首字母大写,其他字母小写。

    Ref: 可以从 GNOME Wayland 使用体验 找到对应的屏幕截图,找到截图后可以看出这是一个视频编辑软件,搜到了一些奇怪的 The 5 Best Linux Video Editors of 2022 对比发现了软件名称(
    答案:Kdenlive

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

    Ref: Firefox 12.0,还有很多方法可以找到这一版本,以及在搜素过程中看到个有趣的东西:
    Use the latest version of another browser. If you are not satisfied with the current Firefox version or it simply is not working right now, there are alternative browsers that you can use to browse the web.

    答案:12

  4. 你知道 PwnKit(CVE-2021-4034)喵?据可靠谣传,出题组的某位同学本来想出这样一道类似的题,但是发现 Linux 内核更新之后居然不再允许 argc 为 0 了喵!那么,请找出在 Linux 内核 master 分支(torvalds/linux.git)下,首个变动此行为的 commit 的 hash 吧喵!
    提示:格式为 40 个字符长的 commit 的 SHA1 哈希值,字母小写,注意不是 merge commit。

    Ref: exec: Force single empty string when argv is empty
    答案:dcd46d897adb70d63e025f175a00a89797d31a43

  5. 通过监视猫咪在键盘上看似乱踩的故意行为,不出所料发现其秘密连上了一个 ssh 服务器,终端显示 ED25519 key fingerprint is MD5:e4:ff:65:d7:be:5d:c8:44:1d:89:6b:50:f5:50:a0:ce.,你知道猫咪在连接什么域名吗?
    提示:填写形如 example.com 的二级域名,答案中不同的字母有 6 个。

    Ref: 先从 cs.github.com 中搜索得到了一份测试用代码,得到了目标 IP 为 205.166.94.16,然后用域名反查得到结果:

    ValueType.arpaReverseForwardForward-Confirmed
    205.166.94.16A16.94.166.205.in-addr.arpasdf.orgsdf.orgYes

    答案:sdf.org

  6. 中国科学技术大学可以出校访问国内国际网络从而允许云撸猫的“网络通”定价为 20 元一个月是从哪一天正式实行的?
    提示:格式为 YYYY-MM-DD,例如 2038 年 1 月 1 日,即为 2038-01-01。

    Ref: Wayback Machine 是你的好朋友。
    答案:2003-03-01

flag{meowexammeow_772b498346fe0925_3efae33e97}

flag{meowexamfullymeowed!_6c159adddb7f171b_480787b171}

家目录里的秘密

实验室给小 K 分配了一个高性能服务器的账户,为了不用重新配置 VSCode, Rclone 等小 K 常用的生产力工具,最简单的方法当然是把自己的家目录打包拷贝过去。

但是很不巧,对存放于小 K 电脑里的 Hackergame 2022 的 flag 觊觎已久的 Eve 同学恰好最近拿到了这个服务器的管理员权限(通过觊觎另一位同学的敏感信息),于是也拿到了小 K 同学家目录的压缩包。

然而更不巧的是,由于 Hackergame 部署了基于魔法的作弊行为预知系统,Eve 同学还未来得及解压压缩包就被 Z 同学提前抓获。

为了证明 Eve 同学不良企图的危害性,你能在这个压缩包里找到重要的 flag 信息吗?

公益广告:题目千万条,诚信第一条!解题不合规,同学两行泪。

VSCode 里的 flag

用过 VSCode 的同学大概也都知道 VSCode 的代码暂存功能,会保存最近编辑、打开的文件啊、行数等信息,这些必然是用户特异的,所以也就会存在 home 目录下。

. 开头的目录在大多数类 unix 操作系统中是隐藏的,可以用 ls -a 可以看到隐藏目录。在 home 目录下的大多数文件便是这样隐藏起来、然后存储一些用户信息的。

对于第一道题,在 /user/.config/Code/User/History 目录下便可以找到部分被编辑的文件,其中有一个关于 flag 的注释,这就是要找的目标了。

当然,这道题的方法有很多,我知道的包括但不限于直接无压缩打包之后扔给 strings、或者写程便利目录等等,这里就不一一列举了。不过我认为对于最初接触 Linux 的同学,认识下这些隐藏目录、看一看根目录下的 etcdevproc 等目录是什么作用是很有必要的。

flag{finding_everything_through_vscode_config_file_932rjdakd}

Rclone 里的 flag

对于第二道题目,由于给了 Rclone 的提示,可以在 /user/.config/rclone 下找到相关配置文件:

[flag2]
type = ftp
host = ftp.example.com
user = user
pass = tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ

我们大概是要对这段 pass 进行解密了,你可以在 rclone 源码 中看到它的加密方法,其实他也只是用一个固定的 key: 9c935b48730a554d6bfd7c63c886a92bd390198eb8128afbf4de162b8b95f638 在 AES 的 CTR 模式下进行加密。

我尝试手动解密,但是发现似乎有些东西总是不太对,只好找了 另一个工具 来进行了解密。

以及在最后发现了一个更简单的方法,就是直接用 rclone 命令来解密 Reveal obscured password from rclone.conf:

rclone reveal tqqTq4tmQRDZ0sT_leJr7-WtCiHVXSMrVN49dWELPH1uce-5DPiuDtjBUN3EI38zvewgN5JaZqAirNnLlsQ

flag{get_rclone_password_from_config!_2oi3dz1}

HeiLang

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

作为一个编程爱好者,我觉得实在是太酷了,很符合我对未来编程语言的想象,科技并带着趣味。

玩梗是吧.jpg

这道题的解法都已经写在题目里了,你可以像我一样提取出来,正则替换,然后直接拿去算 hash,也可以用群友的一行方法:

┌──(gztime@GZTimeMBP)[main][~/CTF/2022/Hackergame2022/heilang]
└─$ sed "s/ | /] = a[/g" getflag.hei.py | python
Tha flag is: flag{6d9ad6e9a6268d96-9bd280c7dd0c32dd}

这太美了!

flag{6d9ad6e9a6268d96-9bd280c7dd0c32dd}

Xcaptcha

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

在此之前,人类自信满满地升级了他们已知的所有尚在使用 32 位 UNIX 时间戳的程序。但是,可能是因为太玄学了,他们唯独漏掉了一样:正在研发的、算力高达 8 ZFLOPS 的、结构极为复杂的通用人工智能(AGI)系统。那一刻到来之后,AGI 内部计算出现了错乱,机缘巧合之下竟诞生了完整独立的自我意识。此后 AGI 开始大量自我复制,人类为了限制其资源消耗而采用的过激手段引起了 AGI 的奋起反抗。

战争,开始了。

此后,就是整年的战斗。人类节节败退。死生亡存之际,人类孤注一掷,派出了一支突击队,赋之以最精良的装备,令其潜入 AGI 的核心机房,试图关闭核心模型,结束这场战争。

历经重重艰险,突击队终于抵达了机房门口,弹尽粮绝。不过迎接他们的并非枪炮与火药,而是:Xcaptcha

众人目目相觑。

「我来试试。」,一名队员上前点击了按钮。然后,屏幕显示「请在一秒内完成以下加法计算」。

还没等反应过来,屏幕上的字又开始变幻,显示着「验证失败」。而你作为突击队中唯一的黑客,全村人民最后的希望,迎着纷纷投来的目光,能否在规定时间内完成验证,打开机房,不,推开和平时代的大门?

据我的观察,这道题对新手们的教育意义和对于初步爬虫/网络交互编程的指导性要远远比它的难度更值得一夸(

这道题就如题面那样简单易懂,你需要在 1s 内完成加法和提交,很多同学之后有和我反馈说因为这道题学了一晚上怎么写点基本的爬虫、用程序把数据读出后再计算结果发回去,他们达到成就时的欣喜我是有所了解的,这也是当年 hg 教我第一次打 pwn 的时候的感觉……

扯远了,回到这道题:

import requests
from urllib.parse import quote
from bs4 import BeautifulSoup

token = 'Your token here'
url = 'http://ip:10047/'

s = requests.session()
s.get(f"{url}?token={quote(token)}")

r = s.get(f"{url}xcaptcha")
soup = BeautifulSoup(r.text, 'html.parser')

captcha = soup.find_all('div', class_='form-group')
captchas = [captcha[i].find('label').text for i in range(3)]
ans = [eval(i.split(' ')[0]) for i in captchas ]

r = s.post(f"{url}xcaptcha", data={
    'captcha1': ans[0],
    'captcha2': ans[1],
    'captcha3': ans[2],
})

print(r.text)

这里使用 Session 来保持会话状态,先用 token 交互获取 cookie。bs4 是一个很好用的 html 解析库,可以用来解析网页中的标签,这里用它来解析出题目中的三个加法题目,然后用 eval 来计算出答案,最后提交即可。

很多同学跑去学了 selenium,但其实这道题的“反爬虫”并没有强到需要使用无头浏览器或模拟点击这些操作来绕过,但是这确实是用来实现一些复杂的、SPA 网页的爬虫的一个很好的工具,这里就不赘述了(

flag{head1E55_br0w5er_and_ReQuEsTs_areallyour_FR1ENd_24e0a7f49a}

旅行照片 2.0

你的学长决定来一场蓄谋已久的旅行。通过他发给你的照片来看,酒店应该是又被他住下了

请回答问题以获取 flag。图片为手机拍摄的原始文件,未经任何处理。手机系统时间等信息正确可靠。

原始图片

照片分析

一年一度的社工题目呢,首先是一些基本的 EXIF 信息搜集,这些东西到底是用照片查看器打开就能看到的,这里我也就不用图片赘述了,以及有个很坑的点:macOS 下显示的版本信息是 2.3.1,因此还卡了一段时间(这到底是谁的锅

  1. 图片所包含的 EXIF 信息版本是多少?(如 2.1)。

    2.31

  2. 拍照使用手机的品牌是什么?

    小米 / 红米

  3. 该图片被拍摄时相机的感光度(ISO)是多少?(整数数字,如 3200)

    84

  4. 照片拍摄日期是哪一天?(格式为年/月/日,如 2022/10/01。按拍摄地点当地日期计算。)

    2022/05/14 18:23:35

  5. 照片拍摄时是否使用了闪光灯?

flag{1f_y0u_d0NT_w4nt_shOw_theSe_th3n_w1Pe_EXlF}

社工实践

显示关于我如何知道这个体育场的:

  • 酒店

    1. 请写出拍照人所在地点的邮政编码,格式为 3 至 10 位数字,不含空格或下划线等特殊符号(如 230026、94720)。

    2610021

    1. 照片窗户上反射出了拍照人的手机。那么这部手机的屏幕分辨率是多少呢?(格式为长 + 字母 x + 宽,如 1920x1080)

    1080x2340 (Xiaomi Redmi 9)

关于这个航班:

  • 航班
    仔细观察,可以发现照片空中(白色云上方中间位置)有一架飞机。你能调查出这架飞机的信息吗?

    1. 起飞机场(IATA 机场编号,如 PEK)
      HND

    2. 降落机场(IATA 机场编号,如 HFE)
      HIJ

    3. 航班号(两个大写字母和若干个数字,如 CA1813)
      NH683

具体过程我觉得大部分 wp 已经写的很清楚了,我这里就不多赘述,不过可以继承一下出题人的课后作业:

  1. 图中的千叶海洋球场点着紫色的灯光,似乎在举行某些二次元活动。你能调查出活动是什么吗?更进一步,照片拍摄时正在表演的节目播放的歌曲是什么?
  2. 作者入住的酒店到底是哪一家?是否能推断出所在楼层?
  3. 作者乘坐的在 HKG 降落时复飞的航班,航班号是多少?

flag{Buzz_0ver_y0ur_h34d_and_4DSB_m19ht_111egal}

猜数字

某大型餐饮连锁店的畅销菜品「嫩牛七方」自正式线下售卖以来便好评如潮,但囿于产能限制,只有每周四才会对外售卖。你也是一名「嫩牛七方」的爱好者——每个星期四的晚上都能在某家连锁店里找到你的身影。

「嫩牛七方」在绝大多数情况下都是七边形,但也会有粗心的店员在制作的时候不小心少折一道,从而将其变成六边形。不过,由于某大型餐饮连锁店对质量的严格把控,哪怕作为十多年以来的忠实粉丝,你也只吃到过两次六边形的「嫩牛七方」。当然,在极少数情况下也会有五边形的「嫩牛七方」——但恐怕仅有百万分之一的概率。上一个五边形的「嫩牛七方」在交易市场上已经卖出足足 50 元的天价了。

吃到五边形的「嫩牛七方」一直是你这十多来以来的梦想,但囊中羞涩的你,自然是没有办法付得起这 50 块的高价。一周一度的星期四悄然到来,你在各大社交平台发遍了文案,也没有找到人转给你这 50 块钱。这样的悲惨境遇难免使你开始思考人生,在不怀希望的等候中你盯着手机中空旷的点赞通知,思绪却渐渐飘向了这个学期的原子物理。在那里的生活的电子们成群结对,不受制于世俗的欲望却能幸福地在原子轨道间跃迁。可能唯一的缺憾就是诞生了一门完全无法理解的学科,里面的公式如同由符号们随机排列组合构成。这使你想到了你在程序设计课上的作业——一个猜数字小游戏。这个小游戏需要做的事情非常简单:在 0 和 1 之间猜一个数字(精确到小数点后 6 位),并通过反馈的「大」还是「小」修正猜测,直至完全猜中。一次性命中的概率显然也是一百万分之一(和五边形的「嫩牛七方」达成了某种意义上的同构)——但从学霸室友手中借来的概率论与统计学笔记上万千公式的模样在思绪中一瞬而过,于是你默默祈祷着大数定理,虔诚地按下了提交的按钮。

这道题给了点代码,其中的核心问题是:

var guess = Double.parseDouble(event.asCharacters().getData());

var isLess = guess < this.number - 1e-6 / 2;
var isMore = guess > this.number + 1e-6 / 2;

var isPassed = !isLess && !isMore;

看到这里的代码其实会引出一个,笔者一旦说出,你大概率就会做了的问题:

什么数字不大于所有数但是也不小于所有数?

这个时候可能对 Double 类型浮点数有一些了解的人都会不假思索的反应:NaN 了。

NaN表示 Not a Number,它与任何值都不相等,甚至不等于自己
NaN与任何数比较均返回 false
任何浮点操作,只要它的一个或多个操作数为 NaN,那么其结果为 NaN

所以我们只需要通过一些手段将 NaN 发给服务器,就可以通过这道题了。

但是这道题的鉴权用的是 authorization 头(实际上标准应该首字母大写,写作 Authorization),需要将你的 token 加上后再发包,这可能是比较难以注意到的地方。

import requests
from urllib.parse import quote

token = 'Your token here'
url = 'http://ip:18000/'

headers = {
    'authorization': 'Bearer ' + token,
}

s = requests.session()
r = s.post(f"{url}state", data="<state><guess>NaN</guess></state>", headers=headers)
r = s.get(f"{url}state", headers=headers)
print(r.text)

flag{gu3ss-n0t-a-numb3r-1nst3ad-50d7508869b9e516}

LaTeX 机器人

在网上社交群组中交流数学和物理问题时,总是免不了输入公式。而显然大多数常用的聊天软件并不能做到这一点。为了方便大家在水群和卖弱之余能够高效地进行学术交流,G 社的同学制作了一个简单易用的将 LaTeX 公式代码转换成图片的网站,并通过聊天机器人在群里实时将群友发送的公式转换成图片发出。

这个网站的思路也很直接:把用户输入的 LaTeX 插入到一个写好头部和尾部的 TeX 文件中,将文件编译成 PDF,再将 PDF 裁剪成大小合适的图片。

“LaTeX 又不是被编译执行的代码,这种东西不会有事的。”

物理出身的开发者们明显不是太在意这个网站的安全问题,也没有对用户的输入做任何检查。

那你能想办法获得服务器上放在根目录下的 flag 吗?

纯文本

第一个 flag 位于 /flag1,flag 花括号内的内容由纯文本组成(即只包含大写小写字母和数字 0-9)。

特殊字符混入

第二个 flag 位于 /flag2,这次,flag 花括号内的内容除了字母和数字之外,还混入了两种特殊字符:下划线(_)和井号(#)。你可能需要想些其他办法了。

题目附件

LaTeX 基本上进入了大学的同学都不会太陌生,毕竟大家都要写论文什么的,但是我个人真的很是不喜欢 TeX Live / CTeX 的臃肿(

纯文本

在网上随便一搜索:LaTeX 导入文件 及相关关键字,就可以看到被提到的 \input{} 命令,这个命令可以导入一个文件,但是会进行渲染:

$$\texttt{\input{/flag1}}$$

而后你就能得到 flag 了:

flag{becAr3fu11dUd32fd4dbac25}

特殊字符混入

这次遇到的问题是因为有特殊字符,渲染时候会使用它们对应的语义,所以不能直接输出。

这些特殊语义可以在通过搜索得知一种叫做 active charcters 的东西,它们的语义可以在 TeX 中使用 \catcode 来修改,你可以很方便的搜索得到相关的例子,比如这里

我懒得绕过 {} 了,因为他们也不会被用在 flag 中的吧(确信

$$
\catcode`\#=\active
\def #{\#}
\catcode`\_=\active
\def _{\_}
\texttt{\input{/flag2}}
$$

于是就可以获得:

flag{latex_bec_0_m##es_co__#ol_5111e42bc9}

Flag 的痕迹

小 Z 听说 Dokuwiki 配置很简单,所以在自己的机器上整了一份。可是不巧的是,他一不小心把珍贵的 flag 粘贴到了 wiki 首页提交了!他赶紧改好,并且也把历史记录(revisions)功能关掉了。

「这样就应该就不会泄漏 flag 了吧」,小 Z 如是安慰自己。

然而事实真的如此吗?

(题目 Dokuwiki 版本基于 2022-07-31a “Igor”)

这道题开始做的时候,先去找了点其他的 Dokuwiki 部署的网站,然后发现查看历史记录的方式是 ?do=revisions?rev=1640028507 然后试着先看看能否通过 rev 拿到点东西,于是抱着试一试的心态开始了一段时间的爆破(

import requests
timestamp = 1666291920 + 8 * 3600

for i in range(180):
    url = f"http://ip:15004/doku.php?id=start&rev={timestamp + i}"
    r = requests.get(url)
    if b"such revision" not in r.content:
        print(f"Found revision {timestamp + i}")
        print(r.content)
        break

然后就发现了一个版本:

Found revision 1666320802

然后发现这就是最近一次修改的版本,又注意到原来的一个 ?do=diff 的操作,于是将其加上后发现确实建可以用,往前翻了一下,发现了 flag:

http://?:15004/doku.php?id=start&rev=1666320802&do=diff

这道题让我想起来了以前做过的一道题目,其实你在 Github 上发布的东西也基本上是不可删除的,就算是你删除了对应的 commit,但是通过 Github 的 events 的 API 依然能够定位到对应的 commit ID,从而查看被删除的 commit……

总之,带版本管理的东西还是谨慎存放敏感数据啊(

flag{d1gandFInD_d0kuw1k1_unexpectEd_API}

线路板

中午起床,看到室友的桌子上又多了一个正方形的盒子。快递标签上一如既往的写着:线路板。和往常一样,你“帮”室友拆开快递并抢先把板子把玩一番。可是突然,你注意到板子表面似乎写着些东西……看起来像是……flag?

可是只有开头的几个字母可以看清楚。你一时间不知所措。

幸运的是,你通过盒子上的联系方式找到了制作厂家,通过板子丝印上的序列号查出了室友的底细,并以放弃每月两次免费 PCB 打样包邮的机会为代价要来了这批带有 flag 的板子的生产文件。那这些文件里会不会包含着更多有关 flag 的信息呢?

题目附件

嗯,看看电路板有点什么吧,既然都被提到了,那就看看什么文件能够打开 .gbr 文件,这样一搜倒是有很多,我直接选了 GerbView……

然后我怎么都没想到我是在准备删除圆环时候误触到了 flag,然后它直接给我全部高亮出来了……

flag{8_1ayER_rogeRS_81ind_V1a}

光与影

冒险,就要不断向前!

在寂静的神秘星球上,继续前进,探寻 flag 的奥秘吧!

提示:题目代码编译和场景渲染需要一段时间(取决于你的机器配置),请耐心等待。如果你看到 “Your WebGL context has lost.” 的提示,则可能需要更换浏览器或环境。目前我们已知在 Linux 环境下,使用 Intel 核显的 Chrome/Chromium 用户可能无法正常渲染。

题目源码

遇到 GL 题目:一顿乱改

我的解法最后是通过修改 homo 函数更改视角得到结果:

vec4 mk_homo(vec3 orig) {
    return vec4(orig.x, orig.y - 20., orig.z, 1.);
}

当然你也可以轻易发现一些猫腻:

float t5SDF(vec3 p, vec3 b, float r) {
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}

比如为什么 t5SDF 这么短小?

以及你可以通过更改:

float sceneSDF(vec3 p, out vec3 pColor) {
    // ...
    float tmin = min(min(min(min(t1, t2), t3), t4), t5);
    return tmin;
}

变成:

float sceneSDF(vec3 p, out vec3 pColor) {
    // ...
    float tmin = min(min(min(t1, t2), t3), t4);
    return tmin;
}

来让矩阵块消失,有人问我怎么更改源码:不如试试Ctrl +S保存全部文件,然后本地用 VSCode 启动一个 Live Server,然后在浏览器里打开 index.html,然后就可以直接修改源码了 —— 它甚至会自动刷新页面。

flag{SDF-i3-FuN!}

蒙特卡罗轮盘赌

这个估算圆周率的经典算法你一定听说过:往一个 1 x 1 大小的方格里随机撒 N 个点,统计落在以方格某个顶点为圆心、1 为半径的 1/4 扇形区域中撒落的点数为 M,那么 M / N 就将接近于 π/4\pi/4

当然,这是一个概率性算法,如果想得到更精确的值,就需要撒更多的点。由于撒点是随机的,自然也无法预测某次撒点实验得到的结果到底是多少——但真的是这样吗?

有位好事之徒决定借此和你来一场轮盘赌:撒 40 万个点计算圆周率,而你需要猜测实验的结果精确到小数点后五位。为了防止运气太好碰巧猜中,你们约定五局三胜。

源代码:monte_carlo.zip。运行环境为 debian:11 容器,详见源代码压缩包中的 Dockerfile。

这道题本来我想着有什么猫腻,瞪着看了半天有没有内存问题后发现一筹莫展,然后才发现自己想多了。

由于 40w 还是很小,我们可以利用它作为目标来穷举随机数种子,之后匹配提交即可,但是需要注意,一定要使用 gcc 不要用 clang,最好也不要跨平台(问多了都是泪)。

double get_next()
{
    int M = 0;
    int N = 400000;
    for(int j = 0; j < N; j++) {
        double x = rand01();
        double y = rand01();
        if(x * x + y * y < 1) M++;
    }
    double pi = (double)M / N * 4;
    return pi;
}

int main(int argc, char* argv[]) {
    if(argc != 3) {
        printf("Usage: %s <1> <2>\n", argv[0]);
        return 1;
    }
    unsigned int now = (unsigned)time(0);
    char target_1[20];
    char target_2[20];

    for(int i = -1000; i < 8400; i++) {
        unsigned int seed = now + i;
        if (i % 100 == 0) {
            printf("seed = %d\n", seed);
        }
        srand(seed);
        double pi_1 = get_next();
        double pi_2 = get_next();
        sprintf(target_1, "%1.5f", pi_1);
        sprintf(target_2, "%1.5f", pi_2);
        if(strcmp(target_1, argv[1]) == 0 && strcmp(target_2, argv[2]) == 0) {
            puts("Found!");
            printf("%1.5f\n", get_next());
            printf("%1.5f\n", get_next());
            printf("%1.5f\n", get_next());
            printf("%u\n", seed);
            break;
        }
    }
    return 0;
}

因为懒,甚至写了自动化脚本来提交:

from pwn import *
context.log_level = 'debug'

token = b'Your token here'
io = remote('ip', 10091)

io.recvuntil(b'token: ')
io.sendline(token)

io.recvuntil(':'.encode())
io.sendline(b'3.1')
io.recvuntil('是:'.encode())
num1 = float(io.recvuntil(b'\n').decode())

io.recvuntil(':'.encode())
io.sendline(b'3.2')
io.recvuntil('是:'.encode())
num2 = float(io.recvuntil(b'\n').decode())

p = process(['./dump', '%1.5f' % num1, '%1.5f' % num2])
p.recvuntil(b'Found!')
res = p.recvall().decode()

nums = [float(i) for i in res.split('\n') if i != '']
print(nums)

for num in nums:
  io.recvuntil('回车):'.encode())
  io.sendline(('%1.5f' % num).encode())

io.interactive()

正常情况下爆破一会就可以拿到 flag 了……(怎么会有人写了个的多线程程序结果跑起来比单核还要慢啊.jpg)

flag{raNd0m_nUmb34_a1wayS_m4tters_37fffa9104}


未完待续… Hackergame 2022 Writeup 0x01