“轩辕杯”云盾砺剑CTF挑战赛 2025 WriteUp

4k 词

有很多脑洞题,想了很久最后还是牢出来了.

在这里仅给出一部分我个人解出的题目的WriteUp.

Crypto:

DIladila:

爆破明文:

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
#include <iostream>
#include <vector>
#include <cstdint>
#include <thread>
#include <mutex>
#include <atomic>
#include <array>
#include <unordered_map>
#include <iomanip>

uint16_t rol(uint16_t val, int r_bits) {
const int max_bits = 16;
return ((val << r_bits) & 0xFFFF) | (val >> (max_bits - r_bits));
}

uint16_t ror(uint16_t val, int r_bits) {
const int max_bits = 16;
return (val >> r_bits) | ((val << (max_bits - r_bits)) & 0xFFFF);
}

void speck_round(uint16_t& x, uint16_t& y, uint16_t k) {
x = (ror(x, 7) + y) & 0xFFFF;
x ^= k;
y = rol(y, 2) ^ x;
}

void encrypt_block(uint16_t& x, uint16_t& y, const std::vector<uint16_t>& keys) {
for (auto k : keys) {
speck_round(x, y, k);
}
}

std::pair<uint16_t, uint16_t> bytes_to_block(const std::array<uint8_t, 4>& bytes) {
uint16_t x = bytes[0] | (bytes[1] << 8);
uint16_t y = bytes[2] | (bytes[3] << 8);
return {x, y};
}

std::vector<std::pair<uint16_t, uint16_t>> target_results = {
{57912, 19067},
{38342, 34089},
{16842, 41652},
{30292, 50979},
{9137, 57458},
{29822, 64285},
{33379, 14140},
{16514, 4653}
};

struct FoundResult {
uint32_t plaintext;
uint16_t cipher_x;
uint16_t cipher_y;
};

std::vector<FoundResult> found_list;
std::mutex print_mutex;
std::atomic<bool> all_found(false);

void brute_force(uint32_t start, uint32_t end, const std::vector<uint16_t>& keys) {
if (all_found.load()) return;

for (uint32_t i = start; i <= end; ++i) {
if (all_found.load()) break;

if (i == 0) continue;

std::array<uint8_t, 4> bytes = {
static_cast<uint8_t>(i & 0xFF),
static_cast<uint8_t>((i >> 8) & 0xFF),
static_cast<uint8_t>((i >> 16) & 0xFF),
static_cast<uint8_t>((i >> 24) & 0xFF)
};

auto block = bytes_to_block(bytes);
uint16_t x = block.first, y = block.second;
encrypt_block(x, y, keys);

for (size_t j = 0; j < target_results.size(); ++j) {
if (target_results[j].first == x && target_results[j].second == y) {
std::lock_guard<std::mutex> lock(print_mutex);
printf("Found match: Plaintext=%08X -> Cipher=(%04X, %04X)\n",
i, x, y);

found_list.push_back({i, x, y});
target_results.erase(target_results.begin() + j);

if (target_results.empty()) {
all_found.store(true);
}
break;
}
}
}
}

