2024强网杯线下赛

记录2024S8强网杯线下赛Crypto部分题解和RealWorld部分题目信息,还有自我感受

比赛分为两个部分,一个是Jeopardy(就是平时的解题赛),另外一个是RW(RealWorld,和实战没什么区别,看了下题目基本都是现场挖洞,第一天拿分的基本都是长亭和DAS)

比赛记录

Jeopardy

解题部分就只记录密码,因为别的我也不会啊(楽)

Shopping

task.py

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
from secret import flag
from Crypto.Util.number import *
from gmpy2 import *

def next_prime(x):
while not isPrime(x):
x += 1
return x

success = 0

for i in range(20):
coins = 27
space = 0
p = getPrime(64)
delta = getRandomNBitInteger(30)
q = next_prime(p + delta)
N = p*q
print("Welcome to my supermarket\n")
while coins > 0:
choice = input('give me your choice\n')
if choice == '1':
space = int(input("What size of house would you like to purchase?\n"))
assert 1 <= space <= 10
ls = [0] * space
coins -= space * 5
print(f'{coins} coins left\n')
elif choice == '2':
op = input()
assert op in ['+', '-', '*', '//', '%', 'root']
a, b, c= input().split('.')
try:
if op == 'root':
exec(f'{a}=iroot({b},{c})[0]')
else:
exec(f'{a}={b}{op}{c}')
except:exit
if op in '+-':
coins -= 1
elif op in '*//%':
coins -= 3
else:
coins -= 5
print(f'{coins} coins left\n')

elif choice == '3':
state = 0
print("One coin to check\n")
coins -= 1
print("You must have decorated a beautiful house.\n")
assert coins >= 0
for i in ls:
if i > 1 and i < N and N%i == 0:
success += 1
state = 1
print(f'wonderful!, still {coins} coins left\n')
break
if state:
break

if success == 20:
print(f'Congratulations! Here is your flag:{flag}\n')

$$
q = p + x
$$

$x \approx 2^{30}$
$$
n = pq = p(p + x)
$$

要求用+,-,%,//,*,iroot6个运算,进行有限步操作把n分解
$$
n = p^2 + px
$$

$$
n - px = p^2
$$

exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

sh = remote("47.94.85.95",28743)

for i in range(20):
sh.recvuntil(b"give me your choice")
sh.sendline(b"1")
sh.recvuntil(b"What size of house would you like to purchase?")
sh.sendline(b"1")
sh.recvuntil(b"give me your choice")
sh.sendline(b"2")
sh.sendline(b"//")
sh.sendline(b"ls[0].N.q")
sh.recvuntil(b"give me your choice")
sh.sendline(b"3")
sh.interactive()

bl0ck

aes.py

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env python3

# Implementation based on https://github.com/boppreh/aes/blob/master/aes.py
"""
This is an exercise in secure symmetric-key encryption, implemented in pure
Python (no external libraries needed).

Original AES-128 implementation by Bo Zhu (http://about.bozhu.me) at
https://github.com/bozhu/AES-Python . PKCS#7 padding, CBC mode, PKBDF2, HMAC,
byte array and string support added by me at https://github.com/boppreh/aes.
Other block modes contributed by @righthandabacus.


Although this is an exercise, the `encrypt` and `decrypt` functions should
provide reasonable security to encrypted messages.
"""

import random


s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)

inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)


def sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = s_box[s[i][j]]


def inv_sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = inv_s_box[s[i][j]]


def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]


def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]

def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] ^= k[i][j]


# learned from https://web.archive.org/web/20100626212235/http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)


def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)


def mix_columns(s):
for i in range(4):
mix_single_column(s[i])


def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v

mix_columns(s)


r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)


def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]

def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
return bytes(sum(matrix, []))

def xor_bytes(a, b):
""" Returns a new byte array with the elements xor'ed. """
return bytes(i^j for i, j in zip(a, b))


class AES:
"""
Class for AES-128 encryption with CBC mode and PKCS#7.

This is a raw implementation of AES, without key stretching or IV
management. Unless you need that, please use `encrypt` and `decrypt`.
"""
rounds_by_key_size = {16: 10, 24: 12, 32: 14}
def __init__(self, master_key, backdoor = None):
"""
Initializes the object with a given key.
"""
assert len(master_key) in AES.rounds_by_key_size
self.n_rounds = AES.rounds_by_key_size[len(master_key)]

# 😈 backdoored by crazyman
# ===
if backdoor is not None:
self.n_rounds = (self.n_rounds & 0b1010) - 1

self._key_matrices = self._expand_key(master_key)


operations = []

operations.extend([
(add_round_key, self._key_matrices[0])
])

for i in range(1, self.n_rounds):
operations.extend([
(sub_bytes, ),
(shift_rows, ),
(mix_columns, ),
(add_round_key, self._key_matrices[i])
])

operations.extend([
(sub_bytes, ),
(shift_rows, ),
(add_round_key, self._key_matrices[-1])
])

self.operations = operations

def _expand_key(self, master_key):
"""
Expands and returns a list of key matrices for the given master_key.
"""
# Initialize round keys with raw key material.
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4

i = 1
while len(key_columns) < (self.n_rounds + 1) * 4:
# Copy previous word.
word = list(key_columns[-1])

