第一次打国赛,四个人没凑齐凑了三个人,不过打的还行,孩子打的很开心,明年还来(

Crypto

基于国密SM2算法的密钥密文分发

开靶机,nc 123.57.248.214 26759

下载附件,提供了一个SM2的介绍,阅读了解SM2算法以及本题的交互方式和流程:

用户建立连接,将个人信息上报服务器,并获取id;

用户使用SM2算法生成密钥对A(公钥A_Public_Key、私钥A_Private_Key),将密钥对A的公钥(A_Public_Key)传输给服务器;

服务器使用国SM2算法生成密钥对B(公钥B_Public_Key、私钥B_Private_Key)、同时产生16字节随机数C;服务器首先使用16字节随机数C对私钥B_Private_Key采用SM4ECB算法加密得到私钥B_Private_Key密文,然后使用A_Public_Key对16字节随机数C进行SM2加密得到随机数C密文;

服务器将公钥B_Public_Key明文、私钥B_Private_Key密文、随机数C密文传输给用户;用户向服务器请求密钥,服务器使用公钥B_Public_Key明文,对密钥D(16字节)采用SM2算法加密,将密钥D密文传输给用户;

用户想办法得到密钥D明文,将D上报验证。

先用命令行验证一下个人信息(名字,学校都经过url加密):

1
curl -d "name=%E7%8E%8B%E7%90%9B&school=%E5%B1%B1%E4%B8%9C%E7%A7%91%E6%8A%80%E5%A4%A7%E5%AD%A6&phone=18769999317" http://123.57.248.214:26759/api/login

返回自己的ID:

1
2
3
4
5
6
{
"message": "success",
"data": {
"id": "8a0cbcd0-d8c6-4f53-a324-881dfd90eb41"
}
}

这里使用SM2算法生成一对公私钥:

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
from random import SystemRandom

class CurveFp:
def __init__(self, A, B, P, N, Gx, Gy, name):
self.A = A
self.B = B
self.P = P
self.N = N
self.Gx = Gx
self.Gy = Gy
self.name = name

sm2p256v1 = CurveFp(
name="sm2p256v1",
A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,
B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93,
P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,
N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123,
Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7,
Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
)

def multiply(a, n, N, A, P):
return fromJacobian(jacobianMultiply(toJacobian(a), n, N, A, P), P)

def add(a, b, A, P):
return fromJacobian(jacobianAdd(toJacobian(a), toJacobian(b), A, P), P)

def inv(a, n):
if a == 0:
return 0
lm, hm = 1, 0
low, high = a % n, n
while low > 1:
r = high//low
nm, new = hm-lm*r, high-low*r
lm, low, hm, high = nm, new, lm, low
return lm % n

def toJacobian(Xp_Yp):
Xp, Yp = Xp_Yp
return (Xp, Yp, 1)

def fromJacobian(Xp_Yp_Zp, P):
Xp, Yp, Zp = Xp_Yp_Zp
z = inv(Zp, P)
return ((Xp * z**2) % P, (Yp * z**3) % P)

def jacobianDouble(Xp_Yp_Zp, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
if not Yp:
return (0, 0, 0)
ysq = (Yp ** 2) % P
S = (4 * Xp * ysq) % P
M = (3 * Xp ** 2 + A * Zp ** 4) % P
nx = (M**2 - 2 * S) % P
ny = (M * (S - nx) - 8 * ysq ** 2) % P
nz = (2 * Yp * Zp) % P
return (nx, ny, nz)

def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
Xq, Yq, Zq = Xq_Yq_Zq
if not Yp:
return (Xq, Yq, Zq)
if not Yq:
return (Xp, Yp, Zp)
U1 = (Xp * Zq ** 2) % P
U2 = (Xq * Zp ** 2) % P
S1 = (Yp * Zq ** 3) % P
S2 = (Yq * Zp ** 3) % P
if U1 == U2:
if S1 != S2:
return (0, 0, 1)
return jacobianDouble((Xp, Yp, Zp), A, P)
H = U2 - U1
R = S2 - S1
H2 = (H * H) % P
H3 = (H * H2) % P
U1H2 = (U1 * H2) % P
nx = (R ** 2 - H3 - 2 * U1H2) % P
ny = (R * (U1H2 - nx) - S1 * H3) % P
nz = (H * Zp * Zq) % P
return (nx, ny, nz)

def jacobianMultiply(Xp_Yp_Zp, n, N, A, P):
Xp, Yp, Zp = Xp_Yp_Zp
if Yp == 0 or n == 0:
return (0, 0, 1)
if n == 1:
return (Xp, Yp, Zp)
if n < 0 or n >= N:
return jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P)
if (n % 2) == 0:
return jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P)
if (n % 2) == 1:
return jacobianAdd(jacobianDouble(jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P), (Xp, Yp, Zp), A, P)