int main() {
std::vector<uint16_t> keys = {0x1234, 0x5678, 0x9abc, 0xdef0};
const uint32_t total = 0xFFFFFFFF;
const uint32_t threads = 32;
std::vector<std::thread> thread_pool;

for (uint32_t i = 0; i < threads; ++i) {
uint32_t start = i * (total / threads) + (i == 0 ? 0 : 1);
uint32_t end = (i + 1) * (total / threads) - 1;
thread_pool.emplace_back(brute_force, start, end, std::cref(keys));
}

for (auto& t : thread_pool) {
if (t.joinable()) t.join();
}

std::cout << "\nAll targets found! Summary:\n";
for (const auto& res : found_list) {
std::cout << "Plaintext: 0x" << std::hex << std::setw(8) << std::setfill('0') << res.plaintext
<< " -> Cipher: (" << std::hex << std::setw(4) << std::setfill('0') << res.cipher_x
<< ", " << std::setw(4) << std::setfill('0') << res.cipher_y << ")\n";
}

return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Found match: Plaintext=72435F61 -> Cipher=(23B1, E072)
Found match: Plaintext=6C49445F -> Cipher=(41CA, A2B4)
Found match: Plaintext=74614D5F -> Cipher=(8263, 373C)
Found match: Plaintext=6C696461 -> Cipher=(7654, C723)
Found match: Plaintext=756F597B -> Cipher=(95C6, 8529)
Found match: Plaintext=7D726573 -> Cipher=(4082, 122D)
Found match: Plaintext=6F747079 -> Cipher=(747E, FB1D)
Found match: Plaintext=67616C66 -> Cipher=(E238, 4A7B)

All targets found! Summary:
Plaintext: 0x72435f61 -> Cipher: (23b1, e072)
Plaintext: 0x6c49445f -> Cipher: (41ca, a2b4)
Plaintext: 0x74614d5f -> Cipher: (8263, 373c)
Plaintext: 0x6c696461 -> Cipher: (7654, c723)
Plaintext: 0x756f597b -> Cipher: (95c6, 8529)
Plaintext: 0x7d726573 -> Cipher: (4082, 122d)
Plaintext: 0x6f747079 -> Cipher: (747e, fb1d)
Plaintext: 0x67616c66 -> Cipher: (e238, 4a7b)

转换:

1
2
3
4
5
6
7
8
9
10
m = [0x67616c66, 0x756f597b, 0x6c49445f, 0x6c696461,
0x72435f61, 0x6f747079, 0x74614d5f, 0x7d726573]

def d2ble(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list

print(bytearray(d2ble(m)).decode())

flag{You_DIladila_Crypto_Matser}

古典密码:

线索:

qwertyuiopasdfghjklzxcvbnm一定是单表替换

字符串nxtcctf一定是维吉尼亚密码

数字5、8有可能是仿射密码(ctfwiki中给出的例子就是5、8)

数字4、5是什么?


第一步:变位密码的爆破

单表替换、维吉尼亚、仿射是不可能改变字符顺序的,因此密文中的两个{}一定是密码棒密码或者其他移位密码加密的结果

有一种可能,题目中未提及最经典的栅栏密码,加密过程中或许还有栅栏密码的参与,以及凯撒密码,这两者里面大概率有一个参与了加密。穷举单次密码棒密码加解密的结果,发现没有,编写脚本爆破:

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
def se(plaintext, num_rows):
plaintext = plaintext.replace(" ", "")
length = len(plaintext)
num_cols = (length + num_rows - 1) // num_rows
padding_char = '#'
plaintext += padding_char * (num_rows * num_cols - length)
ciphertext = ""
for col in range(num_cols):
for row in range(num_rows):
index = row * num_cols + col
if index < len(plaintext):
ciphertext += plaintext[index]
return ciphertext

def fe(plaintext, rails):
fence = [''] * rails
row, step = 0, -1
for char in plaintext:
fence[row] += char
if row == 0 or row == rails - 1:
step = -step
row += step
ciphertext = ''.join(fence)
return ciphertext

def sd(ciphertext, num_rows):
length = len(ciphertext)
num_cols = length // num_rows
plaintext = ""
for row in range(num_rows):
for col in range(num_cols):
index = row + col * num_rows
plaintext += ciphertext[index]
plaintext = plaintext.rstrip('#')
return plaintext

def fd(ciphertext, rails):
fence_length = [0] * rails
current_rail, direction = 0, False
for _ in range(len(ciphertext)):
fence_length[current_rail] += 1
if current_rail in [0, rails - 1]:
direction = not direction
current_rail += 1 if direction else -1
index = 0
rail_content = []
for i in range(rails):
rail_content.append(ciphertext[index:index + fence_length[i]])
index += fence_length[i]
plaintext = ''
current_rail, direction = 0, False
for _ in range(len(ciphertext)):
plaintext += rail_content[current_rail][0]
rail_content[current_rail] = rail_content[current_rail][1:]
if current_rail in [0, rails - 1]:
direction = not direction
current_rail += 1 if direction else -1
return plaintext

def unpad(string):
return string.replace('#', '')

for t in [19, 23]:
m = "flag{" + "_" * t + "}"
#单种加密
for i in range(1,16):
c = se(m, i)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i}, se")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i}, se, del")

for j in range(2,16):
c = fe(m, j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j}, fe")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j}, fe, del")
#单种解密
for i in range(1,16):
c = sd(m, i)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i}, sd")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i}, sd, del")