# Perform schedule_core once every "row".
if len(key_columns) % iteration_size == 0:
# Circular shift.
word.append(word.pop(0))
# Map to S-BOX.
word = [s_box[b] for b in word]
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
# Run word through S-box in the fourth iteration when using a
# 256-bit key.
word = [s_box[b] for b in word]

# XOR with equivalent word from previous iteration.
word = xor_bytes(word, key_columns[-iteration_size])
key_columns.append(word)

# Group key words in 4x4 byte matrices.
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]

def encrypt_block(self, plaintext):
"""
Encrypts a single block of 16 byte long plaintext.
"""
assert len(plaintext) == 16

plain_state = bytes2matrix(plaintext)

for operation, *args in self.operations:
operation(plain_state, *args)

return matrix2bytes(plain_state)

server.py

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 aes import AES
from secret import flag

def xor(a, b):
return bytes([x ^ y for x, y in zip(a * 2, b)])

class HAeSH:
def __init__(self, key):
self.state = b'\x00' * 16
self.key = key
self.blocks = []

def update(self, block, backdoor = None):
assert len(block) == 16 or len(block) == 24 or len(block) == 32, "🚫 Block length should be 16,24,32"
self.blocks.append(block)
mid_state = xor(self.state, block)
if len(self.blocks) > 1:
assert mid_state not in self.blocks, "🚫 Block repetition detected"
self.state = xor(self.state,
AES(mid_state, backdoor).
encrypt_block(self.key))

def digest(self):
return self.state

def blocks_length_check(blocks):
if len(blocks) <= 1: return True
return len(blocks[-1]) == len(blocks[0])

def main():
key = bytes.fromhex(input('🔑: '))
assert len(key) == 16, "Key length should be 16"
hash = HAeSH(key)
blocks = []
for _ in "HAeSH"[2:4]:
block = bytes.fromhex(input(f"🥢[{_}]: "))
hash.update(block, True)
blocks.append(block)
if not blocks_length_check(blocks):
print("🚫 Block length mismatch")
return
quit_option = input("🔚 Quit? (y/n): ")
if quit_option == "y":
break

if hash.digest() == b'\x00' * 16:
print("🔓 Success!")
print(f'🏁 {flag}')
else:
print("🔒 Failed!")

if __name__ == '__main__':
main()

注意到第一轮结束时 state = E_k(block1)

最后要求state = b"\x00"*16,那么我们就构造一个block1 = D_k(b"\x00"*16)使得b"\x00"*16 = E_k(block1)

这里有个坑点是,他用的是block1加密key,而非用key加密block1。

构造的话,根据9轮AES去逆就好了。

exp.py

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env python3

# Implementation based on https://github.com/boppreh/aes/blob/master/aes.py
"""
This is an exercise in secure symmetric-key encryption, implemented in pure
Python (no external libraries needed).

Original AES-128 implementation by Bo Zhu (http://about.bozhu.me) at
https://github.com/bozhu/AES-Python . PKCS#7 padding, CBC mode, PKBDF2, HMAC,
byte array and string support added by me at https://github.com/boppreh/aes.
Other block modes contributed by @righthandabacus.


Although this is an exercise, the `encrypt` and `decrypt` functions should
provide reasonable security to encrypted messages.
"""

import random


s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)

inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)


def sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = s_box[s[i][j]]


def inv_sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = inv_s_box[s[i][j]]


def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]


def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]

def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] ^= k[i][j]


# learned from https://web.archive.org/web/20100626212235/http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)


def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)


def mix_columns(s):
for i in range(4):
mix_single_column(s[i])


def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v

mix_columns(s)


r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)


def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]

def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
return bytes(sum(matrix, []))

def xor_bytes(a, b):
""" Returns a new byte array with the elements xor'ed. """
return bytes(i^j for i, j in zip(a, b))


class AES:
"""
Class for AES-128 encryption with CBC mode and PKCS#7.

This is a raw implementation of AES, without key stretching or IV
management. Unless you need that, please use `encrypt` and `decrypt`.
"""
rounds_by_key_size = {16: 10, 24: 12, 32: 14}
def __init__(self, master_key, backdoor = None):
"""
Initializes the object with a given key.
"""
assert len(master_key) in AES.rounds_by_key_size
self.n_rounds = AES.rounds_by_key_size[len(master_key)]

# 😈 backdoored by crazyman
# ===
if backdoor is not None:
self.n_rounds = (self.n_rounds & 0b1010) - 1

self._key_matrices = self._expand_key(master_key)

def _expand_key(self, master_key):
"""
Expands and returns a list of key matrices for the given master_key.
"""
# Initialize round keys with raw key material.
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4

i = 1
while len(key_columns) < (self.n_rounds + 1) * 4:
# Copy previous word.
word = list(key_columns[-1])

# Perform schedule_core once every "row".
if len(key_columns) % iteration_size == 0:
# Circular shift.
word.append(word.pop(0))
# Map to S-BOX.
word = [s_box[b] for b in word]
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
# Run word through S-box in the fourth iteration when using a
# 256-bit key.
word = [s_box[b] for b in word]

# XOR with equivalent word from previous iteration.
word = xor_bytes(word, key_columns[-iteration_size])
key_columns.append(word)

# Group key words in 4x4 byte matrices.
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]

def encrypt_block(self, plaintext):
"""
Encrypts a single block of 16 byte long plaintext.
"""
assert len(plaintext) == 16
operations = []