class PrivateKey:
def __init__(self, curve=sm2p256v1, secret=None):
self.curve = curve
self.secret = secret or SystemRandom().randrange(1, curve.N)

def publicKey(self):
curve = self.curve
xPublicKey, yPublicKey = multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)
return PublicKey(xPublicKey, yPublicKey, curve)

def toString(self):
return "{}".format(str(hex(self.secret))[2:].zfill(64))

class PublicKey:
def __init__(self, x, y, curve):
self.x = x
self.y = y
self.curve = curve

def toString(self, compressed=True):
return {
True: str(hex(self.x))[2:],
False: "{}{}".format(str(hex(self.x))[2:].zfill(64), str(hex(self.y))[2:].zfill(64))
}.get(compressed)


if __name__ == "__main__":
priKey = PrivateKey()
pubKey = priKey.publicKey()
print(priKey.toString())
print(pubKey.toString(compressed = False))
'''
744341248ec7ce80374c40c849753d533e20ec0a4cae1793bdbc21b1cc912d70
bb58b25650dafa605ae7e6438d5bee1e3f1ac09a8f72db7ab20757bccecf9161675fd9ab6290a8735002223ed4904e328c404cac6916bcadcb64ac3ec2fed42d
'''

并发送publicKey:

1
curl -d "id=8a0cbcd0-d8c6-4f53-a324-881dfd90eb41&publicKey=bb58b25650dafa605ae7e6438d5bee1e3f1ac09a8f72db7ab20757bccecf9161675fd9ab6290a8735002223ed4904e328c404cac6916bcadcb64ac3ec2fed42d" http://123.57.248.214:26759/api/allkey

得到返回的公钥B_Public_Key明文、私钥B_Private_Key密文、随机数C密文:

1
2
3
4
5
6
7
8
9
{
"message": "success",
"data": {
"publicKey": "042ef2a4521c7c08ae33d621acb7b7ca46c76c00fe846c46fbc49c20c3c2bf57cba2d4d3d4163a7abee466d4e600123793f9beb0195e61e0c18996dc3ad1e5b289",
"privateKey": "0541f45390fef991e12043c22da360148d5e40fc6a7da1b8ca17549e825a6430",
"randomString": "c6b6765bed02342373a1f5dfab3ea79f8a8099a26b74d3e8de5b86b6536cbf07a59c739f557c4de61f035f5051d17a4c55b391eb56d7c1e4974354b505154ef16476b1ff99cf4b0a2586a141ebeebc0b328405851e15ea7555b3b55a8606fe63ebb3fb227e660db42bc66fde2b72a195",
"id": "8a0cbcd0-d8c6-4f53-a324-881dfd90eb41"
}
}

获取一下quantum:

1
curl -d "id=8a0cbcd0-d8c6-4f53-a324-881dfd90eb41" http://123.57.248.214:26759/api/quantum

得到quantumString:

