签到题

Chrome按F12打开开发者工具,可以看到输入框处的属性:

1
<p>Key: <input type="text" name="key" maxlength="13"/></p>

将maxlength属性删除再填入hackergame2018即可。

flag:flag{Hackergame2018_Have_Fun!}

猫咪问答

普通的问题,Google一下就可以找到答案:

1
1958 9211B026 9 TP311.1/94 3A202

提交后获得flag。(说出来我都不信,这题我是在手机上做的,还把视频看了一遍…)

flag:flag{G00G1E-is-always-YOUR-FRIEND}

游园会的集章卡片

用PS拼好就行了(我就是懒得拼最后的块了

flag:flag{H4PPY_1M4GE_PR0CE551NG}

猫咪和键盘

据说是只要拼好一行然后照着这行的顺序就能拼好别的行,然而我这种菜鸡没这么机智,只会一行一行拼好,坑爹的是ABC,BAC,CAB三个宏的定义处也要调整,在这里卡了半天…最后的部分如下。拼好后直接g++编译运行即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define ABC "FfQ47if9Zxw9jXE68VtGA"
#define BAC "JDk6Y6Xc88UrUtpK3iF8p"
#define CAB "7BMs4y2gzdG8Ao2gv6aiJ"

int main()
{
def_typed_printf(f_l_x_g_1, "%s%s%s%s");
f_l_x_g_1("fl")("a")("g")("{");
def_typed_printf(a_a_a_a_a_a_a_a_a, "%s%s%s%s%s%s%d");
a_a_a_a_a_a_a_a_a(ABC)("")(BAC)("")(CAB)("")('}');
def_typed_printf(def_typed_printf_, "%s%d%s");
def_typed_printf_("typed_printf")('_')("}");
return 0;
}

flag:flag{FfQ47if9Zxw9jXE68VtGAJDk6Y6Xc88UrUtpK3iF8p7BMs4y2gzdG8Ao2gv6aiJ125typed_printf95}

Word文档

地球人都知道Word文档是zip压缩文件…强行改名解压,里面有flag.txt:

1
$ cat flag.txt | tr -d '\n'

flag:flag{xlsx,pptx,docx_are_just_zip_files}

黑曜石浏览器

要求用黑曜石浏览器访问,HEICORE暗指的是哪个价值2个亿的Chrome皮肤大家都知道吧…先试了HEICORE做UA发现不行,上网找了一下还真有个官网,官网上注册的时候提示要用黑曜石浏览器注册,从网页反应时间来看这里的UA判断是在本地的,那么JS代码里肯定有HEICORE的UA,打开开发者工具,竟然有反调…到嘴的鸭子怎么能就这么飞了呢,在network选项卡查看之前的index.html:

有了UA之后当然是curl大法好:

1
$ curl -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) HEICORE/49.1.2623.213 Safari/537.36' http://202.38.95.46:12001/

flag:flag{H3ic0re_49.1.2623.213_sai_kou}

回到过去

瞎试了几次试对了…开始的q ed是退出之后又进了ed,进了ed后直接按键盘记录输一遍,在最后一个q之前加一句w flag保存起来,再查看flag,注意因为输入了一个Esc+C,会有个清屏,忽略之即可。

flag:flag{t4a2b8c44039f93345a3d9b2}

我是谁::哲学思考

打开开发者工具可以看到返回的状态码是418 I'M A TEAPOT,这个状态码是在一个经典的愚人节恶搞RFC中定义的,用HTTP协议控制咖啡壶和茶壶,脑洞开的真大…所以对方是一个teapot,填上去即得flag

flag:flag{i_canN0t_BReW_c0ffEE!}

我是谁::Can I help me?

RFC7168中定义了请求茶壶时可用的方法——POST和BREW,肯定不是POST这么普通的方法,发一个BREW请求过去:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *


def main():
msg='BREW /the_super_great_hidden_url_for_brewing_tea/ HTTP/1.1\r\nContent-type: message/teapot\r\nHost: 202.38.95.46:12005\r\n\r\n'
r=remote('202.38.95.46',12005)
r.send(msg)
print r.recvall()

if __name__=='__main__':
main()

发现这个茶壶只能煮红茶,给请求的路径加一个black_tea,再发一次即得flag

flag:flag{delivering_tea_to_DaLa0}

猫咪遥控器

别人怎么都那么高端会用canvas画图…本菜鸡只会用python拼出字符画,再用微软爸爸的编辑器vscode打开,按Ctrl+-缩小再目测:

flag:flag{MeowMeow}

她的诗

