USTC Hackergame 2021部分题目Write-Up
USTC Hackergame 2021结束了,这里从一个非科班选手的角度写一下部分题的解题思路与过程。
签到
查看题面
为了能让大家顺利签到,命题组把每一秒的 flag 都记录下来制成了日记本的一页。你只需要打开日记,翻到 Hackergame 2021 比赛进行期间的任何一页就能得到 flag!
签到题还是一样简单,打开页面显示一个时间为:1970-01-01 08:00:00 +08:00点一下Next,时间多了一秒,同时注意到url多了一个参数:/?page=1。结合题意知道只要访问/?page=当前的时间戳,即可拿到flag。调用Python的time库的time函数即可获取当前的时间戳,取整放到url参数中即可。
flag{HappyHacking2021-29decda8a3}
当然只要你点鼠标的速度足够快,也可以通过连点Next约16亿下来获取flag。来自官方的统计信息
去吧!追寻自由的电波
查看题面
(前情提要) 为了打破 Z 同学布下的结界,X 同学偷偷搬出社团的业余无线电台试图向外界通讯。
当然,如果只是这样还远远不够。遵依史称“老爹”的上古先贤的至理名言,必须要“用魔法打败魔法”。X 同学向上级申请到了科大西区同步辐射实验室设备的使用权限,以此打通次元空间,借助到另一个平行宇宙中 Z 同学的法力进行数据对冲,方才于乱中搏得一丝机会,将 flag 用无线电的形式发射了出去。
考虑到信息的鲁棒性,X 同学使用了无线电中惯用的方法来区分字符串中读音相近的字母。即使如此,打破次元的强大能量扭曲了时空,使得最终接受到的录音的速度有所改变。
为了保障同步辐射设备的持续运转,组织牺牲了大量的能源,甚至以东北部分地区无计划限电为代价,把这份沉甸甸的录音文件送到了你的手上。而刚刚起床没多久,试图抢签到题一血还失败了的你,可以不辜负同学们对你的殷切期望吗?
注:flag 花括号内只包含小写字母。
原始音频:
下载到一个mp3文件,打开一听,感觉语速很快,音调很高,难以分辨说的是啥。
一开始考虑了mp3隐写等可能,均失败,后来才注意到题目是有内容的,通过题目第三段可知,这段语音的速度确实是经过加快的,而且采用了我之前没听过的“无线电中惯用的方法”,查到维基页面
那么接下来只要把音频速度和音调放低了听即可。
用到的工具:ffmpeg
ffmpeg -i flag.mp3 -filter_complex "asetrate=48000*2^(-12/12),atempo=0.5" output.mp3
通过此命令转成output.mp3,然后可轻松听出每一个单词(比高中听力简单多了),通过前面维基百科查到的对应表做一个转换,即可拿到flag。转换后的音频如下:
flag{phoneticab}
猫咪问答 Pro Max
查看题面
我猛然一看,就猛然看到这个猫咪问答,我直呼我直呼,上次看到这么这么的发言还是上次,这问答属于是典型的典型了,我之前还没发现,当我发现的时候我已经发现了,这问答就像一个问答,问答的内容充满了内容,我不禁感慨了一句感慨:希望下次看到这么这么的猫咪问答是下次。
考察搜索能力的经典老题了,看了题目文案,感觉真是听君一席话,如听一席话。
题目如下:
- 2017 年,中科大信息安全俱乐部(SEC@USTC)并入中科大 Linux 用户协会(USTCLUG)。目前,信息安全俱乐部的域名(sec.ustc.edu.cn)已经无法访问,但你能找到信息安全俱乐部的社团章程在哪一天的会员代表大会上通过的吗?
提示:输入格式为 YYYYMMDD,如 20211023。请不要回答 “能” 或者 “不能”。 - 中国科学技术大学 Linux 用户协会在近五年多少次被评为校五星级社团?
提示:是一个非负整数。 - 中国科学技术大学 Linux 用户协会位于西区图书馆的活动室门口的牌子上“LUG @ USTC”下方的小字是?
提示:正确答案的长度为 27,注意大小写。 - 在 SIGBOVIK 2021 的一篇关于二进制 Newcomb-Benford 定律的论文中,作者一共展示了多少个数据集对其理论结果进行验证?
提示:是一个非负整数。 - 不严格遵循协议规范的操作着实令人生厌,好在 IETF 于 2021 年成立了 Protocol Police 以监督并惩戒所有违背 RFC 文档的行为个体。假如你发现了某位同学可能违反了协议规范,根据 Protocol Police 相关文档中规定的举报方法,你应该将你的举报信发往何处?
提示:正确答案的长度为 9。
拿到题目就开始一顿搜,第一题刚开始没搜出来,采用了爆破(估计时间也不会太早,我从2010年开始爆破,一会就找到了。实在不行从建校那天开始呗\doge,虽然那会还没互联网),其他几题都容易搜到。
第4题谷歌关键词:SIGBOVIK 2021 Newcomb-Benford。打开第一个查询结果(是个PDF),翻到213页,注意到下面这段话:
得到答案为13。
第5题谷歌关键词:Protocol Police。点开第一个查询结果(RFC 8962: Establishing the Protocol Police),因为提到了举报,在页面右侧菜单栏中找到Reporting Offenses,点击跳转到目标,获取答案。
第2、3题直接去LUG社团的网站搜会更容易搜到结果,不再细说。
后来了解到第一题可以在Wayback Machine查到(又学到了一个新东西)。
卖瓜
查看题面
有一个人前来买瓜。
HQ:哥们,这瓜多少钱一斤啊?
你:两块钱一斤。
HQ:What’s up!这瓜皮子是金子做的还是瓜粒子是金子做的?
你:你瞧瞧现在哪有瓜啊?这都是大棚的瓜,只有 6 斤一个和 9 斤一个的,你嫌贵我还嫌贵呢。
(HQ 心里默默一算)
HQ:给我来 20 斤的瓜。
你:行!
HQ:行?这瓜能称出 20 斤吗?
你:我开水果摊的,还不会称重?
HQ:我问你这瓜能称出 20 斤吗?
你:你是故意找茬,是不是?你要不要吧!
HQ:你这瓜要是刚好 20 斤吗我肯定要啊。那它要是没有怎么办啊?
你:要是不是 20 斤,我自己吃了它,满意了吧?
(你开始选瓜称重)
补充说明:当称的数字变为浮点数而不是整数时,HQ 不会认可最终的称重结果。
题目改编自经典的华强买瓜片段,简单说就是要用6斤1个的瓜和9斤1个的瓜,凑出20斤来。
掐指一算,发现20不是3的倍数,因此直接称肯定不行。
然后试了试能不能称浮点数个瓜,结果发现在后端被取整了,也不行。
然后考虑溢出,在9斤那里放上 $100000000000000000000000000000000$个(并不需要那么多0,只是我懒得算了),变成了“电子秤上已有 $-9223372036854775808/20$ 斤的瓜。”
说明这题可以溢出,然后看一下溢出的数$9223372036854775808(2^{63})$模3的余数,发现是余2的,而20也是模3余2,因此只需要再溢出一次,即可成功搞到模3余0的情况。但在第二次溢出之前,需要先把瓜加回0附近,先放 $9223372036854775808 // 9 = 1024819115206086200$ 个9斤的:
再放1个9斤的:
再和前面一模一样重新操作一次,可以拿到2斤的瓜,最后3个6斤收尾。
透明的文件
查看题面
一个透明的文件,用于在终端中展示一个五颜六色的 flag。
可能是在 cmd.exe 等劣质终端中被长期使用的原因,这个文件失去了一些重要成分,变成了一堆乱码,也不会再显示出 flag 了。
注意:flag 内部的字符全部为小写字母。
题目提示“终端”以及“失去重要成分”,拿到文件一看发现内容如下:
这种编码好像曾在哪里见过(美化PS1环境变量的时候抄过别人写的),经过一番查找,发现是ANSI escape codes。
读懂语法以后,得知该代码每一条均以“\e[”开头,而这里以“[”开头,肯定是缺了“\e”,因此直接把所有“[”替换成“\e[”。刚开始这么试了一下,依稀看到flag字样,仔细看倒也能看出来,不过非常模糊,不容易识别,后来发现如果把文件中的空格替换成可以显示的字符,就可以清楚地看到flag。转换脚本:
with open('transparent.txt') as f:
data = f.read().strip()
d = data.replace('[', '\e[').replace(' ', 'a')
with open('1.sh', 'w') as f:
f.write("clear;echo -e \"" + d + "\"")
运行得到1.sh以后,直接在终端执行命令:
sh 1.sh
旅行照片
查看题面
你的学长决定来一场说走就走的旅行。通过他发给你的照片来看,他应该是在酒店住下了。
从照片来看,酒店似乎在小区的一栋高楼里,附近还有一家 KFC 分店。突然,你意识到照片里透露出来的信息比表面上看起来的要多。
请观察照片并答对全部 5 道题以获取 flag。注意:图片未在其他地方公开发布过,也未采取任何隐写措施(通过手机拍摄屏幕亦可答题)。
题目给了一张照片,并说明了不存在隐写等操作,因此应该是通过照片细节去寻找相关信息。
需要回答的问题如下:
- 该照片拍摄者的面朝方向为?
- 该照片的拍摄时间大致为?
- 该照片的拍摄者所在楼层为?
- 该照片左上角 KFC 分店的电话号码是?
- 该照片左上角 KFC 分店左侧建筑有三个水平排列的汉字,它们是?
前三题通过比较常规的地理知识或经验即可做出(也可直接爆破),主要难点在后两题。
很多同学给出的做法是直接在谷歌搜“蓝色 KFC”等关键词,可直接搜到类似的信息,通过比对可知其位于秦皇岛某海滩。而我就比较灵性了,我搜了半天“绿色 KFC”,并没有搜到什么有用的信息。。。这居然不是绿色,大概是海水的颜色误导了我。
最后我的解法是,把图片中KFC右上角那栋彩色的建筑送进百度识图,也能发现其位于秦皇岛(因为这建筑造型太独特了),然后,打开百度地图的卫星视图,在秦皇岛海岸线附近找沙滩以及图中比较明显的停车场,最后找到这样一处地方:
停车场和沙滩的位置、形状与图片中的一模一样,推测就是这里,然后通过百度街景查看细节:
这个停车场旁边的建筑好像就是题目里说的KFC分店左侧的建筑,只不过拍街景的时候KFC还没建起来。。。因此第5题答案即为“海豚馆”。
在获取了详细的地点信息后,第4题只需打开美团,定位秦皇岛,搜“KFC 新澳海底世界”即可查到这家蓝色KFC的电话。
此题第二种解法:如果你恰好去过秦皇岛新澳海底世界,对照片内的风景有印象的话,这题约等于送分。否则,可以去中国的沿海城市玩一圈,对每一片海滩周围的景物仔细观察,也可以轻松找到答案。
这道题告诉我们,照片不要乱发,否则容易被人肉到很多信息。
FLAG助力大红包
查看题面
“听说没?【大砍刀】平台又双叒做活动啦!参与活动就送 0.5 个 flag 呢,攒满 1 个 flag 即可免费提取!”
“还有这么好的事情?我也要参加!”
“快点吧!我已经拿到 flag 了呢!再不参加 flag 就要发完了呢。”
“那怎么才能参加呢?”
“这还不简单!点击下面的链接就行”
这题目文案看上去就像某多多的老套路。
题目界面如下,需要在10分钟以内收集到若干个好友助力,方可获得flag,虽然一开始就送了你半个flag,但和某多多一样,不是那么容易集齐的。
点击给出的分享链接,发现只有一个按钮,点一下则显示:
打开f12,发现该按钮的请求表单为:
name为ip的input被藏了起来,直接通过Python构造请求,随便赋值一个ip地址,提交请求发现他还会检验后端ip地址是否和前端传过去的匹配,如不相同会报“疑似伪造地址”。
第一反应是真的要去找那么多个ip代理去访问这个链接,但明显不现实,遂考虑用x-forwarded-for请求头去伪造ip,试了一个发现居然真的可以,于是再试一个,返回“重复的/8地址”,幸亏学了点计网,知道/8是ip地址的掩码,因此推测最多只需构造256个前8位(第一个数字)不同的ip地址,依次访问即可获取flag,以下为脚本:
import requests
import time
ip = ".168.0.1"
data = {
"ip": ""
}
headers = {
"X-Forwarded-For": "",
}
url = "http://202.38.93.111:10888/invite/1b9e9ff5-2d92-4dad-94ed-05887c9a9273"
for i in range(256):
data['ip'] = str(i) + ip
headers['X-Forwarded-For'] = str(i) + ip
r = requests.post(url, data=data, headers=headers)
if '助力成功' in r.text:
print(i, ' Success')
time.sleep(1.5)
图之上的信息
查看题面
小 T 听说 GraphQL 是一种特别的 API 设计模式,也是 RESTful API 的有力竞争者,所以他写了个小网站来实验这项技术。
你能通过这个全新的接口,获取到没有公开出来的管理员的邮箱地址吗?
又是一个没听说过的东西,先打开题目网站,通过guest帐号登录:
打开f12刷新页面,发现一条名为graphql的请求:
通过简单的学习和分析可知这种请求的格式大概长这样:
{
Query(
field1: value1
field2: value2
...
){
field3
field4
...
}
}
感觉就是用field1、field2等字段来筛选查找,获取field3、field4等字段。
那如何知道有哪些查询方式以及字段呢,简单了解以后,发现可以通过对接口发起一个这样的查询:
{
__schema {
types {
name
}
}
}
分析一下拿到的数据即可。不过我当时懒得去分析这些数据,而是下了一个软件
在右侧,该软件直接为我们解析了这个接口所有的查询接口以及字段名,依次点击user、GUser:
发现有个privateEmail字段。因为guest的id为2,猜测admin的id是1。因此,只需构造如下的查询:
即可拿到flag。
Easy RSA
查看题面
自从 Hackergame 2018 公然揭露了大整数可以被神童口算分解的事实,RSA 在 hackergame 中已经只能处于低分值的地位了。如果不在其名称前面加上 Easy 这个单词,似乎就会显得完全对不起其他题目。
更何况,在本题的附件中,你还获得了构造 p 和 q 的方式。数理基础扎实的你应该可以轻松解决这些问题吧。
如题所说,这是一道比较简单的RSA,先看flag相关的代码:
p = get_p()
q = get_q()
m = int.from_bytes(open("flag.txt", "rb").read(), "big")
c = pow(m, e, p * q)
print("c = ", c)
# c = 110644875422336073350488613774418819991169603750711465190260581119043921549811353108399064284589038384540018965816137286856268590507418636799746759551009749004176545414118128330198437101472882906564195341277423007542422286760940374859966152871273887950174522820162832774361714668826122465471705166574184367478
发现这就是一个普通的RSA,而且居然直接给了p、q的生成函数。
先看get_p
:
def get_p():
x = 11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391760451
y = 11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391661439
value_p = sympy.nextprime((math.factorial(y)) % x) # Hint:这里直接计算会溢出,请你仔细观察 x 和 y 的特征
return value_p
要得到p就要计算y! % x,但这里x、y都贼大,因此不能直接通过math.factorial
来计算y的阶乘。我本科是数学专业出身,数理基础扎实/doge,由阶乘求模联想到曾经学过的Wilson定理:
在p为素数时成立。
那么只要验证一下x是不是素数:
果然是素数,那么就可以通过Wilson定理逆推了,代码如下:
import gmpy2
import sympy
x = 11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391760451
y = 11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391661439
# (x-1)!mod x=x-1
# (x-k-1)!mod x=(((x-k)!mod x) * invert(-k)) mod x
def get_p(x, y):
"""p = next_p(y! % x) where x is prime"""
r = x - 1
n = x - 1 - y
for i in range(1, n + 1):
r = (r * gmpy2.invert(- i, x)) % x
return sympy.nextprime(int(r))
p = get_p(x, y)
对于下面的q,只要看懂了生成过程,其实比p更容易求出来:
def get_q():
value = [getPrime(256)]
for i in range(1, 10):
value.append(sympy.nextprime(value[i - 1]))
print("value[-1] = ", value[-1])
# value[-1] = 80096058210213458444437404275177554701604739094679033012396452382975889905967
n = 1
for i in range(10):
n = n * value[i]
q = getPrime(512)
value_q = pow(q, e, n)
print("value_q = ", value_q)
# value_q = 5591130088089053683141520294620171646179623062803708281023766040254675625012293743465254007970358536660934858789388093688621793201658889399155357407224541324547522479617669812322262372851929223461622559971534394847970366311206823328200747893961649255426063204482192349202005330622561575868946656570678176047822163692259375233925446556338917358118222905050574458037965803154233167594946713038301249145097770337253930655681648299249481985768272321820718607757023350742647019762122572886601905212830744868048802864679734428398229280780215896045509020793530842541217790352661324630048261329493088812057300480085895399922301827190211956061083460036781018660201163819104150988531352228650991733072010425499238731811243310625701946882701082178190402011133439065106720309788819
return sympy.nextprime(q)
通过阅读代码,得知获取q只需解一个推广的RSA(模数有多个因子的情形),而且因子全部已知。计算q的代码:
import sympy
import gmpy2
value = [80096058210213458444437404275177554701604739094679033012396452382975889905967]
for _ in range(1, 10):
value.insert(0, sympy.prevprime(value[0]))
e = 65537
n = 1
r = 1
for i in range(10):
n = n * value[i]
r = r * (value[i] - 1)
value_q = 5591130088089053683141520294620171646179623062803708281023766040254675625012293743465254007970358536660934858789388093688621793201658889399155357407224541324547522479617669812322262372851929223461622559971534394847970366311206823328200747893961649255426063204482192349202005330622561575868946656570678176047822163692259375233925446556338917358118222905050574458037965803154233167594946713038301249145097770337253930655681648299249481985768272321820718607757023350742647019762122572886601905212830744868048802864679734428398229280780215896045509020793530842541217790352661324630048261329493088812057300480085895399922301827190211956061083460036781018660201163819104150988531352228650991733072010425499238731811243310625701946882701082178190402011133439065106720309788819
d = int(gmpy2.invert(e, r))
q = sympy.nextprime(pow(value_q, d, n))
拿到p、q以后,我们又拥有密文c,很容易获取到明文。代码如下:
c = 110644875422336073350488613774418819991169603750711465190260581119043921549811353108399064284589038384540018965816137286856268590507418636799746759551009749004176545414118128330198437101472882906564195341277423007542422286760940374859966152871273887950174522820162832774361714668826122465471705166574184367478
d = int(gmpy2.invert(e, (p - 1) * (q - 1)))
m = pow(c, d, p * q)
flag = int.to_bytes(m, 28, 'big')
print(flag)
flag{CRYPT0_1s_Interesting!}
本题全部解题代码:
import gmpy2
import sympy
x = 11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391760451
y = 11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391661439
# (x-1)!mod x=x-1
# (x-k-1)!mod x=(((x-k)!mod x) * invert(-k)) mod x
def get_p(x, y):
"""p = next_p(y! % x) where x is prime"""
r = x - 1
n = x - 1 - y
for i in range(1, n + 1):
r = (r * gmpy2.invert(- i, x)) % x
return sympy.nextprime(int(r))
p = get_p(x, y)
value = [80096058210213458444437404275177554701604739094679033012396452382975889905967]
for _ in range(1, 10):
value.insert(0, sympy.prevprime(value[0]))
e = 65537
n = 1
r = 1
for i in range(10):
n = n * value[i]
r = r * (value[i] - 1)
value_q = 5591130088089053683141520294620171646179623062803708281023766040254675625012293743465254007970358536660934858789388093688621793201658889399155357407224541324547522479617669812322262372851929223461622559971534394847970366311206823328200747893961649255426063204482192349202005330622561575868946656570678176047822163692259375233925446556338917358118222905050574458037965803154233167594946713038301249145097770337253930655681648299249481985768272321820718607757023350742647019762122572886601905212830744868048802864679734428398229280780215896045509020793530842541217790352661324630048261329493088812057300480085895399922301827190211956061083460036781018660201163819104150988531352228650991733072010425499238731811243310625701946882701082178190402011133439065106720309788819
d = int(gmpy2.invert(e, r))
q = sympy.nextprime(pow(value_q, d, n))
c = 110644875422336073350488613774418819991169603750711465190260581119043921549811353108399064284589038384540018965816137286856268590507418636799746759551009749004176545414118128330198437101472882906564195341277423007542422286760940374859966152871273887950174522820162832774361714668826122465471705166574184367478
d = int(gmpy2.invert(e, (p - 1) * (q - 1)))
m = pow(c, d, p * q)
flag = int.to_bytes(m, 28, 'big')
print(flag)
这道题的第二种解法:如题目开头 Hackergame 2018 公然揭露了大整数可以被神童口算分解的事实所说,在中科大少年班有着能够心算RSA的天才,相信这类巨佬应该也具备心算大整数阶乘的能力,所以只要找到一个这样的天才,让他帮你心算,即可轻松得到flag。
加密的 U 盘
查看题面
这是一个关于 LUKS (Linux Unified Key Setup) 的故事。
第一天
小 T:「你要的随机过程的课件我帮你拷好了,在这个 U 盘里,LUKS 加密的密码是 suijiguocheng123123。」
小 Z:「啊,你又搞了 Linux 文件系统加密,真拿你没办法。我现在不方便用 Linux,我直接把这块盘做成磁盘镜像文件再回去处理吧。」
第二天
小 Z:「谢谢你昨天帮我拷的课件。你每次都搞这个加密,它真的安全吗?」
小 T:「当然了!你看,你还给我之后,我已经把这块盘的弱密码改掉了,现在是随机生成的强密码,这样除了我自己,世界上任何人都无法解密它了。」
小 Z:「我可不信。」
小 T:「你不信?你看,我现在往 U 盘里放一个 flag 文件,然后这个 U 盘就给你了,你绝对解密不出来这个文件的内容。当初搞 LUKS 的时候我可研究了好几天,班上可没人比我更懂加密!」
这题又出现了没听说过的东西:LUKS。
下载题目文件,发现有两个img文件:day1.img、day2.img。目的是挂载day2.img来获取flag。
先用fdisk看一下day1.img的分区情况:
fdisk -l day1.img
Disk day1.img: 20 MiB, 20971520 bytes, 40960 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: E1D1730D-1029-44A4-898B-FEBC77E7884F
Device Start End Sectors Size Type
day1.img1 2048 40926 38879 19M Linux filesystem
把day1.img1分区dd出来并挂载到/mnt看看:
dd if=day1.img of=header1.dd bs=512 skip=2048
sudo cryptsetup luksOpen header1.dd day1 #输入密码 suijiguocheng123123
sudo mount /dev/mapper/day1 /mnt
ls /mnt
lost+found 随机过程.txt
啊这,还真的是随机过程,我居然打开来仔细看了一遍,感慨随机过程白学了的同时并没有找到其他有用的信息(
研究cryptsetup命令发现了一个东西:
cryptsetup luksHeaderRestore
由于前面研究了很长一段时间LUKS头部的meta data,感觉只要能把day2.img的头部信息恢复到day1.img的状态,可能就可以用day1的密钥解锁镜像了,不然题目告诉我day1的密码就真的一点用都没有,我是不信的。
因此这个命令引起了我的注意。
在前面的基础上简单操作了一下(先把前面的/mnt取消挂载):
dd if=day2.img of=header2.dd bs=512 skip=2048
sudo cryptsetup luksHeaderRestore header2.dd --header-backup-file header1.dd
WARNING!
========
Device header2.dd already contains LUKS2 header. Replacing header will destroy existing keyslots.
Are you sure? (Type uppercase yes): YES
sudo cryptsetup luksOpen header2.dd day2
输入suijiguocheng123123试了下,果然成功了,接下来挂载day2即可获取flag。
sudo mount /dev/mapper/day2 /mnt
cat /mnt/flag.txt
flag{changing_Pa55w0rD_d0esNot_ChangE_Luk5_ma5ter_key}
事实上,LUKS真正用于加密文件系统的密钥并非用户设置的密码,用户设置的密码只是用来给真正的密钥(master key)加密用的,因此修改密码并不会修改加密用的密钥,从而有了破解此题的理论可能性。
Micro World
查看题面
宇宙中某一片极其微小的区域里的粒子被一股神秘力量初始化设置成了 flag 的形状,程序忠实地记录了一段时间之后这片区域的粒子运动情况。
下载文件,发现是个exe,于是跑去windows系统运行了一下,发现是很多粒子在乱飞,题目意思看上去是说,要找到这些粒子的初始状态(排列成flag形状)。
由于是个逆向题,首先用IDA打开,胡乱看了一通代码,发现果然看不懂。随意看了一下String,发现一堆Py开头的变量,推测是Python打包的程序。因此谷歌“python exe逆向”,找到工具pyinstxtractor.py(v1.9),在Python3.8环境下运行:
python pyinstxtractor.py microworld.exe
拿到一个文件夹,其中有两个没有后缀的文件:2、struct。用16进制查看器打开struct和2两个文件,把struct文件的前16个字节复制到2文件的最前面,然后将2重命名为2.pyc。(如果你前面用的pyinstxtractor.py是2.0版本的,那么无需操作这一步,直接运行下面的umcompyle6就可以了。)
使用uncompyle6(pip install uncompyle6)反编译2.pyc:
uncompyle6 2.pyc > 2.py
拿到一个未加混淆的py文件:
# uncompyle6 version 3.7.4
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.7.11 (default, Jul 27 2021, 14:32:16)
# [GCC 7.5.0]
# Embedded file name: 2.py
import time, pygame, random, math
WIDTH = 800
HEIGHT = 480
FPS = 30
RADIUS = 6
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Micro world')
clock = pygame.time.Clock()
running = True
count = 0
list_ = [
(104.57999766036382, 294.30576115498934, 0.1577677901040423, 0.41776948474620357), (97.87091160071468, 284.0771467313936, -0.05370797410649919, -1.5117973702589511), (394.1386262922653, 342.409197651039, 1.257922260374631, -0.6461638773589035), (383.764164219779, 351.02042667556873, 0.20006041825672996, 0.6886413335669115),
(180.44019240845162, 133.7535956479616, -1.2484762435639396, -0.24929662016190135), (184.78982911642964, 145.40211792122318, -0.0022636999589985274, 0.5513600856872762), (115.03335937165885, 300.9993063319222, -0.5676054221924623, -0.6695938101055833), (428.3301722808014, 85.83680504269783, -0.0513590947698387, 0.2619177140188991), (421.5916683285764, 72.17809124687787, 0.2014421765254144, -1.4100046681437837), (162.58901945971377, 126.99241616764206, 0.030293407742100736, -0.8004786559033131), (160.9895035322225, 145.70895141366148, 0.34929641702611086, 1.0244323100199488), (99.54962866775641, 349.46917135540673, 0.8725254958889044, 0.18026779894381553), (113.4935960089335, 350.07171367926867, 1.3948726673436584, -0.054645034381742424), (243.53937023444496, 324.4815163708293, 0.7754113027163299, -0.35104389069767705), (236.07107039080046, 336.93661006373446, 0.48878994205902065, 0.3668410462786823), (142.25100857351947, 107.18317281548164, 0.8769918696942771, 0.3381787797086514), (126.65532126550701, 101.44841747837386, -0.23643662821636835, -0.7474233162903591), (93.89796091125481, 337.69531331910224, 1.0011924240124088, 0.3786722871573705), (80.48068970171919, 325.1970583485235, -0.7399280117105376, -0.1701776000030069), (562.4548633698985, 293.8157173337281, 0.9232890949122534, -0.6707164772798287), (544.2230504332125, 298.5096170399827, -0.45781767619323493, 0.20143165105280758), (591.0728484545633, 384.0271820277358, -0.3546100658269741, -0.11818203300203947), (587.8300731972232, 397.21466538385005, 0.1658293862635661, 0.42343564083856233), (160.96290809954047, 113.1243147430774, 0.3297720052539864, -0.9088842057483848), (341.609111706211, 174.90102865131772, -0.6451128281395191, 0.6625764312805231), (361.89732768708996, 171.7398998209568, 0.9626383465113062, -0.45377732846689295), (413.2372099784884, 109.2059457838029, 0.06068110806079574, 0.7460129288950222), (428.03790114833487, 103.0981885607253, 0.7102816563423606, 0.5965135218875973), (632.823035103914, 183.41308729879773, 0.9426699419434019, -0.17948947163331008), (619.693443152725, 171.6281942118759, -0.4050225991049363, -0.3004630650084027), (234.93804029190275, 314.4850986917503, 0.6079647151190387, -0.3527147434952942), (388.84210326921414, 329.1140520966929, -0.25641606835531666, 0.013640064895390436), (431.54831561818656, 328.2835752571257, 0.0862740779218544, 0.30203328636401916), (449.4737324005318, 318.59090969779714, 0.8493162548457772, -0.39087008306977955), (414.9662287493531, 321.7992369591167, -0.38427287382474645, -0.8225562013945713), (393.14019608044197, 166.3219823104028, -0.8606903726780473, 0.44501048112586244),
(407.74279750683405, 174.07932270043037, -0.41221110195676625, 0.5621672919603494), (459.79766287684146, 367.8798142900581, -0.013886373621122439, -0.3180180360181074), (481.3079661009345, 389.2929845754805, 1.308536261884114, 0.6279064260682542), (299.0319011022105, 142.56295134440845, 0.5129247847316193, 0.6861624164039957), (284.8757848267405, 115.44737423970206, -0.39395187391162256, -0.7247085199267874), (721.0242237005709, 208.11018311759165, 0.5031252275481168, 0.45273074175310746), (698.5559327045538, 206.46532762419102, -0.4253037092008059, -0.10234863067289382), (238.01728536837834, 96.94484935393817, -1.0004911938691854, -0.649305316642717), (231.42367571047265, 113.24222441346936, -1.0247825937879875, -0.17624171000468863), (219.78600179605175, 374.1104577170864, -0.6368728728325652, -0.17569384553335526), (246.11065722883504, 371.79606791842366, 0.5219343181987446, -0.3462866864847798), (61.288999430029705, 325.3049764118085, 0.06327048049117068, 0.43129466994014554), (44.508614390256625, 300.0898728637909, -0.6949149408422702, -0.6606674558084397), (444.7436476698611, 97.43495673585237, 0.0767541318435591, -0.2776251496223229), (744.2355976846196, 142.86962390689527, 0.9981335574540283, -0.24606523573449057), (722.4382189578434, 148.50423479439266, 0.17497076263720399, -0.184532590143744), (418.5327228664219, 136.72695351434712, 0.5475576395865829, -0.8913163462276698), (695.9021288583592, 180.5828160193858, -0.9488479900701872, -0.7148885273332122), (514.3530295181225, 176.69505705865964, 0.4250410294309581, 0.24872469962151272), (491.3217475016306, 192.06501755019133, -0.5852344731438237, 0.6683151007063167), (404.93366351040646, 355.33847789785887, -0.2208752264067056, 0.5785115096521176), (660.6042829909404, 388.98594646068375, -0.2802404219269444, -0.8854758825918975), (657.1412902684084, 422.0142602600062, -0.09955316106014461, 0.6262242796545058), (324.601539532362, 71.99880574130555, -0.44523640628825367, -0.7982939572849916), (313.7688791782026, 100.51158011600064, -0.8550429986673138, 0.23687636012864005), (463.05020279897207, 354.3841115016863, 0.5259364738387711, -0.030002820893658896), (307.8633146779248, 151.5952286725273, 0.6622908641009807, -0.03664136082024409), (398.41892028466214, 111.78772135693538, 0.11954324115479029, -0.8254277998217231), (381.45123257781245, 136.6036806395423, -0.6799079499520264, -0.08600175586428575), (292.6839863142225, 92.60685790044825, 0.536452531252875, -0.7140993636579337), (107.00966574475697, 373.9070654764243, -0.7010200558637464, -0.1381120246392485), (145.16889322345122, 365.860466913724, 0.5965222398714648, -0.5001275164663491), (353.3938458530145, 92.28564527857638, 0.27648661676701847, 0.4055937158607218), (332.6003342739462, 150.35292606144606, 0.16791054337808475, 0.19838069473157122), (333.2155442174266, 135.41367942003444, -0.1948746996045789, -0.8713050639597644), (341.7828985904844, 176.40085292375244, 0.15777998878645658, 0.45702848410002833), (352.9649338991208, 63.40320810353417, 0.31005939791813875, -0.35531540613337653), (558.759849975508, 335.82850463010334, -0.3751838713627865, 0.6972047893312435), (658.9879897953409, 243.4647341215956, -0.2407221233734072, -0.9276449712809698), (680.2245351443412, 296.75251078114536, 0.4337786026208906, 0.9727281158269319), (341.72634396514303, 378.7867598373575, -0.03596521226118493, 0.51167641447653), (697.0597317905844, 409.2588838533252, 0.9048830590914927, -0.4657488561253242), (665.1731546911783, 437.3925803649936, -0.007368744952089001, 0.3787660493964195), (67.00785464657952, 103.1274278061125, 0.03732794987331456, -0.5508360071810254), (105.96964960562794, 128.97178091353496, 0.8136907261343687, 0.4063622560568454), (105.07041437177854, 61.05750261646497, 0.26186719895475497, -0.5163887919827821), (68.49603305059723, 73.94521027290915, -0.7594061833112205, -0.7798070269292883), (71.34190872889897, 124.87773643827933, -0.4688181952259638, -0.5600838356192892), (64.27757202298889, 159.8496885573903, -0.6934232584078162, -0.042604127504053846), (70.57044794264237, 176.93801328964406, -0.4233167428650957, -0.11340691519836832), (144.63770817171127, 69.41378490162104, -0.08749228993661817, -0.28097092956958947), (137.8210753472867, 150.33804975066357, -0.191812024174572, 0.6051129537282753), (137.26460390655052, 180.16854638211603, -0.21242207753516196, 0.11735356970799926), (165.50112593068218, 175.5434604690403, 0.3148565159511876, -0.3132051678133363), (158.21970276683984, 160.0662466663162, -0.399270267894807, -0.47902790124753714), (162.68670402259963, 93.7322224457243, -0.9745665176814846, -0.9728806501583553), (200.74175804924195, 121.50562533623162, -0.6762311833614205, -0.2034953579173453), (224.47067029408507, 148.9360402550957, 0.46187667755869377, -0.22459110166312768), (249.5998504282539, 128.78709867759278, 0.8740685343797732, -0.6745519008298879), (222.1623163178933, 162.24461613732666, -0.40139569192986624, 0.04609689397505523), (270.1496673364134, 119.26292107574434, 0.44998767912644677, -0.8791510712687354), (251.7055547368385, 167.7628397486525, -0.9368313060430262, 0.6578829536537905), (275.1172494773191, 184.09798794704585, -0.5141759452844961, -0.033407853813111066), (265.9957830031348, 199.51750836889886, -0.25941544432835606, 0.056204013662934704), (282.07956665413514, 164.958740535395, 0.8918358020049804, -0.7422688690594414), (353.4402011639725, 114.78109493306745, 0.7570444875545246, -0.415515002478986), (356.5222794940665, 213.68778310329276, 0.6860103516321112, 0.9143623371590044), (330.49971249176855, 226.5949460000718, -0.6111217595640996, 0.873886888891541), (454.61901786077544, 150.71441166327446, 0.8747784392879676, 0.9153485801212855), (388.60627446909075, 197.93977442346403, -0.23680464929294276, 0.14591757123939741), (423.67540627845943, 205.2755085720821, -0.5305405082052022, 0.8620558730400902), (477.52821141998515, 197.94782386045253, 0.8714152377772004, -0.11304356072398081), (509.7764508897392, 142.38940000013736, -0.3416129300096671, 0.4959037037087939), (517.4542284218678, 157.00198576512298, -0.8720656140048659, 0.7778513246341929), (489.14570184081595, 143.71628106431027, -0.43904807996975315, 0.17467707645594244), (484.36624455679043, 162.18554614981508, -0.3197687201188777, 0.006872079622769922), (538.1915570784884, 186.90845701394394, 0.2663539658698997, 0.9595724819979277),
(558.4402698588482, 151.2562488554182, 0.20149147625361707, -0.1756944868363639), (498.8936687068798, 219.16941966684988, -0.522456714559989, 0.747015543216671), (557.5264047150413, 194.47492406099926, 0.686163137594151, -0.13055836811114152), (572.2642293852141, 133.05544915189392, -0.36058409684388026, 0.18723885747756386),
(593.233462901164, 148.90845332339745, 0.11975788522826214, -0.04042765468897547), (592.1067952919601, 200.97587238888386, -0.2553038780755985, 0.7398471255142076), (620.8001885800545, 156.51793210692475, -0.5999930155535687, 0.3525160039601669), (630.0497687231773, 138.21403356381515, -0.7018604176601002, 0.3042234653264848), (656.2600646659411, 156.841032404578, -0.9903679753354846, 0.7348530520214087), (711.2887269504378, 97.8513508705996, 0.3810639611272786, -0.8943944122000136), (738.7664844418454, 116.88118100970506, 0.6209809052535715, -0.41180811075166335), (703.8040510631283, 152.5920657076647, 0.511261150486223, -0.5706642330494665), (740.09107559372, 198.6981029696348, 0.5218916886562541, 0.1740038136901716), (657.6864804602145, 215.93482850112167, -0.9745747977698767, 0.034623277819314735), (71.54711083768379, 350.36060634700226, 0.6498929939882905, -0.6162738389999025),
(150.9033697831391, 276.73234512286984, 0.9223470290051474, 0.582679448995163), (172.75073798985846, 238.05776406669546, 0.9907680736984741, -0.886749479011274), (166.14454393170783, 261.9146208279891, -0.10575763215896572, -0.04019922859299774), (135.8179319546022, 304.5167891568187, 0.51177525757786, 0.6117329317340197), (109.29600036825342, 324.14080376797784, -0.32237035673135206, -0.47626652711191975), (128.25054047146125, 342.5039087606924, 0.41668668412819376, -0.6480033792336315), (160.9435843133916, 336.7772890102673, 0.22013275234783625, 0.4732329263061923), (159.99063987213424, 357.3713520468878, 0.14780147674570876, -0.8751351093745308), (203.82463986148133, 294.85292265982395, 0.36387555042522823, -0.8943361977843205), (238.77834673828744, 257.8957469467245, 0.69549432364027, -0.966824187158376), (234.71098679446786, 302.43763534262735, 0.6189254368321369, -0.02082832064340412), (199.13592069493149, 350.92258754952445, -0.5134844187062317, -0.2621263870546515), (246.08239218788768, 387.5706266290919, 0.5215700810328909, -0.12701382855216137), (279.81163336690304, 310.1193743783627, -0.5995691345591287, 0.8192360880875091), (312.43826713316605, 326.3110183009928, 0.6088247086357645, -0.02551784070396379), (278.3124563204679, 346.2774946054354, -0.618057173316024, 0.23249980020133165), (309.0606334597058, 364.2832298989901, 0.5578012392483811, 0.34382332959224393),
(277.86613144487796, 384.4110746203211, -0.5605136501896855, 0.422632393345197), (297.7963837815137, 417.4215462126431, 0.1776438437597616, 0.978575785653446), (308.64648313310744, 389.32195957645587, -0.8279080321071313, 0.048961465794687076), (332.9030266443413, 352.59935912758954, -0.9665545687281056, 0.614791078799594), (348.26013943242486, 314.11612768512305, -0.842217058058359, -0.032736011662137576), (360.3306887191309, 292.2383422919451, -0.7655300474395692, -0.32450584103905666), (396.9404109804546, 370.8911519658965, -0.15035514887206558, -0.04106844570751811), (400.104874692782, 411.57211198606, -0.1442639002673347, 0.761930073557795), (440.30114216085207, 361.3409051871098, 0.9000423022537898, -0.2836701782551865), (429.3543769941238, 341.9294444418788, 0.3094213701527606, -0.33594650215266264), (447.43603366759595, 277.2285471339229, 0.38651976546653843, -0.880424180225089), (481.7939322275633, 301.5421301772078, -0.15578028786801723, -0.09103221565894848), (496.05798504633856, 291.5141421831062, -0.0719264797652579, -0.09206880803313222), (509.5366997343411, 286.81707674748407, -0.16530741724662823, 0.14137321286975357), (512.6359971378648, 331.33312820778906, -0.7542223282272489, 0.9012269706588789), (565.2777793457861, 322.789250206497, -0.8786007649708629, 0.6588611187591347), (616.2993903993898, 307.1147976370016, 0.30738482960705515, 0.07832583840747809), (549.4707698926467, 355.7327131244734, -0.6862677817538163, 0.5086190046101378), (566.0048628120813, 359.0888370802809, -0.8146347106636107, 0.521808780751152), (603.0173174232779, 355.65724327656727, -0.22158083617492497, 0.09841641765061859), (627.8321653818715, 343.97419893606997, 0.4011913104396747, -0.9268815208862793), (537.4339433151019, 408.0628758894442, -0.9839280253666145, 0.5949213292386479), (640.8558916665046, 286.24303230003744, -0.30163364198134635, 0.3052974925939915), (693.3776564065166, 322.7209553086005, 0.6806539409820418, 0.8785539003185334), (677.9197897159304, 332.5251925812629, 0.1451773968863177, 0.42685898449123116), (715.7380915196851, 359.6720022622894, 0.8791885748031647, 0.9508148986033234), (654.241096201209, 350.8310224121308, -0.8058853258811696, 0.25300083007890595), (660.0062961624873, 367.58093595080226, -0.6293964384264055, 0.09559022040005982), (674.0852620040955, 376.20299215975325, -0.07091622207048798, -0.362852142231338)]
class Point:
def __init__(self, pos, vx, vy):
self.x, self.y = pos
self.vx = vx
self.vy = vy
self.flag = 1
def dotproduct(v1, v2):
return v1[0] * v2[0] + v1[1] * v2[1]
def checkcrush(point1, point2):
distance = math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2)
a = (point1.vx - point2.vx) ** 2 + (point1.vy - point2.vy) ** 2
b = 2 * (point1.x - point2.x) * (point1.vx - point2.vx) + 2 * (point1.y - point2.y) * (point1.vy - point2.vy)
c = distance ** 2 - 4 * RADIUS ** 2
delta = b ** 2 - 4 * a * c
if delta < 0:
return
time = (-b - math.sqrt(delta)) / (2 * a)
if time > 0:
if time < 1:
return time
return
def get_new_point(time, point1, point2):
distance = math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2)
standard_displacement = ((point2.x - point1.x) / distance, (point2.y - point1.y) / distance)
v_1 = (point1.vx, point1.vy)
v_2 = (point2.vx, point2.vy)
v_par_1 = dotproduct(standard_displacement, v_1)
v_par_2 = dotproduct(standard_displacement, v_2)
v_ver_1 = (point1.vx - v_par_1 * standard_displacement[0], point1.vy - v_par_1 * standard_displacement[1])
v_ver_2 = (point2.vx - v_par_2 * standard_displacement[0], point2.vy - v_par_2 * standard_displacement[1])
v_after_par_1 = v_par_2
v_after_par_2 = v_par_1
v_after_1 = (v_after_par_1 * standard_displacement[0] + v_ver_1[0], v_after_par_1 * standard_displacement[1] + v_ver_1[1])
v_after_2 = (v_after_par_2 * standard_displacement[0] + v_ver_2[0], v_after_par_2 * standard_displacement[1] + v_ver_2[1])
afterpos_1 = (point1.x + point1.vx * time + v_after_1[0] * (1 - time), point1.y + point1.vy * time + v_after_1[1] * (1 - time))
afterpos_2 = (point2.x + point2.vx * time + v_after_2[0] * (1 - time), point2.y + point2.vy * time + v_after_2[1] * (1 - time))
return (Point(afterpos_1, v_after_1[0], v_after_1[1]), Point(afterpos_2, v_after_2[0], v_after_2[1]))
def drawpoint(screen, list_):
for item in list_:
pygame.draw.circle(screen, BLUE, (round(item.x), round(item.y)), RADIUS, 3)
def next_pos_list(Pointlist):
pointlist = []
for i in range(len(Pointlist)):
for point in Pointlist[i + 1:]:
times = checkcrush(Pointlist[i], point)
if times != None:
a, b = get_new_point(times, Pointlist[i], point)
pointlist.extend([a, b])
Pointlist[i].flag = 0
point.flag = 0
else:
for item in Pointlist:
if item.flag != 0:
pointlist.append(Point((item.x + item.vx, item.y + item.vy), item.vx, item.vy))
for poi in pointlist:
poi.x = poi.x % WIDTH
poi.y = poi.y % HEIGHT
else:
return pointlist
Pointlist = []
for item in list_:
Pointlist.append(Point((item[0], item[1]), item[2], item[3]))
else:
def value(lis):
count = 0
for item in lis:
count = count + (item.x - round(item.x)) ** 2 + (item.y - round(item.y)) ** 2
else:
return count
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(BLACK)
drawpoint(screen, Pointlist)
Pointlist = next_pos_list(Pointlist)
pygame.display.flip()
pygame.quit()
# okay decompiling 2.pyc
容易读懂程序模拟了一堆粒子的运动,粒子之间有完全弹性碰撞。
不过我一开始没注意缩进,踩了两个坑,uncompyle6在反编译时会出现一些缩进的错误,需要手动改一下,这题的文件好像有3个缩进错误的地方。
改完缩进以后,将下面代码修改一下(在表示速度的量前面加个负号),运行程序,粒子即反向运动,拿个手机录屏,慢慢拖到能辨认flag的位置。
Pointlist = []
for item in list_:
# Pointlist.append(Point((item[0], item[1]), item[2], item[3]))
Pointlist.append(Point((item[0], item[1]), -item[2], -item[3]))
flag{Rev3sEtiM^5}
另外在赛后我又找到了一个pyc文件反编译工具:python-decompile3,用它对前面拿到的2.pyc进行反编译,则不会出现缩进的问题。
马赛克
查看题面
反马赛克,试了一下炼丹,但是失败了。但分析生成代码,发现它是一个简单的均值池化马赛克,故其实有很多块是可以通过类似扫雷的思路完全确定的,大概只有中间的一些无法确定,但由于马赛克本身具有容错性,对无法确定的块随机选择也可以解出来,或者可以稍微写一下随机的策略,可以解的更快。解题代码如下:
import random
from copy import deepcopy
from pyzbar.pyzbar import decode
import numpy as np
from itertools import combinations
from PIL import Image
X, Y = 103, 137 # 马赛克左上角位置(单位为像素)
N = 20 # 马赛克块的数量(共N*N块)
BOX_SIZE = 23 # 每个马赛克块的大小(边长,单位为像素)
PIXEL_SIZE = 11 # 二维码每个块的大小(边长,单位为像素)
img = Image.open('pixelated_qrcode.bmp').convert('L')
img = np.array(img)
def fill(x, y, strict=True):
x1 = X + x * BOX_SIZE
y1 = Y + y * BOX_SIZE
W = np.array([11 - x1 % 11, 11, 1 + x1 % 11])
H = np.array([11 - y1 % 11, 11, 1 + y1 % 11])
mean = img[x1, y1]
areas, indexes = [], []
area = 0
for i, j in np.ndindex(3, 3):
if status[x1 // 11 + i][y1 // 11 + j] == 1:
area += W[i] * H[j] * result[x1 + i * 11, y1 + j * 11]
else:
areas.append(W[i] * H[j] * 255)
indexes.append((i, j))
output = match_sum(areas, area, mean, strict)
if output is False:
return
for id_ in output:
i, j = indexes[id_]
i, j = x1 // 11 + i, y1 // 11 + j
result[i * 11: i * 11 + 11, j * 11: j * 11 + 11] = 255
for i, j in np.ndindex(3, 3):
status[x1 // 11 + i][y1 // 11 + j] = 1
def match_sum(L, S, mean, strict=True):
avgs = np.array([abs((sum(pair) + S) // (23 * 23) - mean) for n in range(len(L) + 1) for pair in combinations(L, n)])
index = [pair for n in range(len(L) + 1) for pair in combinations(range(len(L)), n)]
mins = np.where(avgs.min() == avgs)[0]
if strict and len(mins) > 1 and avgs.min() < 1:
return False
return index[random.choice(mins)]
result = np.zeros((627, 627))
status = np.zeros((57, 57))
for i, j in np.ndindex(57, 57):
if i * 11 < X or j * 11 < Y:
result[i * 11: i * 11 + 11, j * 11: j * 11 + 11] = img[i * 11, j * 11]
status[i, j] = 1
elif i * 11 + 10 > X + 20 * 23 or j * 11 + 10 > Y + 20 * 23:
result[i * 11: i * 11 + 11, j * 11: j * 11 + 11] = img[i * 11 + 10, j * 11 + 10]
status[i, j] = 1
for i in range(20):
for j in range(20):
fill(i, j)
status_bak = deepcopy(status)
result_bak = deepcopy(result)
while 1:
status = deepcopy(status_bak)
result = deepcopy(result_bak)
for i in range(20):
for j in range(20):
fill(i, j, False)
barcodes = decode(Image.fromarray(result))
for barcode in barcodes:
barcode_url = barcode.data.decode("utf-8")
print(barcode_url)
虽然通过while 1不断尝试,但感觉几乎每次都能跑出来flag,很少有识别不出来的情况:
minecRaft
查看题面
kk 同学很喜欢玩 Minecraft,他最近收到了一张 MC 地图,地图里面有三盏灯,还有很多奇奇怪怪的压力板。
但他发现这些灯好像不太符合 MC 电磁学(Red stone),你能帮他把灯全部点亮吗?
注:本题解法与原版 Minecraft 游戏无关。
补充说明:flag 花括号内为让三盏灯全部点亮的最短的输入序列。例如,如果踩踏压力板输入的最短的序列为 abc,则答案为 flag{abc}。
进入游戏,根据提示需要点亮顶上的三盏灯,在地图里跳了半天,并没有发现啥有价值的东西,但这题的定位是web,因此打开f12,然而出现了debugger无限阻塞调试,意识到它好像不太想让我调试JS代码,肯定在这里面有做文章。
在Console里运行一下下面这个函数,然后重启开发者工具窗口,就可以快速反反调试:
(function() {
var __Function__ = Function.__Function__ || Function;
var __Empty__ = Function.__Empty__ || function() {};
Function = function() {
for (var i=0; i<arguments.length; i++) {
if ((typeof arguments[i] == 'string') && arguments[i].indexOf('debugger')>=0) {
return __Empty__;
}
}
return __Function__.apply(this, Array.prototype.slice.call(arguments, 0));
}
Function.__proto__.constructor = Function;
Function.__Function__ = __Function__;
})();
接下来,去寻找点灯的代码,在index即主页文件里可以找到以下有用信息:
function printcinput(){
let content=document.getElementById('spann');
if (cinput[0]==='M') {
if (pressplateList[64].status===false){
pressplateList[64].TurnOn_redstone_lamp();
pressplateList[64].status=true;
}
}
if(cinput.length>=32){
let tbool=gyflagh(cinput.join(''));
if(tbool) {
pressplateList[65].TurnOn_redstone_lamp();
content.innerText='Congratulations!!!';
return;
}
cinput.length=0;
}
content.innerText=cinput.join('');
}
猜一下应该是需要我们第一下踩’M’,然后再踩31个字符并使得tbool
变量为True
,不过这里只有两个TurnOn_redstone_lamp
函数,却需要点三盏灯,试了一下发现第一下踩到M时总是会点亮两盏,大概是想让随机乱踩到M的同学觉得快要摸到flag了。
进入gyflagh
函数:
function gyflagh(_0x111955) {
const _0x50051f = _0x22517d;
let _0x3b790d = _0x111955[_0x50051f(0x1a8)](_0x50051f(0x1b7));
if (_0x3b790d === _0x50051f(0x1aa))
return !![];
return ![];
}
发现代码经过了混淆,但很容易通过调试知道它的意图,可以整理成以下样子(有经验的选手估计不需要整理):
function gyflagh(s) {
let encrypted = s['encrypt']('1356853149054377');
if (encrypted === '6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c')
return true;
return false;
}
看出是一个加密,对我们输入的字符串s用密钥”1356853149054377”加密得到结果为”6fbde674819a59bfa12092565b4ca2a7a11dc670c678681daf4afb6704b82f0c”,即可点亮三盏灯,对应的字符串”s”也就是flag(所以这题为什么不在math分类?)
接下来只要简单分析一下字符串的encrypt
方法即可,以下是我把代码还原成人能看的样子:
String['prototype']['encrypt'] = function(input_string) {
let array1 = new Array(2);
let s = '';
plaintext = escape(this);
for (var i = 0; i < 4; i++)
array2[i] = Str4ToLong(input_string['slice'](i * 4, (i + 1) * 4));
//array2 = [909456177, 825439544, 892352820, 926364468],由于array2仅与input_string相关,故可以直接算出来当常量用
for (i = 0; i < plaintext['length']; i += 8) {
array1[0] = Str4ToLong(plaintext['slice'](i, i + 4)),
array1[1] = Str4ToLong(plaintext['slice'](i + 4, i + 8)),
code(array1, array2),
s += LongToBase16(array1[0]) + LongToBase16(array1[1]);
}
return s;
}
其中,这里最重要的部分其实是code
函数,而其他几个函数例如Str4ToLong
、LongToBase16
在浏览器中都存有逆函数,可以在Console窗口直接调用。我们只要写一个code
的逆函数decode
即可。code函数反混淆以后如下:
function code(array1, array2) {
let a = array1[0]
, b = array1[1];
let array2 = [909456177, 825439544, 892352820, 926364468];//前面算出来的常量
const c = 2654435769
, d = 84941944608;
let i = 0;
while (i != d) {
a += (b << 4 ^ b >>> 5) + b ^ i + array2[i & 3],
i += c,
b += (a << 4 ^ a >>> 5) + a ^ i + array2[i >>> 11 & 3];
}
array1[0] = a,
array1[1] = b;
}
根据encrypt
方法,我们只需要关注array1
的两个元素的变化,也就是code里的a和b,所以decode
中我直接以a和b为参数,打印出还原后的a和b。decode
的编写只要把code
的代码反着写即可:
function decode(a, b){
i = 84941944608;
c = 2654435769;
let array2 = [909456177, 825439544, 892352820, 926364468];//前面算出来的常量
while (i != 0){
b -= (a << 4 ^ a >>> 5) + a ^ i + array2[i >>> 11 & 3],
i -= c,
a -= (b << 4 ^ b >>> 5) + b ^ i + array2[i & 3];
}
console.log(a,b);
}
这样,encrypt
方法的加密结果完全可以反推回去了。
flag{McWebRE_inMlnCrA1t_3a5y_1cIuop9i}
p😭q
查看题面
学会傅里叶的一瞬间,悔恨的泪水流了下来。
当我看到音频播放器中跳动的频谱动画,月明星稀的夜晚,深邃的银河,只有天使在浅吟低唱,复杂的情感于我眼中溢出,像是沉入了雾里朦胧的海一样的温柔。
这一刻我才知道,耳机音响也就图一乐,真听音乐还得靠眼睛。
(注意:flag 花括号内是一个 12 位整数,由 0-9 数位组成,没有其它字符。)
这题给了一个GIF以及一个生成GIF的脚本:
#!/usr/bin/env python3
from array2gif import write_gif # version: 1.0.4
import librosa # version: 0.8.1
import numpy # version: 1.19.5
num_freqs = 32
quantize = 2
min_db = -60
max_db = 30
fft_window_size = 2048
frame_step_size = 512
window_function_type = 'hann'
red_pixel = [255, 0, 0]
white_pixel = [255, 255, 255]
y, sample_rate = librosa.load("flag.mp3") # sample rate is 22050 Hz
power = librosa.feature.melspectrogram(y, sample_rate, n_mels=num_freqs,
n_fft=fft_window_size, hop_length=frame_step_size,
window=window_function_type)
db = librosa.power_to_db(power)
spectrogram = (numpy.around(db / quantize) * quantize)
gif_data = [numpy.kron(numpy.array(
[[red_pixel if freq % 2 and round(frame[freq // 2]) > threshold else white_pixel for threshold in list(range(
min_db, max_db + 1, quantize))[::-1]] for freq in range(num_freqs * 2 + 1)]),
numpy.ones([quantize, quantize, 1])) for frame in spectrogram.transpose()]
write_gif(gif_data, 'radio.gif', fps=sample_rate / frame_step_size)
估计是要通过脚本的逻辑从gif反向生成原始的音频。
注意到最后生成gif每一帧的函数:
gif_data = [numpy.kron(numpy.array(
[[red_pixel if freq % 2 and round(frame[freq // 2]) > threshold else white_pixel for threshold in list(range(
min_db, max_db + 1, quantize))[::-1]] for freq in range(num_freqs * 2 + 1)]),
numpy.ones([quantize, quantize, 1])) for frame in spectrogram.transpose()]
虽然完全看不懂,但可以猜测到gif每一帧的红色条子的高度是有用的信息,因此第一步就是把每一帧中红色条子的高度读成一个向量。
但我压根不懂这些频谱函数、分贝值之类的东西,因此我的方法是把第3题(自由电波)的mp3(懒得去找其他mp3了)用脚本跑了一下,拿到每一步的中间变量,例如spectrogram
,然后和gif每一帧读出来的array进行对比,发现:
vecs = []
gif = Image.open("radio.gif")
frames = ImageSequence.Iterator(gif)
for frame in frames:
frame = np.array(frame)[:, ::2]
vec = frame.sum(0)[1::2]
vecs.append(vec)
spec = np.array(vecs).astype('float32').transpose() - 60
这样就可以把gif还原回spectrogram
了,接下来只要看spectrogram
是怎么生成的,然后逆回去估计就差不多了:
生成代码是这样的:
power = librosa.feature.melspectrogram(y, sample_rate, n_mels=num_freqs,
n_fft=fft_window_size, hop_length=frame_step_size,
window=window_function_type)
db = librosa.power_to_db(power)
spectrogram = (numpy.around(db / quantize) * quantize)
这里最后一步好像是做了一个取整之类的处理,但对值的影响应该不会特别大,我就直接把这里的spectrogram
和db
视为同一个东西,那么只要根据spectrogram
得到power
就好了,由librosa.power_to_db
这个函数名猜测它还有一个叫db_to_power
的函数,我写的反向代码:
power = librosa.db_to_power(spec)
# 最后再生成wav文件
y = librosa.feature.inverse.mel_to_audio(power, sr=sample_rate,
n_fft=fft_window_size,
hop_length=frame_step_size,
window=window_function_type)
sf.write('flag.wav', y, sample_rate)
本题全部解题代码:
# -*- coding: utf-8 -*-
import os
from PIL import Image, ImageSequence
import numpy as np
import librosa
import soundfile as sf
num_freqs = 32
quantize = 2
min_db = -60
max_db = 30
fft_window_size = 2048
frame_step_size = 512
window_function_type = 'hann'
red_pixel = [255, 0, 0]
sample_rate = 22050
vecs = []
gif = Image.open("radio.gif")
frames = ImageSequence.Iterator(gif)
for frame in frames:
frame = np.array(frame)[:, ::2]
vec = frame.sum(0)[1::2]
vecs.append(vec)
spec = np.array(vecs).astype('float32').transpose() - 60
power = librosa.db_to_power(spec)
y = librosa.feature.inverse.mel_to_audio(power, sr=sample_rate,
n_fft=fft_window_size,
hop_length=frame_step_size,
window=window_function_type)
sf.write('flag.wav', y, sample_rate)
最后听一下flag.wav即可拿到flag。
flag{634971243582}
总结一下
作为一个非科班出身的菜狗,目前玩了三届Hackergame,感觉Hackergame兼具专业性与趣味性,题目难度的梯度也很适合我这种从0学起的萌新,从前年的三等奖到今年的组内第4,抛开大佬们都跑去出题了的因素,感觉自身的提升也比较明显,打个比赛收获非常多,不亚于任何一门CS的专业课。
明年争取多做几个逆向题吧!