1
2
3
4
5
6
7
{
"message": "success",
"data": {
"id": "8a0cbcd0-d8c6-4f53-a324-881dfd90eb41",
"quantumString": "f667cc317d71768385c53cf05515e1ebd441cdccd0fb5031da0dae8fe944f876fb0f7bbc8313e8302cef9de28fe7ed41ab1a36e2a79bd5a85d47d8cd41cbe621686dec13d0b9b08c7d0edecf7f35750760e5c398e7182e37f44915f22d3a1bde6e4b0d1441e4f619d8d8bf4b13e94f12"
}
}

在阅读附件时,了解选手信息的返回值,发现存在一个名为quantumStringServer的值,这是服务器使用公钥B_Public_Key明文,对密钥D(16字节)采用SM2算法加密的原始明文:

查看个人信息,找到quantumStringServer:

1
curl -d "id=8a0cbcd0-d8c6-4f53-a324-881dfd90eb41" http://123.57.248.214:26759/api/search

得到quantumStringServer:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"message": "success",
"data": {
"id": "8a0cbcd0-d8c6-4f53-a324-881dfd90eb41",
"name": "王琛",
"school": "山东科技大学",
"phone": "18769999317",
"publicKey": "04ecc6686aa76d12f3e224cd921b07517e24e78b519ba52d8bcb52019d0c12ba21f12af1be6a802c0ce8e4fb250b581ea8c95fd1a8936179e82eabe59210ceeeb2",
"privateKey": "c12b47e113a721633e539f92d2d18d2131c00a506d4add04d911b1b9d08b683b",
"randomString": "7a70276347ae69eaaeeef818fa8c078b",
"quantumStringServer": "a57119343fe30a4dfe3f9e89f4cf7fe9"
}
}

发送刚刚获得的quantumStringServer:

1
curl -d "id=8a0cbcd0-d8c6-4f53-a324-881dfd90eb41&quantumString=a57119343fe30a4dfe3f9e89f4cf7fe9" http://123.57.248.214:26759/api/check
1
2
3
4
{
"message": "success",
"data": "结果正确,请访问 /api/search获取您的 flag"
}

再查看个人信息,得到flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"message": "success",
"data": {
"id": "8a0cbcd0-d8c6-4f53-a324-881dfd90eb41",
"name": "王琛",
"school": "山东科技大学",
"phone": "18769999317",
"publicKey": "04ecc6686aa76d12f3e224cd921b07517e24e78b519ba52d8bcb52019d0c12ba21f12af1be6a802c0ce8e4fb250b581ea8c95fd1a8936179e82eabe59210ceeeb2",
"privateKey": "c12b47e113a721633e539f92d2d18d2131c00a506d4add04d911b1b9d08b683b",
"randomString": "7a70276347ae69eaaeeef818fa8c078b",
"quantumStringServer": "a57119343fe30a4dfe3f9e89f4cf7fe9",
"quantumStringUser": "a57119343fe30a4dfe3f9e89f4cf7fe9",
"flag": "已完成考题,结果正确:flag{df5135d9-a437-458d-9500-20fa643caed5}"
}
}