真够坑的…在解码出来的诗里面找了半天有没有什么隐写。原有的poem.txt编码方式为uuencode,特点是每行的第一个字符用来表示本行编码前的长度,此处的长度字符被故意设置为比原文稍短,python解码出来的数据行末被截短,flag也就没了。用其他的uuencode解码实现来解码可以看到被截短的部分:

flag:flag{STegAn0grAPhy_w1tH_uUeNc0DE_I5_50_fun}

猫咪克星

就是把发过来的算式直接eval,注意要把一些乱七八糟的坑爹表达式换掉(还好没发过来个__import__('os').system('rm -rf ~')啥的…我的pwntools是python2的,不过换掉print之后就没问题了,最后报错出来的信息就是flag)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
import re


def main():
r = remote('202.38.95.46', 12009)
r.recvuntil('nds\n')
while True:
c = r.recvuntil('\n')
print c
c = re.subn(r'exit\(\)', '0', c)[0]
c = re.subn(r'__import__\(\'time\'\).sleep\(\d*\)', '0', c)[0]
c = re.subn(r'print\(\'[a-z0-9\\x]*\'\)', '0', c)[0]
c = re.subn(r'__import__\(\'os\'\).system\(\'find ~\'\)', '0', c)[0]
print c
print eval(c)
r.sendline(str(eval(c)))


if __name__ == '__main__':
main()

flag:flag{'Life_1s_sh0rt_use_PYTH0N'*1000}

猫咪电路

红石电路,从信标的那一端直接反推即可,做完题目之后不小心陷入其中,玩了1个多小时…

flag:flag{0110101000111100101111111111111111111010}

FLXG 的秘密::来自未来的漂流瓶

看了半天的剧情…脑洞是真的大。卦的顺序众说纷纭,试了好几种终于找到对的了:

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
import base64


txt = open('flxg.txt', encoding='utf-8').read()
gua64 = ['坤', '剥', '比', '观', '豫', '晋', '萃', '否', '谦', '艮', '蹇', '渐', '小过', '旅', '咸', '遁',
'师', '蒙', '坎', '涣', '解', '未济', '困', '讼', '升', '蛊', '井', '巽', '恒', '鼎', '大过', '姤',
'复', '颐', '屯', '益', '震', '噬嗑', '随', '无妄', '明夷', '贲', '既济', '家人', '丰', '离', '革', '同人',
'临', '损', '节', '中孚', '归妹', '睽', '兑', '履', '泰', '大畜', '需', '小畜', '大壮', '大有', '夬', '乾']
b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
out = ''
assert(len(b64) == 64)
i = 0
l = len(txt)
while i < l:
if txt[i] in gua64:
out += b64[gua64.index(txt[i])]
i += 1
elif txt[i:i+2] in gua64:
out += b64[gua64.index(txt[i:i+2])]
i += 2
else:
print(txt[i:i+2])
raise ValueError
dec_out = base64.b64decode(out.encode('ascii'))
print(dec_out)
open('flxg_dec', 'wb').write(dec_out)

flag就在文件最后,出来的文件再用tar解包,可惜后面没逆出来那一堆lock…

flag:flxg{Power_of_the_Hexagram}

FLXG 的秘密::难以参悟的秘密

没做出来,别看了,丢人。

C 语言作业

IDA打开后可以看到__err函数可以执行一条不含sh的命令,而且__err还被注册成了好几个信号的处理函数。需要设法产生某种信号,输入-2147483648/-1(-0x8000000/-1),结果是2147483648(0x80000000),并不能被存储在int中(天秀啊),就会产生SIGFPE信号,进入__err后执行vim,在vim里执行:! cat /flag发现flag在-里,再执行:! cat /-,找到flag:

flag:flag{816484e67b21efd5de8f1661d180a007}

加密算法和解密算法

简单的brainfuck解析器(src是那段brainfuck代码):

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
a = open('src').read()
ptr = 0
addsum = 0
indent = 0
decsum = 0
for ch in a:
if ch == '+':
addsum += 1
continue
if ch == '-':
decsum += 1
continue
if addsum != 0:
print(' '*indent+'*p+=%d;' % addsum)
addsum = 0
if decsum != 0:
print(' '*indent+'*p-=%d;' % decsum)
decsum = 0
if ch == ',':
print(' '*indent+'*p=getchar();')
if ch == '.':
print(' '*indent+'print(*p);')
if ch == '>':
print(' '*indent+'p++;')
ptr += 1
if ch == '<':
print(' '*indent+'p--;')
ptr -= 1
if ch == '[':
print(' '*indent+'while(*p){')
indent += 1
if ch == ']':
print(' '*(indent-1)+'}')
indent -= 1

