ZJCTF 线上线下部分题目 writeup

前言

这次的比赛的难度并不算高。

线下赛里,web题的难度很低。而且题量也比较少,8个小时的比赛web就3题…其中2题,半小时左右就完成了。

另一道游戏题,JS加了混淆,JS不是很懂,所以最后靠队友打到15000分数拿到的flag(膜拜大佬,自己没超过3000分)

做完web,就开始划水摸鱼…然后这次逆向除了一道安卓送分题,其他好像有一定难度,队友没做出来,cry 和 misc 的难题,队友也没做出来。为啥 web 没难题呢,我摸鱼摸了一天…都不好意思起来了…,只能看队友们操作。线下赛没有什么很骚的操作,思路都很清晰。

线上赛的 web 注入题没做出来,感觉是过滤了 select 和 from 在一起的情况,这两一起出现就400错误。线上也有道游戏题,赛后据师傅们说跟线下赛的游戏题类似的。线上最后就解了两题,在专科组里还是以17的成绩混进了线下,太侥幸了。

线上赛

黑白分明

观察游戏 JS 文件, 获取到提交的地址

抓包获取 head响应包中的token,根据提示

对token进行base64 解密,提交

返回的内容提示断开连接,并响应了 set-cookie,猜测提交的请求中需要包含cookie,所以讲浏览器中的cookie添加到请求中,重新发送请求,然后获取到flag

欢迎参加

查看网页cookie,为084e0343a0486ff05530df6c705c8bb4,像一串MD5,解密后得到字符串’guest’

题目要求使用admin登录,将字符串’admin’使用MD5加密后保存为新的cookie(21232f297a57a5a743894a0e4a801fc3)

重新访问网页,显示如下

使用burp进行抓包,将Cookie值改成21232f297a57a5a743894a0e4a801fc3,发送,接收到的返回包如下

访问327a6c4304ad5938eaf0efb6cc3e53dc.php,得到flag

线下赛

万能密码

这题就一个登录框,根据提示,登录就能得到 flag,题目叫万能密码,打开题目一开始就试了 admin’ or ‘1’=’1 、admin’# 、admin” 结果都是提示用户名或密码错误…然后开始怀疑这题真的是万能密码吗..

然后把这题先放了一下,去看了下游戏题,玩了一会2048,突然想到这题可以去 fuzz 下

然后拿字典 fuzz 了一下,结果 flag 就出来了,可惜提交的时候已经只有150分了..

逆转思维

这题就是一个 php 伪协议和反序列化的一个简单应用,类似于 bugku 的 welcome to bugku 那题,思路很清晰,利用伪协议读源码,然后一步一步读取提示的文件的源码,最后最后利用序列化读取 flag

贰零肆捌

这题跟线上赛的黑白分明那题类似。但这题的JS加了混淆…然后我JS的函数又不是很熟悉..忘记准备JS手册了。还不能上网查,很尴尬。

一开始这题,发现有历史最高分和当前分数以及游戏状态的存储,就是页面刷新了也能保存住当前的游戏状态,从当前状态继续。

我就以为的思路是修改当前分数大于15000,因为这题题目说打到15000给flag,然后查看 local Storage 发现确实历史最高分和当前游戏分数存储在这里。

1
{"grid":{"size":4,"cells":[[{"position":{"x":0,"y":0},"value":2},{"position":{"x":0,"y":1},"value":8},{"position":{"x":0,"y":2},"value":16},{"position":{"x":0,"y":3},"value":2}],[{"position":{"x":1,"y":0},"value":2},{"position":{"x":1,"y":1},"value":4},{"position":{"x":1,"y":2},"value":8},{"position":{"x":1,"y":3},"value":2048}],[{"position":{"x":2,"y":0},"value":4},{"position":{"x":2,"y":1},"value":32},{"position":{"x":2,"y":2},"value":256},{"position":{"x":2,"y":3},"value":4}],[{"position":{"x":3,"y":0},"value":8},{"position":{"x":3,"y":1},"value":16},{"position":{"x":3,"y":2},"value":2},{"position":{"x":3,"y":3},"value":16}]]},"score":22104,"over":false,"won":true,"keepPlaying":false}