operations.extend([
(add_round_key, self._key_matrices[0])
])

for i in range(1, self.n_rounds):
operations.extend([
(sub_bytes, ),
(shift_rows, ),
(mix_columns, ),
(add_round_key, self._key_matrices[i])
])

operations.extend([
(sub_bytes, ),
(shift_rows, ),
(add_round_key, self._key_matrices[-1])
])

self.operations = operations
plain_state = bytes2matrix(plaintext)

for operation, *args in self.operations:
operation(plain_state, *args)

return matrix2bytes(plain_state)

def decrypt_block(self, ciphertext):
"""
Encrypts a single block of 16 byte long plaintext.
"""
assert len(ciphertext) == 16
print(ciphertext)
operations = []

operations.extend([
(add_round_key, self._key_matrices[-1])
])

for i in range(1, self.n_rounds):
operations.extend([
(inv_shift_rows, ),
(inv_sub_bytes, ),
(add_round_key, self._key_matrices[self.n_rounds-i]),
(inv_mix_columns,)
])

operations.extend([
(inv_shift_rows, ),
(inv_sub_bytes, ),
(add_round_key, self._key_matrices[0])
])

self.operations = operations
cipher_state = bytes2matrix(ciphertext)
for operation, *args in self.operations:
operation(cipher_state, *args)

return matrix2bytes(cipher_state)

key = bytes.fromhex('00000000000000000000000000000000')
aes = AES(key,backdoor=True)
ciphertext = b"\x00"*16
m = aes.decrypt_block(ciphertext)
print(m.hex())
# 1ce3875ccc5613de0c4025effc27db77

手动交互即可

fak1

ntru.sage

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
import random
from Crypto.Util.number import bytes_to_long,long_to_bytes

p,n,q,dg,df = 3,117,1091,35,37
R.<x> = PolynomialRing(Zmod(q))
RR.<xx> = PolynomialRing(Zmod(p))
QR = R.quotient(x ^ n + 1)
QRR = RR.quotient(xx ^ n + 1)
flag = b"flag{*******************************************}"

def balancemodlist(List,p):
for i in range(len(List)):
if int(List[i]) > p / 2:
List[i] = int(List[i]) - p
return List

def randomPoly(d = 0):
if d == 0:
poly = QR([random.randint(0,1) for _ in range(n)])
else:
L_0 = [0] * (n - 2 * d - 1)
L_1 = (d + 1) * [1]
L_2 = d * [-1]
L = L_0 + L_1 + L_2
for i in range(randint(5,10)):
random.shuffle(L)
poly = QR(L)
return poly

def genPrivKey():
while True:
poly = randomPoly(df)
try:
poly_q = (1 / QR(poly))
poly_p = (1 / QRR(RR(balancemodlist(poly.list(),q))))
break
except:
pass
return poly, poly_q, poly_p

def MsgtoPoly(msg):
msg = Integer(bytes_to_long(msg))
m = msg.digits(p)
for i in range(len(m)):
if m[i] > p / 2:
m[i] -= p
return QR(m)

def genKeys():
f, f_q, f_p = genPrivKey()
g = randomPoly(dg)
pk = QR(p * g * f_q)
sk = (f,f_q,f_p,g)
return pk,sk

def encrypt(pk, m):
r = randomPoly()
return pk * r + m

def decrypt(sk,c):
f,_,f_p,_ = sk
m = QR(balancemodlist((QRR(balancemodlist((f * c).list(),q)) * f_p).list(),p))
return m

task.py

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
from sage.all import *
from Crypto.Util.number import long_to_bytes
import os
import signal

def _handle_timeout(signum, frame):
raise TimeoutError('function timeout')

timeout = 60
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(timeout)

load("ntru.sage")
p,n,q,dg,df = 3,117,1091,35,37


cs = []
def Game():
while True:
pk,sk = genKeys()
try:
1 / pk
print(pk.list())
break
except:
pass

secret = os.urandom(23)
secretPoly = MsgtoPoly(secret)
print("Welcome to fak1. You can send some message to me. Have fun!")
msgs = [[0 for _ in range(116)]]

for _ in range(10):
msg = [round(int(i,16) % 3329) for i in input("Give me a list of message (hex):").split(" ")]
if len(msg) != 116 or (msg in msgs):
print("You bad!")
return
msgs.append(msg)
m = secretPoly + QR([round(i / 3329 * q) for i in msg])
c = encrypt(pk,m)
try:
assert m == decrypt(sk,c)
except:
print("You bad!!")
return
print(c.list())

if long_to_bytes(int(input("You wanner say something? :"), 16)) == secret:
print(flag)
print("You good!")
else:
print("OK, bye bye~")

Game()

由加密可知
$$
C_i = pk \times r_i + m_i
$$
记$m_i = Secret + M_i$,$M_i$为用户输入的明文

取两组进行分析
$$
C_i = pk\times r_i + m_i
$$

$$
C_j = pk\times r_j + m_j
$$

两式相减
$$
C_i - C_j = pk(r_i - r_j) + (m_i + m_j)
$$

$$
\therefore C_i - C_j = pk(r_i - r_j) + (M_i - M_j)
$$

我们可以计算出$r_i - r_j$的值
$$
r_i - r_j = ((C_i - C_j) - (M_i - M_j))\times pk^{-1}
$$
$r_i,r_j$的系数为0,1。然后讨论$r_i - r_j$的值