代码大致分为前面的输入处理和最后的输出,将输入处理部分按10次getchar()分开,可以看到每次输入一个字符,会进行两个while循环,以第一次的两个while循环为例:

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
*p=getchar();
while(*p){
*p-=1;
p++;
p++;
*p+=7;
p++;
p++;
*p+=5;
p++;
p++;
*p+=3;
p++;
p++;
*p+=2;
p++;
p++;
*p+=4;
p++;
*p+=6;
p--;
p--;
*p+=3;
p--;
p--;
*p+=9;
p--;
p--;
*p+=8;
p--;
p--;
*p+=6;
p--;
p--;
*p+=8;
p--;
}
p++;
while(*p){
*p-=1;
p++;
p++;
*p+=5;
p++;
p++;
*p+=9;
p++;
p++;
*p+=9;
p++;
p++;
*p+=2;
p++;
p++;
*p+=4;
p--;
*p+=9;
p--;
p--;
*p+=4;
p--;
p--;
*p+=4;
p--;
p--;
*p+=2;
p--;
p--;
*p+=2;
p--;
}
p--;

读入字符时存储在a[0],仔细观察第一个循环,每圈执行完后指针总会回到a[0]处,因此可以将循环简化:

1
2
3
4
5
6
7
a[0]=getchar();
while(a[0]){
a[0]-=1; a[2]+=7; a[4]+=5;
a[6]+=3; a[8]+=2; a[10]+=4;
a[11]+=6; a[9]+=3; a[7]+=9;
a[5]+=8; a[3]+=6; a[1]+=8;
}

后面的循环同样一圈执行完成后指针位置不会变,同样可以简化(注意两个循环之间指针由a[0]移至a[1]):

1
2
3
4
5
6
while(a[1]){
a[1]-=1; a[3]+=5; a[5]+=9;
a[7]+=9; a[9]+=2; a[11]+=4;
a[10]+=9; a[8]+=4; a[6]+=4;
a[4]+=2; a[2]+=2;
}

随后指针返回a[0]读入下一个输入,执行2个循环,直至10个字符全部读入…最后的输出部分为从a[2]开始输出至a[11],可以看出a[2]a[11]是关于10个输入的线性组合(说的高端一点这叫希尔密码),写脚本提取出系数矩阵(脚本太烂就不贴了),问题即为解线性同余方程组Ax===b(mod 64),注意256是64的倍数,因此直接模64即可(瞎猜的,将来flag出了偏差我不负责任的)。

可惜我这种菜鸡不会用z3,只能用高斯消去法求逆矩阵,(此处省略10分钟回忆线代知识)将系数矩阵A和单位矩阵E并排放好,通过行变换把A变为单位矩阵,旁边的E就变成了A的逆矩阵,即 [A|E]->…->…->…->[E|A^-1] 。(看官方的Writeup还求行列式和伴随矩阵是认真的吗hhh)。对于求模64的逆矩阵,将高斯消去法中的除以某数换成乘以这个数的模64乘法逆元即可,求得逆矩阵后用逆矩阵乘以密文向量就解出了明文向量,注意输出部分最后所加的常量:

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
EP = [[23, 69, 40, 61, 47, 21, 62, 73, 18, 81],
[46, 67, 40, 54, 31, 23, 54, 75, 64, 69],
[21, 80, 63, 33, 60, 26, 39, 32, 48, 39],
[80, 27, 69, 53, 37, 81, 24, 61, 23, 50],
[35, 22, 66, 43, 68, 36, 67, 22, 58, 37],
[81, 64, 51, 46, 37, 44, 75, 77, 71, 18],
[34, 79, 74, 52, 27, 19, 38, 79, 30, 68],
[19, 38, 52, 72, 49, 71, 36, 40, 60, 45],
[76, 55, 41, 68, 39, 62, 48, 65, 21, 66],
[38, 78, 43, 59, 55, 74, 50, 18, 36, 77]]

INV = [0 for i in range(64)]

for i in range(64):
for j in range(64):
if (i*j) % 64 == 1:
INV[i] = j
break

EXT = [EP[i]+[1 if i == j else 0 for j in range(10)]for i in range(10)]


def show(martix):
for i in range(10):
print(martix[i])


def add(martix, k, line1, line2):
for i in range(20):
martix[line2][i] = (k*martix[line1][i]+martix[line2][i]) % 64