for j in range(2,16):
c = fd(m, j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j}, fd")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j}, fd, del")
#双重加密
for i in range(1,16):
for j in range(2,16):
c = fe(se(m, i),j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sefe")
if t == 19 and q[5:6] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sefe, del")

for i in range(1,16):
for j in range(2,16):
c = se(fe(m, j),i)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fese")
if t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fese, del")
#双重解密
for i in range(1,16):
for j in range(2,16):
c = fd(sd(m, i),j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdfd")
if t == 19 and q[5:6] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdfd, del")

for i in range(1,16):
for j in range(2,16):
c = sd(fd(m, j),i)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fdsd")
if t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fdsd, del")
#加密解密
for i in range(1,16):
for j in range(2,16):
c = fd(se(m, i),j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sefd")
if t == 19 and q[5:6] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sefd, del")

for i in range(1,16):
for j in range(2,16):
c = sd(fe(m, j),i)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fesd")
if t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fesd, del")
#解密加密
for i in range(1,16):
for j in range(2,16):
c = fe(sd(m, i),j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdfe")
if t == 19 and q[5:6] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdfe, del")

for i in range(1,16):
for j in range(2,16):
c = se(fd(m, j),i)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fdse")
if t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {j,i}, fdse, del")
#双棒加密
for i in range(1,16):
for j in range(1,16):
c = se(se(m, i), j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sese")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sese, del")
#双棒解密
for i in range(1,16):
for j in range(1,16):
c = sd(sd(m, i), j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdsd")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdsd, del")
#加密解密
for i in range(1,16):
for j in range(1,16):
c = sd(se(m, i), j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sesd")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sesd, del")
#解密加密
for i in range(1,16):
for j in range(1,16):
c = se(sd(m, i), j)
q = unpad(c)
if t == 23 and q[6:8] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdse")
elif t == 19 and q[5:7] == "{}":
print(f"{c}, lenm = {len(m)}, lenc = {len(c)}, arg = {i,j}, sdse, del")

认为空格属于密码棒密码的填充,发现有一个解刚好对上:

f___#_{}__g__l___#__#__#__a__#, lenm = 25, lenc = 30, arg = (7, 6), sese, del

用这组参数进行解密:

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
def se(plaintext, num_rows):
plaintext = plaintext.replace(" ", "")
length = len(plaintext)
num_cols = (length + num_rows - 1) // num_rows
padding_char = '#'
plaintext += padding_char * (num_rows * num_cols - length)
ciphertext = ""
for col in range(num_cols):
for row in range(num_rows):
index = row * num_cols + col
if index < len(plaintext):
ciphertext += plaintext[index]
return ciphertext

def sd(ciphertext, num_rows):
length = len(ciphertext)
num_cols = length // num_rows
plaintext = ""
for row in range(num_rows):
for col in range(num_cols):
index = row + col * num_rows
plaintext += ciphertext[index]
plaintext = plaintext.rstrip('#')
return plaintext

m = "flag{" + "_" * 19 + "}"
c = se(se(m,7),6)
print(c)
c = "f___#_{}__g__l___#__#__#__a__t"
d = sd(sd(c,6),7)
print(d)

enc = "ntid#c{}rShcljrko#od#lc#WYicO!"
dec = sd(sd(enc,6),7)
print(dec)

得到njih{ddolYScoikOWrlctrcc},看起来是对的

第二步:改变字符密码的爆破

目前已经有两层加密被用掉了,接下来剩下一个单表替换、一个维吉尼亚、一个仿射密码,和一个未知的古典密码(猜测是凯撒),这4种各1个

变位密码的位置会影响改变字符密码的结果,因此需要考虑更多的可能,需要考虑6!*26种情况(凯撒密码位移不确定)

经过大量尝试+爆破发现顺序是两个密码棒密码→密钥为nxtcctf的维吉尼亚密码→参数5,8的仿射密码→qwertyuiopasdfghjklzxcvbnm的单表替换→偏移量为23的凯撒密码,即可得到flag:flag{nxtcNBflagNBctfflag}

告白2009-01-23:

wiener攻击:

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
import gmpy2
import libnum
from Crypto.Util.number import long_to_bytes
def transform(x,y):
res=[]
while y:
res.append(x//y)
x,y=y,x%y
return res

def continued_fraction(sub_res):
numerator,denominator=1,0
for i in sub_res[::-1]:
denominator,numerator=numerator,i*numerator+denominator
return denominator,numerator

def sub_fraction(x,y):
res=transform(x,y)
res=list(map(continued_fraction,(res[0:i] for i in range(1,len(res)))))
return res

def get_pq(a,b,c):
par=gmpy2.isqrt(b*b-4*a*c)
x1,x2=(-b+par)//(2*a),(-b-par)//(2*a)
return x1,x2

def wienerAttack(e,n):
for (d,k) in sub_fraction(e,n):
if k==0:
continue
if (e*d-1)%k!=0:
continue

phi=(e*d-1)//k
px,qy=get_pq(1,n-phi+1,n)
if px*qy==n:
p,q=abs(int(px)),abs(int(qy))
d=gmpy2.invert(e,(p-1)*(q-1))
return d
print("该方法不适用")

n = 106907120255411141276638612258492580223206670508697860345280705552076099016030935898699700187523599766269485047282325650117035914628760419926410817774570995043643433455055595591107437470658308764074450729921003648782408533657438504280874574703167028727399770901329675528708585142713643443248769642817712218371
e = 92066298664485065396027178362270794902621018857568310802765263839921592653297188141639082907410773099588833460614099675385786190965706296547920850855064908555902716021514756109564555466796584126969045436871844375174789134742417250605776973188216013735765092101366990049447374275811804264794446656219369440535
c = 72413193823586193683552385578931939035012872670413497855056244201691512354415666469936125548748032982020958495114951719066245650644060153838816623502095911253320142088319318206119073607336497914311058118174988818658610257295726356030260769061712429926392969618604615189351858925626182197332313954336604548074
d = wienerAttack(e, n)
print("d=", d)
m=pow(c,d,n)
print(m)
print(hex(m))
print(long_to_bytes(m))

得到m = 634374818174323263916343,手机九键得到oisttseeowoi,键盘替换得到ihleelccibih,l改成d得到ihleedccibih,踢掉的两个变成空格得到ihleedcc ib ih,栅栏2栏得到ich liebe dich

德语“我爱你”:ich liebe dich

flag{ich liebe dich}

Reverse:

ezBase:

UPX被改成小写了,改回大写后脱壳
base64是魔改的,表是AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789+/,末尾还异或了4,密文是iP}ui7siC`otMgA~h5o]Tg<4jPmtIvM5C~I4h644K7M~KVg=,求得flag:flag{Y0u_@R3_Upx_4nd_b45364_m4st3r!}

hookme:

mainactivity有一段这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicstaticfinalvoid onCreate$lambda$0(EditText $inputField, MainActivity this$0, TextView $resultTextView, View v) {
String userInput = $inputField.getText().toString();
if (!(userInput.length() == 0)) {
byte[] encryptedData = this$0.rc4Encrypt(userInput);
String correctCiphertextHex = this$0.getString(C0497R.string.correct_ciphertext);
Intrinsics.checkNotNullExpressionValue(correctCiphertextHex, "getString(...)");
byte[] correctCiphertext = this$0.hexStringToByteArray(correctCiphertextHex);
if (Arrays.equals(encryptedData, correctCiphertext)) {
$resultTextView.setText("success");
return;
}else {
$resultTextView.setText("wrong");
return;
}
}
$resultTextView.setText("请输入flag!");
}

密文是correct_ciphertext,查找得到:
<string name="correct_ciphertext">f235b888b3f4e08bff17e7e29bc3bf67d0f9a1b7b6581bb4a1eb299684e99923a8d193caf91d</string>
因为rc4是流加密,所以我们并不需要去hook native层,仅需要hook encryptedData即可,hook脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Java.perform(function () {
var MainActivity = Java.use("com.example.hookme.MainActivity");
MainActivity.rc4Encrypt.overload('java.lang.String').implementation = function (input) {
console.log("[*] rc4Encrypt 被调用,输入: " + input);
var result = this.rc4Encrypt(input);
var hex = '';
for (var i = 0; i < result.length; i++) {
var unsignedByte = result[i] & 0xFF;
var str = unsignedByte.toString(16);
if (str.length === 1) {
str = '0' + str;
}
hex += str;
}
console.log("[+] rc4Encrypt 返回值 (hex): " + hex);
return result;
};
});

frida -UF com.example.hookme -l hook.js,输入很多个1后得到密文:

1
2
[*] rc4Encrypt 被调用,输入: 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
[+] rc4Encrypt 返回值 (hex): a568e8def9a0b483a844e6e598c4bc6282f9f5b3b55e4ce4f2e92ec386b99a25add8969ff951e6dc62000a07d3fe3bf4ad56501e51f2a618baed293b822e82514aedf98bd6e60bb3e712a5bb44562aee8c5556a1b06a209f0755

将密文异或0x31即可得到密钥流:
9459d9efc89185b29975d7d4a9f58d53b3c8c482846f7dd5c3d81ff2b788ab149ce9a7aec860d7ed53313b36e2cf0ac59c67612f60c397298bdc180ab31fb3607bdcc8bae7d73a82d623948a75671bdfbd646790815b11ae3664

密文异或密钥流得到flag:flag{ee9fb062624c1e527fab36d3a27484d1}

Matlab_SMC?:

程序最后的部分有一个ZIP文件,提取里面有一个encrypt_flag.m,但是已经被编译了,没法逆,运行程序发现加密是单字节的,而且与位置无关,与x或者y也无关,考虑找关系:

m c delta
0 1 0
1 8 7
2 25 17
3 52 27
4 89 37
5 136 47
6 193 57
7 260 67
8 337 77
9 424 87
10 521 97
11 628 107

可以发现对于整数是差值7、17、27、37、47….的数列,通项公式为5n²+2n+1,带入负数和小数试试,发现全部吻合,编写python脚本求原值:

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 openpyxl
import math

def reverse_function(y):
a = 5
b = 2
c = 1 - y
discriminant = b**2 - 4 * a * c
if discriminant < 0:
return None
sqrt_d = math.sqrt(discriminant)
x1 = (-b + sqrt_d) / (2 * a)
x2 = (-b - sqrt_d) / (2 * a)
return x1 if x1 > 0 else x2 if x2 > 0 else 0

input_file = "encflag.xlsx"
wb = openpyxl.load_workbook(input_file)
ws = wb.active
ws.cell(row=1, column=4, value="原定价")
ws.cell(row=1, column=5, value="原日补货量")
for row in ws.iter_rows(min_row=2, max_col=3):
name_cell = row[0]
price_y = row[1].value
supply_y = row[2].value
original_price = reverse_function(price_y)
original_supply = reverse_function(supply_y)
ws.cell(row=name_cell.row, column=4, value=original_price)
ws.cell(row=name_cell.row, column=5, value=original_supply)
output_file = "decflag.xlsx"
wb.save(output_file)
print(f"已完成计算,结果已保存至 '{output_file}'")

据此可以还原原来的表格:

单品名称 定价 日补货量 原定价 原日补货量
菠菜(份) 24.890125 214.0045 1.995 6.33
高瓜(2) 1084.192 6.9605 14.52 0.91
海鲜菇(包) 89.210125 454.9045 4.005 9.33
红椒(2) 876.626125 26.338 13.035 2.06
红薯尖 157.6 101.152 5.4 4.28
洪湖藕带 9833.195125 88.162 44.145 3.98
姜蒜小米椒组合装(小份) 66.866125 236.7845 3.435 6.67
金针菇(盒) 83.6245 1423.7845 3.87 16.67
净藕(1) 939.25 190.528 13.5 5.96
菱角 778.3045 14.7445 12.27 1.47
螺丝椒 589.4125 229.9645 10.65 6.57
螺丝椒(份) 72.431125 685.25 3.585 11.5
木耳菜 212.05 196.738 6.3 6.06
奶白菜 261.442 192.3805 7.02 5.99
七彩椒(2) 968.2405 10.882 13.71 1.22
上海青 578.075125 63.1045 10.545 3.33
双孢菇(盒) 157.6 538.4845 5.4 10.17
娃娃菜 205.280125 647.1845 6.195 11.17
芜湖青椒(1) 516.928 1003.328 9.96 13.96
西兰花 987.110125 724.4045 13.845 11.83
西峡花菇(1) 2248 102.05 21 4.3
苋菜 182.002 451.1005 5.82 9.29
小米椒(份) 246.5005 2284.1845 6.81 21.17
小青菜(1) 299.5645 132.3845 7.53 4.93
小皱皮(份) 65.2405 685.25 3.39 11.5
蟹味菇与白玉菇双拼(盒) 591.041125 8 10.665 1
云南生菜 529.192 23.9125 10.08 1.95
云南生菜(份) 71.8645 5079.2845 3.57 31.67
云南油麦菜(份) 87.328 2178.5845 3.96 20.67
长线茄 1230.112 67.4125 15.48 3.45
枝江青梗散花 257.128 33.058 6.96 2.34
竹叶菜 125.051125 921.5245 4.785 13.37
紫茄子(2) 504.808 555.2045 9.84 10.33
平均值 - - 9.493182 8.346364

最后一行求得平均值9.493,8.346,计算md5,flag{7ce3c18d1e03070c79cf5074a195a240}

你知道Base么:

main:

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
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
v3 = &v7;
for ( n202 = 202; n202; --n202 )
{
*(_DWORD *)v3 = 0xCCCCCCCC;
v3 += 4;
}
j___CheckForDebuggerJustMyCode(&unk_1400240A3, argv, envp);
memset(Str, 0, 0x40u);
printf("Hello CTFer!\\n");
v9[0] = 0xA92F3865;
v9[1] = 0x9E60E953;
v10[0] = 0x12345678;
v10[1] = 0x3456789A;
v10[2] = 0x89ABCDEF;
v10[3] = 0x12345678;
memset(v11, 0, 8);
printf(aGiveMeYourKey);
scanf("%8s", v11);
memset(key, 0, 8);
for ( i = 0; i < 8; ++i )
key[i] = v11[i];
v14 = v11;
TEA((__int64)v11, (__int64)v10);
for ( n2 = 0; n2 < 2; ++n2 )
{
if ( *(_DWORD *)&v14[4 * n2] != v9[n2] )
{
printf("you failed!!!\\n");
exit(1);
}
}
printf("You have passed the first level!!!\\n");
LODWORD(keylen) = 8;
printf("Where is my Base_Table???\\n");
printf("Plz input your found Table:\\n");
scanf("%64s", Str);
memset(buf, 0, 0x40u);
for ( n64 = 0; n64 < 64; ++n64 )
buf[n64] = Str[n64];
LODWORD(len) = j_strlen(Str);
RC4((__int64)Str, (unsigned int)len, (__int64)key, keylen);
v20 = 0x2CE3BFB4762359D4LL;
n88 = 0x58;
……
v73 = 0x80;
qmemcpy(v74, "R46", sizeof(v74));
compare((__int64)Str, (__int64)&v20);
memset(buf_, 0, 0x1Eu);
printf("Plz input Your flag: ");
scanf("%29s", buf_);
v5 = j_strlen(buf_);
v76 = base64(buf_, v5, buf);
qmemcpy(v77, "0tCPwtnncFZyYUlSK/4Cw0/echcG2lteBWnG2Ulw0htCYTMW", 48);
compare2(v76, v77);
return 0;
}

题目分3个阶段,首先要通过TEA得到RC4的密钥,然后通过RC4得到base64表,最后解码base64得到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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def TEA_DEC(dwordenc, dwordkey):
delta = 0x9E3779B9
rounds = 32
result = []
for j in range(0, len(dwordenc) // 2):
m1 = dwordenc[2 * j]
m2 = dwordenc[2 * j + 1]
ttl = (delta * rounds) & 0xFFFFFFFF
for _ in range(rounds):
m2 = (m2 - (((m1 << 4) + dwordkey[2]) ^ (m1 + ttl) ^ ((m1 >> 5) + dwordkey[3]))) & 0xFFFFFFFF
m1 = (m1 - (((m2 << 4) + dwordkey[0]) ^ (m2 + ttl) ^ ((m2 >> 5) + dwordkey[1]))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
result.append(m1)
result.append(m2)
return result

def b2dle(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False) for i in range(0, len(byte_array), 4)]

def d2ble(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list

def RC4(data, key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + ord(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
result = []
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
result.append((char - k) & 0xFF)

return bytes(result)

def b64dec(encoded_data: bytes, charset: bytes) -> bytes:
char_map = {c: i for i, c in enumerate(charset)}
decoded = bytearray()
for i in range(0, len(encoded_data), 8):
chunk = encoded_data[i:i+8]
num = 0
for j, c in enumerate(chunk):
bits = char_map[c] - 1
num |= bits << (35 - 5 * j)
decoded.extend([
(num >> 32) & 0xFF,
(num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
num & 0xFF
])
return bytes(decoded)

key = [0x12345678, 0x3456789A, 0x89ABCDEF, 0x12345678]
enc = [0xA92F3865, 0x9E60E953]

dec = TEA_DEC(enc, key)
rc4key = bytearray(d2ble(dec)).decode()
print(rc4key)

encchart = bytearray.fromhex("D4592376B4BFE32C588F5619DAF0C0BD363D7B461BB8171FE3D00345CD04EDC967E6AB29A7BC0BDE5C3071D7D55AC69F4065C471A9C3AED9B5E5128C80523436")
chart = RC4(encchart, rc4key).decode()
print(chart)

encflag = "0tCPwtnncFZyYUlSK/4Cw0/echcG2lteBWnG2Ulw0htCYTMW"
flag = b64dec(encflag, chart).decode()
print(flag)

flag{y0u__rea11y__k1ow__Base!}