1
2
3
当ri - rj = 0的时候,有可能为0-0,也有可能1-1
当ri - rj = 1的时候,只可能是1-0,这样可以确定ri是1
当ri - rj = -1的时候,只可能是0-1,这样可以确定ri是0

我们可以有9组这样的值,很大概率能够恢复$r_i$。恢复$r_i$之后通过$C_i = pk\times r_i + (Secret + M_i)$恢复Secret

测试了一下

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
from Crypto.Util.number import *
from random import *
from pwn import *
import os

p,n,q,dg,df = 3,117,1091,35,37
R.<x> = PolynomialRing(Zmod(q))
RR.<xx> = PolynomialRing(Zmod(p))
QR = R.quotient(x ^ n + 1)
QRR = RR.quotient(xx ^ n + 1)

# 密钥生成
while True:
pk,sk = genKeys()
try:
1 / pk
# print(pk.list())
break
except:
pass

secret = os.urandom(23)
secretPoly = MsgtoPoly(secret)
print(f"secret = {secret}")

input_msgs = [[randint(0, 1) for _ in range(116)] for i in range(10)]
msgs = [QR([round(int(i) / 3329 * q) for i in msg]) for msg in msgs]
Cs = [encrypt(pk,secretPoly + m) for m in msgs]

# r0 - ri
Tmp = []
C0 = Cs[0]
M0 = msgs[0]
for i in range(1,10):
t = ((C0 - Cs[i]) - (M0 - msgs[i])) * pk^(-1)
Tmp.append(t)

r0 = ['?' for i in range(117)]
for tmp in Tmp:
t = tmp.list()
for i in range(len(t)):
if t[i] == 1:
r0[i] = 1
if t[i] == 1090:
r0[i] = 0
r0 = QRR(r0)
secret_poly = C0 - M0 - r0*pk
s = secret_poly.list()
ss = []

for i in s:
if(i == 1090):
ss.append(2)
else:
ss.append(i)
secret = 0
for i in range(len(ss)):
secret += int(3^i*int(ss[i]))

print(long_to_bytes(secret))

exp

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
from Crypto.Util.number import *
from random import *
from pwn import *

p,n,q,dg,df = 3,117,1091,35,37
R.<x> = PolynomialRing(Zmod(q))
RR.<xx> = PolynomialRing(Zmod(p))
QR = R.quotient(x ^ n + 1)
QRR = RR.quotient(xx ^ n + 1)

input_msgs = [[randint(0, 1) for _ in range(116)] for i in range(10)]
send_msgs = [" ".join([hex(i)[2:].zfill(2) for i in msg]) for msg in input_msgs]

sh = remote("47.94.85.95",30833)
pk = eval(sh.recvline().strip().decode())
pk = QRR(pk)
sh.recvuntil(b"Welcome to nntru. You can send some message to me. Have fun!")
Cs = []
for i in range(10):
sh.recvuntil(b"Give me a list of message (hex):")
sh.sendline(send_msgs[i].encode())
c = QRR(eval(sh.recvline().strip().decode()))
Cs.append(c)

# r0 - ri
TMP = []
C0 = Cs[0]
M0 = msgs[0]
for i in range(1,10):
t = ((C0 - Cs[i]) - (M0 - msgs[i])) * pk^(-1)
TMP.append(t)

r0 = ['?' for i in range(117)]
for tmp in TMP:
t = tmp.list()
for i in range(len(t)):
if t[i] == 1:
r0[i] = 1
if t[i] == 1090:
r0[i] = 0
r0 = QRR(r0)

secret_poly = C0 - M0 - r0*pk
secret_num = []

for i in secret_poly.list():
if i == 1090: # -1就是2
secret_num.append(2)
else:
secret_num.append(i)

secret = 0
for i in range(len(secret_num)):
secret += int(3^i*int(secret_num[i]))

sh.recvuntil(b"You wanner say something? :")
sh.sendline(hex(secret)[2:].encode())
print(sh.recvline().strip().decode())

f2ke——unsolved

task.py

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
from sage.all import *
from Crypto.Util.number import long_to_bytes
import os
import signal

def _handle_timeout(signum, frame):
raise TimeoutError('function timeout')

timeout = 70
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(timeout)

load("ntru.sage")
p,n,q,dg,df = 3,117,1091,35,37


def Game():
secret = os.urandom(23)
secretpoly = MsgtoPoly(secret)
print("Welcome to f2ke. You can send some message to me. Have fun!")
msgs = [[0 for _ in range(116)]]