def mul(martix, x, line):
for i in range(20):
martix[line][i] = (martix[line][i]*x) % 64


def swap(martix, line1, line2):
for i in range(20):
martix[line1][i], martix[line2][i] = martix[line2][i], martix[line1][i]


def mulline(martix, x):
y = [0]*10
for i in range(10):
for j in range(10):
y[i] += martix[i][j]*x[j]
y[i] = (y[i]) % 64
return y


for i in range(10):
if INV[EXT[i][i]] == 0:
for k in range(i+1, 10):
if INV[EXT[k][i]] != 0:
swap(EXT, i, k)
break
else:
raise ValueError
mul(EXT, INV[EXT[i][i]], i)
for j in range(10):
if j == i:
continue
add(EXT, (-EXT[j][i]) % 64, i, j)

DP = [EXT[i][10:] for i in range(10)]

BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'

ADD = [2, 6, 8, 8, 3, 5, 5, 7, 4, 9]


def enc(s):
sin = [BASE64.find(s[i]) for i in range(10)]
sout = mulline(EP, sin)
return ''.join([BASE64[sout[i]+ADD[i]] for i in range(10)])


def dec(s):
sin = [(BASE64.find(s[i])-ADD[i]) % 64 for i in range(10)]
sout = mulline(DP, sin)
return ''.join([BASE64[sout[i]] for i in range(10)])


def main():
cipher = ['JzRVPiVpqo', '4iDM8celyu', 'eIs4ff4DKe', 'G3EMKihzuH']
print('flag{%s}' % (''.join(map(dec, cipher))))


if __name__ == '__main__':
main()