score 参数就是分数,修改其值大于 15000 ,然后刷新。发现并没有什么变化…

然后试着用 bp 抓包,看看大于 15000 分会不会有包发送,但还是没有… 然后接着玩游戏,玩死掉的时候,突然bp就拦截到了包。这里包忘记截图了。

数据内容是base64加密的内容,解密后是’score|当前小时|字符’ 大概是这样,有点忘记了。

发包之后响应包里提示 ‘你的head里少了什么,请注意打开方式’ 然后这里这里就懵逼了…

之后就让队友继续打游戏,开着 bp ,等分数超过15000然后死掉之后,抓包,在放包,flag就出来了

然后我们之后我把数据包的内容复制到我自己的bp里,重新发包,又是提示 ‘你的head里少了什么,请注意打开方式’ 这里有点懵逼为什么这样。

这题正确的解法应该是 ‘找到 JS 文件中,找到游戏分数时超过15000分发送的ajax请求的代码,然后分析出在请求中要携带什么参数,然后自己构造请求包来进行发送,获得flag’

但由于 js 不是很会,所以加了混淆 js 也没看懂。

简单逆向

接下来的 wp 是队友写的

使用Jeb反编译apk

看到调用了动态链接库文件

使用ida查看动态链接库文件

得到 flag

清廉校园

用winhex打开图片,最下发现字符串

怀疑是凯撒加密,解密得到flag

反推蟒蛇

这题最后几十分钟才放出来

其中 flag.enc 文件的内容为

1
57, 183, 124, 9, 149, 65, 245, 166, 175, 1, 226, 106, 216, 132, 224, 208, 139, 1, 188, 224, 9, 235, 106, 149, 141, 80

用工具反编译 pyo 文件得到

1
2
3
4
5
6
7
8
9
10
11
# Embedded file name: encrypt.py
from random import randint
from math import floor, sqrt
_ = ''
__ = '_'
____ = [ ord(___) for ___ in __ ]
_____ = randint(65, max(____)) * 255
for ___ in range(len(__)):
_ += str(int(floor(float(_____ + ____[___]) / 2 + sqrt(_____ * ____[___])) % 255)) + ' '

print _

这段程序是类似与随机数生成器的程序。这其中,只要 _____ 这个参数确定的话,这个参数就类似于随机数种子,但这个种子的值又受 __ 变量的影响,__ 变量的内容就是flag

因为flag的前6个值是可以确定的,为 zjctf{ 所以我们可以以此为突破口,先推出 _____ 这个随机数种子的值。脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
from random import randint
from math import floor, sqrt

flag = 'zjctf{'
flag_ascii = [ord(i) for i in flag]
f = ['57', '183', '124', '9', '149', '65']
for n in range(26010, 32640):
echo_flag_rand = []
e = n
for i in range(len(flag)):
echo_flag_rand.append(str(int(floor(float(e + flag_ascii[i]) / 2 + sqrt(e * flag_ascii[i])) % 255)))
if echo_flag_rand == f:
print(n)

这里可以跑出 e 即 _____ 这个随机数种子为 26010

然后我们已经知道 26010 了 然后就可以用这个去穷举 flag 的值,脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from math import floor, sqrt

f = [57, 183, 124, 9, 149, 65, 245, 166, 175, 1, 226, 106, 216, 132, 224, 208, 139, 1, 188, 224, 9, 235, 106, 149, 141, 80]
echo_flag_rand = ''
e = 26010
echo_flag = ''
for i in range(0, len(f)):
n = 0
for n in range(128, 64, -1):
echo_flag_rand = str(int(floor(float(e + n) / 2 + sqrt(e * n)) % 255)) + ' '
if int(echo_flag_rand) == f[i]:
echo_flag += chr(n)
break
print(echo_flag)

然后就可以穷举出 flag,当时这题思路的出来了,脚本来不及写…还是对 python 不够熟悉…写的慢