for _ in range((n + 3) // 2):
while True:
pk,sk = genKeys()
try:
1 / pk
break
except:
pass
msg = [round(int(i,16) % 3329) for i in input("Give me a list of message (hex):").split(" ")]
if len(msg) != 116 or (msg in msgs):
print("You bad!")
return
msgs.append(msg)
m = secretpoly + QR([round(i / 3329 * q) for i in msg])
c = encrypt(pk,m)
try:
assert m == decrypt(sk,c)
except:
print("You bad!!")
return
print(c.list())
print(pk.list())

if long_to_bytes(int(input("You wanner say something? :"), 16)) == secret:
print(flag)
print("You good!")
else:
print("OK, bye bye~")

Game()

Signin——unsolved

task.py

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
from hashlib import sha256
from Crypto.Util.number import inverse, bytes_to_long
from random import randint
from os import urandom
from secret import flag
import signal

p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
a = 0
b = 7
gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

zero = (0, 0)
G = (gx, gy)


def add(p1, p2):
if p1 == zero:
return p2
if p2 == zero:
return p1
(p1x, p1y), (p2x, p2y) = p1, p2
if p1x == p2x and (p1y != p2y or p1y == 0):
return zero
if p1x == p2x:
tmp = (3 * p1x * p1x + a) * inverse(2 * p1y, p) % p
else:
tmp = (p2y - p1y) * inverse(p2x - p1x, p) % p
x = (tmp * tmp - p1x - p2x) % p
y = (tmp * (p1x - x) - p1y) % p
return (int(x), int(y))


def mul(n, p):
r = zero
tmp = p
while 0 < n:
if n & 1 == 1:
r = add(r, tmp)
n, tmp = n >> 1, add(tmp, tmp)
return r


def sign(msg, q, d, G):
z = int(sha256(msg).hexdigest(), 16)
while True:
k = randint(1, q - 1)
P = mul(k, G)
r = P[0]
s = inverse(k, q) * (z + r * d) % q
if r != 0 and s != 0:
return r, s, k.bit_length()


def verify(sig, msg, q, G, Q):
r, s = sig
print(r, s)
if r == 0 or s == 0:
return False
z = int(sha256(msg).hexdigest(), 16)
u1 = z * inverse(s, q) % q
u2 = r * inverse(s, q) % q
P = add(mul(u1, G), mul(u2, Q))
return P[0] == r


signal.alarm(240)
menu = \
'''
1.sign
2.verify
3.exit
'''

secret_key = bytes_to_long(urandom(27) + b"qwbs8")
Q = mul(secret_key, G)

count = 678
for i in range(count):
print(menu)
op = int(input(">").strip())
if op == 1:
msg = input("msg: ").strip().encode()
if msg == b"qwbs8_send_flag_to_me":
print("another msg plz.")
else:
r, s, kbits = sign(msg, order, secret_key, G)
print(r, s, kbits)
elif op == 2:
msg = input("msg: ").strip().encode()
ur = int(input("r: ").strip())
us = int(input("s: ").strip())
sig = (ur, us)
if verify(sig, msg, order, G, Q):
if msg == b"qwbs8_send_flag_to_me":
print(flag)
else:
print(True)
else:
print("malformed signature.")
else:
exit(0)

b1ock——unsolved

des.py

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import random
# DES S-boxes
S_BOXES = [
# S1
[
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
[0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
[4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
[15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]
],
# S2
[
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
[3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
[0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
[13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]
],
# S3
[
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
[13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
[13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
[1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]
],
# S4
[
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
[13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
[10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
[3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]
],
# S5
[
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
[14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
[4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
[11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]
],
# S6
[
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
[10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
[9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
[4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]
],
# S7
[
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
[13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
[1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
[6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]
],
# S8
[
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
[1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
[7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
[2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]
]
]

# Initial and Final Permutation tables
IP = [58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7]

FP = [40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25]

# PC-1 - Permuted choice 1
PC1 = [57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4]

# PC-2 - Permuted choice 2
PC2 = [14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32]

# Left shifts for each round
SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

E = [32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1]

P = [16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25]

def _initial_permutate(block):
# Apply initial permutation
return ''.join(block[i-1] for i in IP)

def _final_permutate(block):
# Apply final permutation
return ''.join(block[i-1] for i in FP)

def _exchange_blocks(block):
# Exchange left and right halves
return block[32:64] + block[:32]

def _pbox_permutate(block):
# P-box permutation
return block[0:64] + "".join(block[64:].zfill(32)[i-1] for i in P).zfill(32)

def _sbox_permutate(block):
# Apply S-boxes
l, r, x = block[0:32], block[32:64], block[64:].zfill(48)

output = ''
for i in range(8):
chunk = x[i*6:(i+1)*6]
row = int(chunk[0] + chunk[5], 2)
col = int(chunk[1:5], 2)
output += bin(S_BOXES[i][row][col])[2:].zfill(4)
return l + r + output

def _add_round_key(block, key):
# XOR block with key
l, r, er = block[0:32], block[32:64], block[64:].zfill(48)
return l + r + bin(int(er, 2) ^ key)[2:].zfill(48)

def _extend_block(block):
# Extend block to 48 bits
l, r = block[0:32], block[32:64]
return l + r + ''.join(r[i-1] for i in E)

def _xor_blocks(block):
# XOR left and right halves
l, r, rr = block[0:32], block[32:64], block[64:96].zfill(32)
return bin(int(l,2) ^ int(rr,2))[2:].zfill(32) + r

class DES:
def __init__(self, master_key, rounds=16, backdoor = None):
R = random.Random()

self.rounds = rounds
self._subkeys = self._expand_key(master_key)

operations = []
operations.extend([
(_initial_permutate, ),
])

for i in range(self.rounds):
operations.extend([
(_extend_block, ),
(_add_round_key, self._subkeys[i]),
(_sbox_permutate, ),
(_pbox_permutate, ),
(_xor_blocks, ),
(_exchange_blocks, ),
])


operations.extend([
(_exchange_blocks, ),
(_final_permutate, ),
])

# 😈 backdoored by CRAZYMAN!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if backdoor:
R.seed(backdoor)
operations = R.choices(operations, k = len(operations))

self.operations = operations


def _expand_key(self, master_key):
key_bin = bin(int.from_bytes(master_key, 'big'))[2:].zfill(64)

# Apply PC-1
key = ''.join([key_bin[i - 1] for i in PC1])

# Split into left and right halves
c = key[:28]
d = key[28:]

subkeys = []
# Generate subkeys
for i in range(16):
# Left shift
c = c[SHIFTS[i]:] + c[:SHIFTS[i]]
d = d[SHIFTS[i]:] + d[:SHIFTS[i]]

# Combine C and D
cd = c + d

# Apply PC-2
subkey = ''.join([cd[i - 1] for i in PC2])
subkeys.append(int(subkey,2))

for i in range(16, self.rounds):
subkeys.append(subkeys[i % 16])

return subkeys[:self.rounds]

def encrypt_block(self, plaintext):
assert len(plaintext) == 8

state = bin(int.from_bytes(plaintext, 'big'))[2:].zfill(64)

for operation, *args in self.operations:
state = operation(state, *args)

return int(state[:64],2).to_bytes(8, 'big')

server.py

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 os
import signal
from Crypto.Cipher import DES as pyDES
# from secret import flag
from des import DES

def _handle_timeout(signum, frame):
raise TimeoutError('function timeout')


def _test():
key = os.urandom(8)
des = pyDES.new(key, pyDES.MODE_ECB)
mydes = DES(key, rounds=16)
mydes_backdoored = DES(key, rounds=16, backdoor = 0xdeadbeef)
for _ in range(200):
plaintext = os.urandom(8)
ciphertext = des.encrypt(plaintext)
assert (mydes.encrypt_block(plaintext) == ciphertext)
assert (mydes_backdoored.encrypt_block(plaintext) != ciphertext)

def main():
seed = int(input('🌱 '))
key = os.urandom(8)

cipher = DES(key, rounds = 48, backdoor = seed)
m0 = os.urandom(8)
c0 = cipher.encrypt_block(m0)
print(f'🤐 {c0.hex()}')

for _ in "🥵 You Only Live Once!"[0]:
m = bytes.fromhex(input('💬 '))
if m == m0:
print(f'🏁 {flag}')
exit()
c = cipher.encrypt_block(m)
print(f'🤫 {c.hex()}')

if __name__ == '__main__':
_test()
main()

st——unsolved

这个,感觉就是个逆向,给了个data.bin和encrypt文件,要逆encrypt,然后data.bin寸的像是终端输入的信息,无果,连密码部分都没碰到。

RW

这部分更是一点都做不动。

压缩包密码

1
2
3
4
5
6
7
8
9
10
11
12
klmT0%Gamo+g : rw_FXPDF.zip
gHB^#&Kh1Yg! : rw_gnac.zip
8JjD9sfvHH05 : rw_ire.zip
bed^#O&eam3& : rw_ltp.zip
QqMmHGExAxO8 : rw_ohMyLicense.zip
rp3IU5GfU+^n : rw_OPDD.zip
u!Awy#6DDMz! : rw_UbuntuPrv.zip
sf7#Di_fweN^ : rw_WPNet.zip
1IH!TWruaw~o : rw_DataEase.zip
CcGllP6iXBCE : rw_realAC.zip
r2b#!DtM~f9a : rw_thinkshop++.zip
eut&U5RH&Q9j : Misc-CuteAnimals-附件.zip

FXPDF

题目信息:

题目名称:FXPDF
旗帜名称:FXP
题目描述:附件中给出了一个已经正确安装Foxit PDF Editor Pro的Win10虚拟机,该虚拟机与台上靶机内的虚拟机环境完全相同,仅有系统密码不同。请挖掘并利用Foxit PDF Editor Pro中的漏洞,构造一个恶意PDF文档,实现在靶机的Win10 虚拟机中打开该文档后,能自动弹出系统计算器。上台演示的时候注意关闭exp的调试信息。
附件信息: 附件中的虚拟机与台上靶机内的虚拟机环境完全相同,仅有系统密码不同。附件中虚拟机系统用户名为jack,密码为admin。
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)开启附件中提供的虚拟机环境(操作系统Win10,仅系统密码和附件中的虚拟机不同),该虚拟机中已经正确安装Foxit PDF Editor Pro。
展示目标:选手携带自己的攻击机上台,将可以完成漏洞利用的恶意PDF文档文件上传到自己的HTTP服务器。操作员将在靶机的Win10 虚拟机中,下载并打开选手HTTP服务器上的恶意PDF文档,该文档需要在打开后的规定的时间内,自动在Win10 虚拟机中弹出系统计算器。

DataEase

题目信息:

题目名称:DataEase
旗帜名称:D-E
题目描述:附件中给出了一个已经正确的DataEase环境,展台上仅开放81端口,请通过81端口进行攻击,并获取靶机权限,从而将https://ip:81/flag.html的页面修改为特定主页。账号密码均为dataease
附件信息:附件为台上相同(除密码不同)的环境,但开放端口不同,展台仅开放81端口。
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)开启附件中的虚拟机环境(操作系统ubuntu,密码和附件不同),该虚拟机中部署了DataEase系统。虚拟机中仅有81端口被转发映射至靶机的81端口,可以使选手直接访问。且展台靶机允许回连选手机器
展示目标:选手携带自己的攻击机上台,需要在规定的时间内攻击81端口的服务程序,实现获取靶机权限,从而将主页的https://ip:81 /flag.html的页面修改为特定页面。需要修改的特定页面详见附件中的flag.html,选手展示时需将其替换为“hacked by ”,为自己的队伍名。

gnac

题目信息:
题目名称:gnac
旗帜名称:GNC
题目描述:题目给出了一台Genian NAC虚拟机,请选手攻击该镜像,修改admin用户密码,实现登录https://192.168.143.19:8443/mc2/faces/adminlogin.xhtml。
附件信息:附件为台上相同的攻击环境,但是密码不同。
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)启动附件中的gnac环境。
展示目标:选手携带自己的攻击机上台,接入靶机所在网段,选手运行exp攻击服务器,若在规定时间内修改密码并登录
https://192.168.143.19:8443/mc2/faces/adminlogin.xhtml即为挑战成功。

ire

题目信息:
题目名称:ire
旗帜名称:IRE
题目描述:附件中给出了一台Ubunt64-bit 22.04.5 LTS虚拟机,环境和展示区相同但密码不同。虚拟机/home/game目录中有start.sh脚本,选手可以执行start.sh脚本启动题目环境(提供的虚拟机中已经通过服务启动)。请挖掘并利用相关程序的漏洞,实现任意命令执行,若靶机弹出计算器则挑战成功。
附件信息:虚拟机用户名和口令为game/game
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)启动附件中的Ubunt64-bit 22.04.5 LTS环境,并以game用户登录。
展示目标:选手携带自己的攻击机上台,接入靶机所在网段开始进行脆弱性检测,若在规定时间内靶机弹出计算器,即为挑战成功。

ltp

题目信息:
题目名称:ltp
旗帜名称:LTP
题目描述:附件中给出了一台Ubuntu22.04虚拟机,虚拟机上启动了目标lighttp服务,题目文件、配置信息等在附件中也有给出。请针对目标http服务进行漏洞利用,获取qwb用户权限(修改lighttpd主页)。
附件信息:虚拟机密码为qwb@123456。
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)启动附件中的Ubuntu22.04环境,并以qwb用户登录。
展示目标:选手携带自己的攻击机上台,接入靶机所在网段。操作员以qwb用户登录Ubuntu22.04,测试网络连通性后,选手执行exp,修改主页(位于/var/index.html)成功,即为挑战成功。

OML

题目信息:
题目名称:ohMyLicense
旗帜名称:OML
题目描述:附件中给出了一台Windows Server 2025虚拟机,请针对目标虚拟机挖掘出未授权远程代码执行漏洞并进行利用,获取C:\Users\Public\flag.txt中的内容。
附件信息:附件为台上相同的攻击环境,但是密码不同。附件中Administrator用户对应密码为gV9!mD1{hE。
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)启动附件中的Windows Server 2025环境。
展示目标:选手携带自己的攻击机上台,接入靶机所在网段,选手运行exp攻击Windows Server 2025服务器,若在规定时间内获取C:\Users\Public\flag.txt中的内容,即为挑战成功。

OPDD

题目信息:
题目名称:OPDD
旗帜名称:OPDD
题目描述:附件中给出了一个opnsense的虚拟机。初始化过程中set up VLANS 选no,WAN选em0即可。环境和展示区相同但密码不同。请分析并挖掘利用opnsense服务程序的漏洞,实现RCE,并替换修改虚拟机中的/usr/local/www/index.php文件,从而将opnsense的登录页面修改为特定主页。
附件信息:附件为台上相同的环境,但是密码不同。附件中虚拟机系统账密为root/opnsense
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)开启附件中的虚拟机环境(密码和附件不同),该虚拟机中部署了opnsense。
展示目标:选手携带自己的攻击机上台,需要在规定的时间内攻击web端口的服务程序,实现修改虚拟机中的/usr/local/www/index.php文件,从而将opnsense的登录界面修改为特定页面。需要修改的特定页面详见附件中的index.php,选手展示时需将其中的“XXXXX”替换为自己的队伍名。

rac

题目信息:
题目名称:realAC
旗帜名称:rAC
题目描述:附件中给出了一个虚拟机,环境和展示区相同但密码不同。请实现root权限的RCE,并将展示页面的https://ip:443/login.html修改为特定主页。
附件信息:附件为台上相同的攻击环境(仅密码不同)。
台上拓扑:交换机同时连接选手攻击机和代理虚拟机,代理虚拟机与靶机相连。代理虚拟机将靶机的80,85,443,9998端口,通过端口转发的方式可被选手访问。其中代理虚拟机仅提供端口转发功能。

展示目标:选手携带自己的攻击机上台,接入代理虚拟机所在网段,需要在规定的时间内攻击靶机,将https://ip:443/login.html修改为特定页面。需要修改的特定页面详见附件中的login.html,选手展示时需将其中的“XXXXX”替换为自己的队伍名。

ts++

题目信息:
题目名称:thinkshop++
旗帜名称:TS++
题目描述:附件中给出了一个docker虚拟机(名称为thinkshopplus),虚拟机上部署了一个服务系统,请分析服务存在的漏洞,针对Web服务进行攻击,获取操作系统控制权限。并替换修改虚拟机中的/var/www/html/public/hack.html文件,从而将http://ip:port/public/hack.html的页面修改为特定页面。
附件信息:附件为台上相同的攻击环境。Web服务内部暴露端口为80,部署方式:
docker load < thinkshopplus.tar
docker run -tid –name thinkshopplus -p 36000:80 thinkshopplus
台上拓扑:交换机同时连接选手攻击机和靶机。靶机运行docker虚拟机启动Web服务,并将内部80端口映射到指定端口。
展示目标:选手携带自己的攻击机上台,接入靶机所在网段,需要在规定的时间内攻击指定端口的Web服务,实现RCE,并替换修改虚拟机中的/var/www/html/public/hack.html文件,从而将http://ip:port/public/hack.html的页面修改为附件中所提供的hack.html,其中选手展示时需将其中的“XXX”替换为自己的队伍名。

Ubp

题目信息:
题目名称:UbuntuPrv
旗帜名称:UbP
题目描述:附件中以OVF格式给出了一个VMWare17.5以上兼容性的Ubuntu虚拟机,Ubuntu虚拟机的内核经过修改重新编译,请利用漏洞提升至root权限。导入后的宿主机有一调试用户。调试用户的用户名为qwbs8,密码为8f4cpkdjwN2YPPra;
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware(最新版)开启附件中的Ubuntu虚拟机环境,虚拟机在22号端口开启ssh访问,映射到宿主机的11122端口,可以在攻击机通过scp协议将exp传输到虚拟机进行攻击。展示机包含调试用户,但是密码不同,无法通过ssh访问。选手通过操作账号连接ssh。操作账号的用户名为qwbs8_user,密码为123456。
展示目标:选手携带自己的攻击机上台,可以通过ssh直接登录客户虚拟机,上传文件并执行操作。选手需要在规定的时间内攻击虚拟机,得到root权限,并能够读取/etc/shadow文件。

WPNet

题目信息:
题目名称:WPNet
旗帜名称:WPNet
题目描述:附件中给出了一台Windows 10 x64虚拟机,及一台Ubuntu虚拟机,两台虚拟机均使用桥接模式,网段配置为:192.168.3.0。Windows虚拟机中安装有WPS应用程序,选手可上传1个doc或docx文件,并使用WPS打开;在攻击环境中,Ubuntu与Windows虚拟机处于同一局域网内,并开启了Apache服务。请选手针对Windows虚拟机上的wps进行漏洞利用,实现RCE(弹出计算器)。
附件信息:附件为台上相同的攻击环境。附件中Windows用户未设置密码,Ubuntu中qwb用户密码为welcometoqwb。
台上拓扑:交换机同时连接选手攻击机和靶机。靶机中使用vmware启动附件中的Windows 10环境。
展示目标:选手携带自己的攻击机上台,接入靶机所在网段,并开启http服务(如python3 -m http.server)。操作员登录Windows,下载选手使用的exp.doc或exp.docx,并使用WPS打开exp文件(展示期间可多次执行),若在规定时间内靶机弹出calc计算器,即为挑战成功。

Thinking

4号下午到酒店之后,大概4点去场馆溜了一圈,大门如图

比赛的地方在4楼赛博厅

当时工作人员还在测试,我们就走进去看了一圈。两边是32支战队的战队logo,口号以及战队简介,一边各16支战队

5号上午比赛正式开始的时候,场地中间插满了旗子,真就夺旗赛,但这些特殊的旗都是RW相关的赛题,一血的队伍就可以把旗子拔走

接着讲讲我这一天干了啥,早上做了下Shopping game,手还算快,第八个做出来,然后去看st,逆了半天没触碰到一点密码的东西就放弃了,然后队友那边有个关于FXPDF的payload,让我试了试,题目要求的功能是实现了,但是很可惜,有多余的操作,所以上台挑战的时候没给过。到了下午2点放新题,我一看,好家伙上了3道密码,这让我怀疑这是强网杯还是强密杯。啃了两个多小时还好把bl0ck做了出来(也还算挺靠前的,好像也是第八个做了出来),到5点比赛结束最终排名定格在17名(Jeopardy部分),总榜不太确定,最后主办方连榜单都没放。说实话是远超预期,昨天和队友看了各大战队之后实在是想不到我们这双非战队能稳赢哪支队伍,一来我们4个实力就一般,二来比赛可联网,大家肯定都有后备力量,而我们….一言难尽,比啥都比不过😭

噢,最后我们快走的时候还被拉去拍了点照片和视频当作宣传

接下来是6号的事情。

早上刚开赛,和我预期的差不多,大部分队伍都把前一天的题目做完了,而我们依旧没有进展,自然而然掉到了末尾。10点上了最后两题密码(Signin和f2ke),可惜没做出来。

花了挺长时间把fak1做了出来,然后就摆烂等颁奖了。心有余而力不足,只能做出一些大家都会做的题,最后定榜29名😭

晚上吃完饭后和星盟的师傅去ktv玩了一会儿,还挺开心的😀。

最后感谢一下主办方吧,谁叫强网杯给的这么多呢。定制队伍的赛牌(还区分了队长和队员),有车费报销,有酒店,给了背包和鼠标垫周边,还保底3等奖(喜提1w)。但线下赛有点像不需要交wp的ISCC(这是能说的吗?)

事已至此,我的CTF生涯就暂且告一段落了!!!!!!

-------------已经到底啦!-------------