(小声bibi:ZJU School Bus上某题和这个解法一毛一样

flag:flag{h1ll-c1ph3r-w1th-10x10-r3v3rs1bl3-matr1x}

她的礼物

看题目意思是输进去第10行就能算出flag,IDA打开可以看到主函数内的sleep(2u)system("echo -en '\a' > /dev/tty5")都是对于计算没有影响的,直接强行修改汇编指令,把call _systemcall _sleep分别换成5个nop,运行魔改过的程序,过了10秒又停了,这回是sub_401260里的alarm(0xAu)引发的闹钟所致,如法炮制把call _alarm也换掉,再运行就直接得到了flag(我就是懒得删掉输出歌词的部分了):

1
$ ./gift_patch2 "However, someday, someone will find it." | tail

flag:flag{HowEVER,_Somedaj,_sOMe0NE_wILl_FiND_it.}

困惑的 flxg 小程序

Windows逆向是真的迷…IDA打开之后发现主函数根本就是假的,在.rdata段中的几个输出的字符串下方.rdata:00000001400054D8处,有一堆明显疑似flag的数据,查一下引用,发现sub_140004980用到了这里,逆向一番可以看到,此函数将输入进行base64编码,strrev反过来,再依次异或0,1,2…最后和刚才那堆数据比较,上述过程反过来即可得到flag(这flag看着跟假的似的):

1
2
3
4
5
6
7
8
9
import base64


a = '\x39\x65\x45\x54\x77\x5F\x34\x5F\x64\x5F\x66\x68\x3C\x34\x58\x55\x7F\x43\x21\x4B\x7F\x20\x43\x76\x5F\x20\x4C\x4D\x7A\x53\x70\x7D\x56\x4D\x65\x47\x4C\x5D\x71\x43\x18\x6F\x47\x48\x42\x18\x1C\x4D\x74\x45\x01\x69\x00\x4D\x5B\x6D'
b = ''
for i in range(len(a)):
b += chr(ord(a[i]) ^ i)
c = b[::-1]
print(base64.b64decode(c.encode('ascii')))

flag:flxg{Congratulations_U_FiNd_the_trUe_flXg}

一些宇宙真理

要验证80个证明…看一下fork的源仓库,里面的示例程序是通过调用verify_proof(keypair.vk, *proof, h2_bv, h1_bv, x_bv)来验证证明的。那我就有young学young,写一个验证程序(写的贼烂,求轻喷):

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
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <sstream>

#include <boost/optional/optional_io.hpp>

#include "snark.hpp"
#include "test.h"

using namespace libsnark;
using namespace std;

int main(int argc, char const *argv[])
{
int ress[80];
default_r1cs_ppzksnark_pp::init_public_params();
r1cs_ppzksnark_verification_key<default_r1cs_ppzksnark_pp> vk_in;
ifstream vk_stream("toolkit/vk");
stringstream vkf;
if (vk_stream)
{
vkf << vk_stream.rdbuf();
vk_stream.close();
}
vkf >> vk_in;
std::vector<bool> h1_bv(256);
std::vector<bool> h2_bv(256);
std::vector<bool> x_bv(256);
std::vector<bool> r1_bv(256);
std::vector<bool> r2_bv(256);
{
h1_bv = int_list_to_bits({169, 231, 96, 189, 221, 234, 240, 85, 213, 187, 236, 114, 100, 185, 130, 86, 231, 29, 123, 196, 57, 225, 159, 216, 34, 190, 123, 97, 14, 57, 180, 120}, 8);
h2_bv = int_list_to_bits({253, 199, 66, 55, 24, 155, 80, 121, 138, 60, 36, 201, 186, 221, 164, 65, 194, 53, 192, 159, 252, 7, 194, 24, 200, 217, 57, 55, 45, 204, 71, 9}, 8);
x_bv = int_list_to_bits({122, 98, 227, 172, 61, 124, 6, 226, 115, 70, 192, 164, 29, 38, 29, 199, 205, 180, 109, 59, 126, 216, 144, 115, 183, 112, 152, 41, 35, 218, 1, 76}, 8);
r1_bv = int_list_to_bits({180, 34, 250, 166, 200, 177, 240, 137, 204, 219, 178, 17, 34, 14, 66, 65, 203, 6, 191, 16, 141, 210, 73, 136, 65, 136, 152, 60, 117, 24, 101, 18}, 8);
r2_bv = int_list_to_bits({206, 64, 25, 10, 245, 205, 246, 107, 191, 157, 114, 181, 63, 40, 95, 134, 6, 178, 210, 43, 243, 10, 217, 251, 246, 248, 0, 21, 86, 194, 100, 94}, 8);
}
for (int i = 1; i < 80; i++)
{
r1cs_ppzksnark_proof<default_r1cs_ppzksnark_pp> proof;
stringstream pfns;
pfns << "toolkit/proof_" << i;
std::string pfn = pfns.str();
ifstream pfis(pfn);
pfis >> proof;
bool res = verify_proof(vk_in, proof, h1_bv, h2_bv, x_bv);
ress[i] = res;
}
cout << "flag{";
for (int i = 1; i < 80; i++)
{
cout << ress[i];
}
cout << "}" << endl;
return 0;
}

这破程序怎么有这么多依赖库…git clone总是玄学失联,害得我还得一个一个去下载,怪不得做的人这么少…直接在原来的Makefile里加上两行,让make自动编译即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OPTFLAGS = -march=native -mtune=native -O2
CXXFLAGS += -g -Wall -Wextra -Wno-unused-parameter -std=c++11 -fPIC -Wno-unused-variable
CXXFLAGS += -I $(DEPSRC)/libsnark -I $(DEPSRC)/libsnark/depends/libfqfft -I $(DEPSRC)/libsnark/depends/libff -DUSE_ASM -DCURVE_ALT_BN128
LDFLAGS += -flto

DEPSRC=depsrc
DEPINST=depinst

LDLIBS += -L . -lsnark -lgmpxx -lgmp -lff -lprocps
LDLIBS += -lboost_system

all:
$(CXX) -o test.o src/test.cpp -c $(CXXFLAGS)
$(CXX) -o test test.o $(CXXFLAGS) $(LDFLAGS) $(LDLIBS)
$(CXX) -o my.o src/my.cpp -c $(CXXFLAGS)
$(CXX) -o src/my my.o $(CXXFLAGS) $(LDFLAGS) $(LDLIBS)

clean:
$(RM) test.o test

1
2
3
4
$ g++ -o my.o src/my.cpp -c -g -Wall -Wextra -Wno-unused-parameter -std=c++11 -fPIC -Wno-unused-variable -I depsrc/libsnark -I depsrc/libsnark/depends/libfqfft -I depsrc/libsnark/depends/libff -DUSE_ASM -DCURVE_ALT_BN128
$ g++ -o src/my my.o -g -Wall -Wextra -Wno-unused-parameter -std=c++11 -fPIC -Wno-unused-variable -I depsrc/libsnark -I depsrc/libsnark/depends/libfqfft -I depsrc/libsnark/depends/libff -DUSE_ASM -DCURVE_ALT_BN128 -flto -L . -lsnark -lgmpxx -lgmp -lff -lprocps -lboost_system
$ cd src
$ ./my

题目给的proof居然只有79个…运行完之后发现flag里0只有39个,所以第80个proof是0,加在flag后面即可。

flag:flag{10100100000101100110000001111011110101100101000011111111101000100100110100101110}


总结

我好菜啊…