感觉是非预期(

Sign_in_passwd

下载附件打开解压,得到两串字符串:

1
2
j2rXjx8yjd=YRZWyTIuwRdbyQdbqR3R9iZmsScutj2iqj3/tidj1jd=D
GHI3KLMNJOPQRSTUb%3DcdefghijklmnopWXYZ%2F12%2B406789VaqrstuvwxyzABCDEF5

看起来像自定义base64加密,第二行的编码表有几个url编码的字符,url解码一下,得到:

1
GHI3KLMNJOPQRSTUb%253DcdefghijklmnopWXYZ%252F12%252B406789VaqrstuvwxyzABCDEF5

进行自定义base64解码:

1
2
3
4
5
6
7
import base64

b64='j2rXjx8yjd=YRZWyTIuwRdbyQdbqR3R9iZmsScutj2iqj3/tidj1jd=D'
book1= 'GHI3KLMNJOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5'
book2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
print(base64.b64decode(b64.translate(str.maketrans(book1,book2))))
#b'flag{8e4b2888-6148-4003-b725-3ff0d93a6ee4}'

badkey(赛后)

第二天研究了一阵子这个题,不懂怎么构造ValueError就弄了个工作量证明就干不动了,赛后在NSSCTF上复现一下。

附件代码:

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
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from hashlib import sha256
import random, os, signal, string

def proof_of_work():
random.seed(os.urandom(8))
proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
print(f"sha256(XXXX+{proof[4:]}) == {_hexdigest}")
print('Give me XXXX: ')
x = input()
if len(x) != 4 or sha256(x.encode()+proof[4:].encode()).hexdigest() != _hexdigest:
print('Wrong PoW')
return False
return True

if not proof_of_work():
exit(1)

signal.alarm(10)
print("Give me a bad RSA keypair.")

try:
p = int(input('p = '))
q = int(input('q = '))
assert p > 0
assert q > 0
assert p != q
assert p.bit_length() == 512
assert q.bit_length() == 512
assert isPrime(p)
assert isPrime(q)
n = p * q
e = 65537
assert p % e != 1
assert q % e != 1
d = inverse(e, (p-1)*(q-1))
except:
print("Invalid params")
exit(2)

try:
key = RSA.construct([n,e,d,p,q])
print("This is not a bad RSA keypair.")
exit(3)
except KeyboardInterrupt:
print("Hacker detected.")
exit(4)
except ValueError:
print("How could this happen?")
from secret import flag
print(flag)

看完代码,首先发现解RSA前有一个工作量证明的操作,比较简单,先给做了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import itertools
import hashlib
import string
import sys
from pwn import *
from Crypto.Util.number import *

r = remote('node4.anna.nssctf.cn',28182)

k = (r.recvuntil(b') == ', drop=1)[-16:]).decode()
res = r.recvline().strip().decode()

for item in itertools.product(string.ascii_letters + string.digits, repeat=4):
str1 = ''.join(item) + k
m = hashlib.sha256(str1.encode()).hexdigest()
if m == res:
break

print(str1)

key1=''.join(item).encode()
print(key1.decode())
r.sendline(key1)

然后需要发送一个所谓的badkey,首先需要符合:

1
2
3
4
5
6
7
8
9
10
11
12
assert p > 0
assert q > 0
assert p != q
assert p.bit_length() == 512
assert q.bit_length() == 512
assert isPrime(p)
assert isPrime(q)
n = p * q
e = 65537
assert p % e != 1
assert q % e != 1
d = inverse(e, (p-1)*(q-1))

然后会把几个参数用RSA库还原密钥,如果构造出现了ValueError,就会返回flag:

1
2
3
4
5
6
7
8
9
10
11
try:
key = RSA.construct([n,e,d,p,q])
print("This is not a bad RSA keypair.")
exit(3)
except KeyboardInterrupt:
print("Hacker detected.")
exit(4)
except ValueError:
print("How could this happen?")
from secret import flag
print(flag)

比赛时就卡在这里不知道如何让还原的密钥出现ValueError的同时还要满足上面所有assert条件,赛后看wp才看明白。

可以通过查看RSA库的源码去发现如何使其ValueError:pycryptodome/RSA.py at master · Legrandin/pycryptodome · GitHub

直接打开网站后全局搜索ValueError,挨个找,找到# Verify consistency of the key这一大部分,这里用了很多种方法详细验证了密钥的一致性,如果不满足会返回ValueError:

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
# Verify consistency of the key
if consistency_check:

# Modulus and public exponent must be coprime
if e <= 1 or e >= n:
raise ValueError("Invalid RSA public exponent")
if Integer(n).gcd(e) != 1:
raise ValueError("RSA public exponent is not coprime to modulus")

# For RSA, modulus must be odd
if not n & 1:
raise ValueError("RSA modulus is not odd")

if key.has_private():
# Modulus and private exponent must be coprime
if d <= 1 or d >= n:
raise ValueError("Invalid RSA private exponent")
if Integer(n).gcd(d) != 1:
raise ValueError("RSA private exponent is not coprime to modulus")
# Modulus must be product of 2 primes
if p * q != n:
raise ValueError("RSA factors do not match modulus")
if test_probable_prime(p) == COMPOSITE:
raise ValueError("RSA factor p is composite")
if test_probable_prime(q) == COMPOSITE:
raise ValueError("RSA factor q is composite")
# See Carmichael theorem
phi = (p - 1) * (q - 1)
lcm = phi // (p - 1).gcd(q - 1)
if (e * d % int(lcm)) != 1:
raise ValueError("Invalid RSA condition")
if hasattr(key, 'u'):
# CRT coefficient
if u <= 1 or u >= q:
raise ValueError("Invalid RSA component u")
if (p * u % q) != 1:
raise ValueError("Invalid RSA component u with p")

return key

因为有前面assert的限制,前几种方法直接被排除了,发现一个可以利用的:

1
2
if Integer(n).gcd(d) != 1:
raise ValueError("RSA private exponent is not coprime to modulus")

这里需要:
$$
gcd(n,d)≠1
$$
由于n只有两个因子,所以也就有$d=ap$。

要想办法求出a,对于ed,我们知道$ed\equiv 1(\bmod p-1)$,可以尝试通过化简式子构造模逆运算得到a,变换一下:
$$
d=a+a(p-1)
$$

$$
ed=ea+ea(p-1)
$$

对上面的等式也进行模运算,得到:
$$
ea\equiv 1(\bmod p-1)
$$
a可以通过模逆运算求出,接下来把对应的q求出来,如果q为素数就说明生成了一组满足条件的数。
$$
ed-1=k(p-1)(q-1)
$$
变换,得到:
$$
\frac{eap-1}{p-1}=k(q-1)
$$
由于$a=inverse(e,p-1)$运算得到,e比较小,a应该和p-1位数接近,所以等式左边$\frac{eap-1}{p-1}$的位数应该比右边的位数稍大一点点,通过爆破k来得到q-1,验证q是否为素数,如果为素数就符合条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import gmpy2
from Crypto.Util.number import *

e=65537
def getbadkey():
while True:
p = getPrime(512)
a = gmpy2.invert(e, p - 1)
d = a * p
assert (e * d) % (p - 1) == 1
kq_1 = (e * a * p - 1) // (p - 1)
k = 1
while len(bin(kq_1 // k)[2:]) > 512:
k += 1
while len(bin(kq_1 // k)[2:]) == 512:
q = int(kq_1 // k + 1)
if kq_1 % k == 0 and isPrime(q):
return p,q
k += 1
print('Generate again!')

p,q=getbadkey()
print(p,q)

跑了一段时间得到了一对符合要求的p和q,发送pq得到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
import itertools
import hashlib
import string
import gmpy2
from pwn import *
from Crypto.Util.number import *

r = remote('node4.anna.nssctf.cn',28187)

k = (r.recvuntil(b') == ', drop=1)[-16:]).decode()
res = r.recvline().strip().decode()

for item in itertools.product(string.ascii_letters + string.digits, repeat=4):
str1 = ''.join(item) + k
m = hashlib.sha256(str1.encode()).hexdigest()
if m == res:
break

print(str1)
key1=''.join(item).encode()
r.sendline(key1)

p=12076642294777392341200649121962416337724839477860340097470142502776797439640035563284873377664904772372528584629239601698062542799561491154260556202925853
q=8441961566402997081524543425432673960094096161105682027271171001237952224619247624377132183414264560295416499070878664745607355793032867660533894966369859
r.sendline(str(p).encode())
r.sendline(str(q).encode())

r.recvline()
r.recvline()
r.recvline()
print(r.recvline().strip().decode())

misc

签到卡

打开靶机,进入网站,看到一个python打字机,python输入,随便输一些东西测试,如果出现报错会提供提示:

python3打印文件内容:
print(open('/etc/passwd').read())

直接打印flag即可:

print(open('/flag').read())

被加密的生产流量

下载附件wireshark打开流量包,追踪TCP流直接看到几段断断续续的编码,手动打一下:

MMYWMX3GNEYWOXZRGAYDA===

这是base32,直接解码得到flag。

国粹(赛后)

提供了三张全是麻将图片,有一张是很有顺序的类似于全图鉴一样的麻将表,另外两张非常长,有很多的麻将图片。

那个麻将表不知道为什么给了两排,还多了一个一萬,忽略即可。

题目的做法是根据顺序表对麻将进行数字编码,然后将对应两张图片的麻将依次解码,得到的两个图片转化成数组可以绘制一个坐标图,上面就能显示出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
42
43
44
45
46
47
48
49
50
51
from PIL import Image
import matplotlib.pyplot as plt


# 2279 = 43 * 53(width in single)
# 73 = 73(height in single)

img = Image.new("RGB", (2226, 73))
task = Image.open("题目.png")
img = task.crop((53, 0, 2279, 73))

a = Image.open("a.png")
k = Image.open("k.png")

def compare_image_regions(img1, img2, region1_box, region2_box):
region1 = img1.crop(region1_box)
region2 = img2.crop(region2_box)
pixels1 = region1.load()
pixels2 = region2.load()
if region1.size != region2.size:
return False
for x in range(region1.width):
for y in range(region1.height):
if pixels1[x, y][0:2] != pixels2[x, y][0:2]:
return False
return True

a_list = []
k_list = []
for i in range(a.width // 53):
for j in range(2226 // 53):
region1_box = (i * 53, 0, (i + 1) * 53, 73)
region2_box = (j * 53, 0, (j + 1) * 53, 73)
result = compare_image_regions(a, img, region1_box, region2_box)
if result:
a_list.append(j)
break
for i in range(a.width // 53):
for j in range(2226 // 53):
region1_box = (i * 53, 0, (i + 1) * 53, 73)
region2_box = (j * 53, 0, (j + 1) * 53, 73)
result = compare_image_regions(k, img, region1_box, region2_box)
if result:
k_list.append(j)
break

plt.show()
print(a_list)
print(k_list)
plt.scatter(a_list, k_list)
plt.show()

Reverse

babyRE

给了xml文件,用记事本打开,第一行看到:

1
<project name="re4baby22" app="Snap! 8.2, https://snap.berkeley.edu" version="2"><notes></notes>

看到一个网址,打开,简单了解了一下这个网站:

Snap.berkeley 是一种基于块编程语言,旨在引导儿童和青少年学习编程的编程环境。它是由加州大学伯克利分校开发的免费软件,已成为全球许多学校和机构的教育工具。Snap.berkeley 提供了大量的模块和工具,以帮助学生构建他们自己的程序,从简单的图形和动画到更复杂的计算机游戏和应用程序。它还具有类似于传统文本编程的基本编程概念,例如变量、循环和条件语句,因此可以帮助学生建立对计算机科学的基本理解。

类似于Scratch编程,用网站给的在线交互打开xml文件,看到程序:

简单跑了一下程序没有发现什么有用信息,查找程序的这几个对象,在lock里发现了加密代码,用了secret数组,进行了简单的异或运算,连续两个数挨个异或:

手打一下题目的secret数组,python异或回去,得到flag:

1
2
3
4
list=[102, 10, 13, 6, 28, 74, 3, 1, 3, 7, 85, 0, 4, 75, 20, 92, 92, 8, 28, 25, 81, 83, 7, 28, 76, 88, 9, 0, 29, 73, 0, 86, 4, 87, 87, 82, 84, 85, 4, 85, 87, 30]
for a in range(len(list)-1):
list[a+1]=list[a+1]^list[a]
print(''.join(chr(i)for i in list))