2024 强网杯 - Reverse
carbofish

2024 强网杯 Reverse 方向 Writes UP

强网的题目还是不错的,不过可惜的是 mips 这题有点手慢了,吃饭前还只有1解,吃完饭就3解了,只能拿个四血了 :(
下次再接再厉~

boxx

游戏题

1
flag是每个关卡中每个箱子移动的最短的次数拼接的md5码值和几个字符,1.flag{四个字符_md5值},2.注意同一张图箱子不一定只有一个哦3.同一关需要计算所有箱子的总的最小移动次数,将每一关的最短次数拼接  解释:例如第一关是3第二关是5,就是md5(35...)

image-20241103200519596

这是地图数组,14 张 20*20 的地图,

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
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #

# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #

# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #

# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # #

主程序中只用到了前9张地图,后四张地图打印出来就是上面说的四个字母 qwb!
然后写一个脚本来解每张地图

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
#include "bits/stdc++.h"
#include "map2.h"
using namespace std;

const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};

struct State {
int playerX, playerY;
vector<pair<int, int>> boxes;

bool operator<(const State &other) const {
if (playerX != other.playerX) return playerX < other.playerX;
if (playerY != other.playerY) return playerY < other.playerY;
return boxes < other.boxes;
}
};

class SokobanSolver {
private:
vector<vector<int>> map;
int n, m;
vector<pair<int, int>> targets;
State initialState;

public:
SokobanSolver(vector<vector<int>> &gameMap) {
map = gameMap;
n = map.size();
m = map[0].size();
findInitialState();
}

void findInitialState() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (map[i][j] == 2) {
initialState.playerX = i;
initialState.playerY = j;
map[i][j] = 0;
} else if (map[i][j] == 3) {
initialState.boxes.push_back({i, j});
map[i][j] = 0;
} else if (map[i][j] == 4) {
targets.push_back({i, j});
}
}
}
}

bool isValid(int x, int y) {
return x >= 0 && x < n && y >= 0 && y < m && map[x][y] != 1;
}

bool hasBox(const vector<pair<int, int>> &boxes, int x, int y) {
return find(boxes.begin(), boxes.end(), make_pair(x, y)) != boxes.end();
}

vector<int> calculateMinPushes() {
vector<int> result;
std::map<State, int> visited;
queue<State> q;


q.push(initialState);
visited[initialState] = 0;

while (!q.empty()) {
State current = q.front();
q.pop();
int steps = visited[current];

bool allBoxesOnTarget = true;
for (const auto &box: current.boxes) {
bool onTarget = false;
for (const auto &target: targets) {
if (box == target) {
onTarget = true;
break;
}
}
if (!onTarget) {
allBoxesOnTarget = false;
break;
}
}

if (allBoxesOnTarget) {
return {steps};
}

for (int dir = 0; dir < 4; dir++) {
int newPlayerX = current.playerX + dx[dir];
int newPlayerY = current.playerY + dy[dir];

if (!isValid(newPlayerX, newPlayerY)) continue;

vector<pair<int, int>> newBoxes = current.boxes;
bool pushed = false;

for (auto &box: newBoxes) {
if (box.first == newPlayerX && box.second == newPlayerY) {

int newBoxX = box.first + dx[dir];
int newBoxY = box.second + dy[dir];

if (!isValid(newBoxX, newBoxY)) continue;
if (hasBox(newBoxes, newBoxX, newBoxY)) continue;

box = {newBoxX, newBoxY};
pushed = true;
break;
}
}

State newState = {newPlayerX, newPlayerY, newBoxes};

if (visited.find(newState) == visited.end()) {
visited[newState] = steps + (pushed ? 1 : 0);
q.push(newState);
}
}
}

return {-1};
}
};

void convert_1d_to_3d(int map_22_1d[], int map_22_3d[14][20][20]) {
for (int i = 0; i < 14; i++) {
for (int j = 0; j < 20; j++) {
for (int k = 0; k < 20; k++) {
int index = i * (20 * 20) + j * 20 + k;
if (index < 5600) {
map_22_3d[i][j][k] = map_22_1d[index];
}
}
}
}
}

int mapX[14][20][20];

int main() {
convert_1d_to_3d(map_22, mapX);
vector<vector<int>> mapX2 = vector<vector<int>>(20, vector<int>(20));

for (int k = 0; k <= 8; ++k) {
for (int i = 0; i < 20; ++i) {
for (int j = 0; j < 20; ++j) {
int code = mapX[k][i][j];
mapX2[i][j] = code;
}
}
SokobanSolver solver(mapX2);
vector<int> result = solver.calculateMinPushes();

cout << "case " << k << " ";
if (result[0] == -1) {
cout << "no solution" << endl;
} else {
cout << "at least " << result[0] << " times" << endl;
}
}
return 0;
}

/*
case 0 at least 2 times
case 1 at least 12 times
case 2 at least 13 times
case 3 at least 9 times
case 4 at least 21 times
case 5 at least 13 times
case 6 at least 25 times
case 7 at least 31 times
case 8 at least 3 times
*/

// 212139211325313
// qwb!_fec2d316d20dbacbe0cdff8fb6ff07b9

斯内克

又是一个游戏题,通过 Game over! 定位到最终的比对函数哪里

image-20241103201303955

发现有个 md5 散列和比较,然后还有调用,猜测是个 smc
对着 LpAddress 交叉引用发现有一个函数根据按下的按键修改 lpAdress 下的内容
得到 smc 解密的目标散列值 9c06c08f882d7981e91d663364ce5e2e

image-20241103201341052

在这里找到了随机数种子 0xDEADBEEF,同时也确定了这就是个根据你的输入内容解密的smc

image-20241103201649369

题目提示 需要选手的操作序列最短,也就是蛇转方向要尽可能的少,搓一个脚本利用 启发式和A*算法 算出蛇的最优转头路线,然后爆破果子的个数,发现枚举到第10个果子的时候 lpAdress 内容的散列值刚好是预期的

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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#include "bits/stdc++.h"
#include "md5.h"
#include "defs.h"
using namespace std;

enum Direction {
UP, // W
DOWN, // S
LEFT, // A
RIGHT // D
};

struct State {
int x, y; // 蛇头位置
Direction dir; // 当前方向
string path; // 路径
int fruitIndex; // 当前要吃的果子索引
int steps; // 步数

State(int x, int y, Direction d, string p, int f, int s)
: x(x), y(y), dir(d), path(p), fruitIndex(f), steps(s) {}

bool operator<(const State& other) const {
if (x != other.x) return x < other.x;
if (y != other.y) return y < other.y;
if (dir != other.dir) return dir < other.dir;
return fruitIndex < other.fruitIndex;
}
};

class SnakePathFinder {
private:
const int SIZE = 20;
vector<pair<int, int>> fruits;

bool isValid(int x, int y) {
return x >= 0 && x < SIZE && y >= 0 && y < SIZE;
}

Direction getOppositeDirection(Direction dir) {
switch(dir) {
case UP: return DOWN;
case DOWN: return UP;
case LEFT: return RIGHT;
case RIGHT: return LEFT;
}
return UP;
}

char getDirectionChar(Direction dir) {
switch(dir) {
case UP: return 'W';
case DOWN: return 'S';
case LEFT: return 'A';
case RIGHT: return 'D';
}
return 'W';
}

pair<int, int> moveInDirection(int x, int y, Direction dir) {
switch(dir) {
case UP: return {x-1, y};
case DOWN: return {x+1, y};
case LEFT: return {x, y-1};
case RIGHT: return {x, y+1};
}
return {x, y};
}

// 优化后的启发式估计函数
int estimateRemainingSteps(const State& state) {
if (state.fruitIndex >= fruits.size()) return 0;

int totalEstimate = 0;
int currentX = state.x;
int currentY = state.y;

// 计算到所有剩余果子的最小距离之和
for (int i = state.fruitIndex; i < fruits.size(); i++) {
int dx = abs(currentX - fruits[i].first);
int dy = abs(currentY - fruits[i].second);

// 考虑转向的代价
if (i == state.fruitIndex) {
bool needHorizontalMove = (currentY != fruits[i].second);
bool needVerticalMove = (currentX != fruits[i].first);

if (needHorizontalMove && needVerticalMove) {
// 如果需要同时在水平和垂直方向移动,至少需要一次转向
if ((state.dir == LEFT || state.dir == RIGHT) && needVerticalMove) {
totalEstimate += 1; // 考虑转向的代价
}
if ((state.dir == UP || state.dir == DOWN) && needHorizontalMove) {
totalEstimate += 1; // 考虑转向的代价
}
}
}

totalEstimate += dx + dy; // 曼哈顿距离
currentX = fruits[i].first;
currentY = fruits[i].second;
}

return totalEstimate;
}

public:
SnakePathFinder(const vector<pair<int, int>>& fruitSequence) {
fruits = fruitSequence;
}

string findOptimalPath(int startX, int startY, Direction startDir) {
// 使用优先队列,按照估计的总步数排序
auto cmp = [](const State& a, const State& b) {
return (a.steps + a.path.length()) > (b.steps + b.path.length());
};
priority_queue<State, vector<State>, decltype(cmp)> pq(cmp);

set<State> visited;
State initial(startX, startY, startDir, "", 0, 0);
pq.push(initial);
visited.insert(initial);

while(!pq.empty()) {
State current = pq.top();
pq.pop();

// 如果所有果子都被吃掉
if(current.fruitIndex >= fruits.size()) {
return current.path;
}

// 如果到达当前目标果子
if(current.x == fruits[current.fruitIndex].first &&
current.y == fruits[current.fruitIndex].second) {
State nextState = current;
nextState.fruitIndex++;

if(visited.find(nextState) == visited.end()) {
pq.push(nextState);
visited.insert(nextState);
}
continue;
}

// 尝试所有可能的方向
for(int i = 0; i < 4; i++) {
Direction newDir = static_cast<Direction>(i);

// 不能直接调头
if(newDir == getOppositeDirection(current.dir)) {
continue;
}

auto [newX, newY] = moveInDirection(current.x, current.y, newDir);

if(!isValid(newX, newY)) {
continue;
}

string newPath = current.path;
if(newDir != current.dir) {
newPath += getDirectionChar(newDir);
}

State newState(newX, newY, newDir, newPath,
current.fruitIndex, current.steps + 1);

if(visited.find(newState) == visited.end()) {
pq.push(newState);
visited.insert(newState);
}
}
}

return "No path found";
}
};

unsigned char map_data_orig[1152] = {
0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0x38, 0x4C, 0xB0, 0x38, 0x6D,
0xEE, 0x3F, 0xC4, 0xB4, 0xB4, 0x09, 0x6A, 0xF0, 0x38, 0x2C, 0x79, 0xF6, 0x34, 0xE9, 0x89, 0x38,
0xAC, 0x7F, 0x35, 0xD4, 0xB4, 0xB4, 0x38, 0x6D, 0x77, 0xF6, 0xB6, 0x38, 0x6D, 0x78, 0xF6, 0xB6,
0x2B, 0x18, 0xB4, 0xB4, 0xB4, 0x3B, 0x81, 0x81, 0x81, 0x81, 0xEF, 0x4E, 0x38, 0x4C, 0x7D, 0xF6,
0x33, 0xD4, 0xB4, 0xB4, 0xB0, 0xE8, 0xF4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB0, 0xE8, 0xF6, 0x2B, 0x27,
0xA3, 0x1D, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xB4, 0xB0, 0xF8, 0x04, 0x38, 0x89,
0xE3, 0xC3, 0xCA, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0xB0, 0xF8, 0x04, 0x38,
0xB3, 0x67, 0xE3, 0x16, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xD4, 0xB0, 0xF8, 0x04,
0x38, 0xB6, 0xD3, 0xB6, 0xA9, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0xB0, 0xF8,
0x04, 0x38, 0x89, 0xD8, 0xC7, 0x33, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xB4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xD4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xE4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0xB0, 0xEC, 0xFE, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4,
0x6F, 0x14, 0x4C, 0xEC, 0xFE, 0xB4, 0xB4, 0xB4, 0x2F, 0xC0, 0x2C, 0xEC, 0xFE, 0xB4, 0xB4, 0xB4,
0xCC, 0x6C, 0xFE, 0xB4, 0xB4, 0xB4, 0xB6, 0x24, 0xCC, 0x72, 0xB4, 0xB4, 0xB4, 0x3B, 0xF4, 0xB4,
0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xB4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x4C,
0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1, 0xC4, 0x4C,
0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xC4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xCC, 0xE2, 0xE4, 0x4C, 0xE1, 0x4C, 0xF9, 0xED,
0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1, 0xE3, 0x60, 0xE4, 0x79, 0x04,
0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x2C, 0xF8, 0x85, 0x37,
0x4C, 0xE8, 0xF6, 0x4C, 0x69, 0xF4, 0xE4, 0x40, 0x4C, 0xD0, 0x2C, 0xE8, 0xF4, 0x3B, 0xF4, 0xB4,
0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x4C,
0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1, 0xB4, 0x4C,
0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xB4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xD0, 0x62, 0x64, 0xCC, 0xE2, 0xE4, 0x4C, 0xE1,
0x4C, 0xF9, 0xED, 0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1, 0xE3, 0x60,
0xE4, 0x79, 0x04, 0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x2C,
0xF8, 0x85, 0x37, 0x52, 0x54, 0x2F, 0x2F, 0x2F, 0xB0, 0xEC, 0x00, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4,
0xB4, 0xB4, 0x6F, 0x14, 0x4C, 0xEC, 0x00, 0xB4, 0xB4, 0xB4, 0x2F, 0xC0, 0x2C, 0xEC, 0x00, 0xB4,
0xB4, 0xB4, 0xCC, 0x6C, 0x00, 0xB4, 0xB4, 0xB4, 0xB6, 0x24, 0xCC, 0x72, 0xB4, 0xB4, 0xB4, 0x3B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xD4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xE4, 0x4C, 0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xE4, 0x4C, 0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38,
0x4A, 0xE1, 0xE4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xCC, 0xE2, 0xE4, 0x4C, 0xE1, 0x4C,
0xF9, 0xED, 0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1, 0xE3, 0x60, 0xE4,
0x79, 0x04, 0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x2C, 0xF8,
0x85, 0x37, 0x4C, 0xE8, 0xF6, 0x4C, 0x69, 0xF4, 0xE4, 0x40, 0x4C, 0xD0, 0x2C, 0xE8, 0xF4, 0x3B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xD4, 0x4C, 0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xD4, 0x4C, 0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38,
0x4A, 0xE1, 0xD4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xD0, 0x62, 0x64, 0xCC, 0xE2, 0xE4,
0x4C, 0xE1, 0x4C, 0xF9, 0xED, 0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1,
0xE3, 0x60, 0xE4, 0x79, 0x04, 0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xE4, 0x2C, 0xF8, 0x85, 0x37, 0x52, 0x54, 0x2F, 0x2F, 0x2F, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38,
0x4A, 0xC0, 0xB4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x4C, 0x79, 0x85, 0x37,
0x4C, 0xF8, 0x04, 0x37, 0xE3, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x2C,
0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B, 0xF4, 0xB4, 0xB4,
0xB4, 0x38, 0x4A, 0x50, 0xE4, 0x4C, 0x79, 0x85, 0x37, 0x4C, 0xF8, 0x04, 0x37, 0xE3, 0xD0, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4,
0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x4C, 0x79,
0x85, 0x37, 0x4C, 0xF8, 0x04, 0x37, 0xE3, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xE4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B, 0xF4,
0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x4C, 0x79, 0x85, 0x37, 0x4C, 0xF8, 0x04, 0x37, 0xE3,
0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0xA0, 0xEC,
0x42, 0xB4, 0xB4, 0xB4, 0x3D, 0xA0, 0xEC, 0x52, 0xB4, 0xB4, 0xB4, 0xBE, 0xA0, 0xEC, 0x62, 0xB4,
0xB4, 0xB4, 0x51, 0xA0, 0xEC, 0x6F, 0xB4, 0xB4, 0xB4, 0x3D, 0xA0, 0xEC, 0x7F, 0xB4, 0xB4, 0xB4,
0x5B, 0xA0, 0xEC, 0x12, 0xB4, 0xB4, 0xB4, 0x8D, 0xA0, 0xEC, 0x22, 0xB4, 0xB4, 0xB4, 0x65, 0xA0,
0xEC, 0x32, 0xB4, 0xB4, 0xB4, 0xA7, 0xA0, 0xEC, 0xBF, 0xB4, 0xB4, 0xB4, 0x4D, 0xA0, 0xEC, 0xCF,
0xB4, 0xB4, 0xB4, 0xAC, 0xA0, 0xEC, 0xDF, 0xB4, 0xB4, 0xB4, 0xF8, 0xA0, 0xEC, 0xEF, 0xB4, 0xB4,
0xB4, 0x06, 0xA0, 0xEC, 0xFF, 0xB4, 0xB4, 0xB4, 0xE9, 0xA0, 0xEC, 0x8F, 0xB4, 0xB4, 0xB4, 0x3B,
0xA0, 0xEC, 0x9F, 0xB4, 0xB4, 0xB4, 0xA3, 0xA0, 0xEC, 0xAF, 0xB4, 0xB4, 0xB4, 0x31, 0xB0, 0xEC,
0xF5, 0xC4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0x6F, 0x14, 0x4C, 0xEC, 0xF5, 0xC4, 0xB4, 0xB4,
0x2F, 0xC0, 0x2C, 0xEC, 0xF5, 0xC4, 0xB4, 0xB4, 0xCC, 0x6C, 0xF5, 0xC4, 0xB4, 0xB4, 0xB5, 0x68,
0xE6, 0x38, 0xCA, 0xEC, 0xF5, 0xC4, 0xB4, 0xB4, 0x24, 0x1B, 0xF8, 0x04, 0x37, 0x38, 0xCA, 0x6D,
0xF5, 0xC4, 0xB4, 0xB4, 0x24, 0x1B, 0x7D, 0x85, 0x42, 0xB4, 0xB4, 0xB4, 0x63, 0xD0, 0xF7, 0xF4,
0xD3, 0xC0, 0x6F, 0xF4, 0x6F, 0x00, 0xBB, 0xC4, 0x38, 0x4C, 0x3F, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD
};

uint8 *map_data;

unsigned char tmp_map[1152]={};

std::string charArrayToHex(uint8* input, size_t size) {
std::stringstream hexStream;
hexStream << std::hex << std::setfill('0');
for (size_t i = 0; i < size; ++i) {
hexStream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(input[i]));
}
return hexStream.str();
}


int main() {
int n = 1152;

map_data = (uint8 *) malloc(n);

int rounds = 0;
while (true) {
rounds++;

srand(0xDEADBEEF);
memcpy(map_data, map_data_orig, n);
// 测试用例
vector<pair<int, int>> fruits;

for (int i = 0; i < rounds; ++i) {
int y = rand() % 20;
int x = rand() % 20;
fruits.push_back({x, y});
// cout << x << " " << y << "\n";
}

SnakePathFinder pathFinder(fruits);

vector<uint8> v;

v.push_back(0x11);
v.push_back(0x12);

string path = pathFinder.findOptimalPath(10, 10, RIGHT);

for (char p : path) {
if(p == 'D')
{
for(int i=0;i<n;i++)
{
map_data[i] += 30;
}

}
else if(p == 'A'){
for(int i=0;i<n;i++)
{
tmp_map[i] = map_data[i];

}
for(int i=0;i<n;i++)
{
map_data[i] = tmp_map[(i+6)%n];
}
}
else if(p == 'S')
{
for(int i=0;i<n;i++){
map_data[i]=(map_data[i]>>5)|(map_data[i]<<3);
}
}
else if(p == 'W'){
for(int i=0;i<n;i++){
map_data[i]-=102;
}

}
}

MD5 md5 = MD5(map_data, n);
string hexd = md5.toStr();

cout << "round:" << rounds << " way: " << path << " md5: " << hexd << " ";

if (hexd == "9c06c08f882d7981e91d663364ce5e2e") {
cout << "found it" << "\n";
cout << charArrayToHex(map_data, n);
exit(0);
} else {
cout << "nop\n";
}

if (rounds > 1000) break;
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
round:1 way: W md5: 62c753a165784d502246b51539f63797 nop
round:2 way: WAS md5: ddf7a36ef6ee6dfc6dc91aad5d0dd21d nop
round:3 way: WASA md5: 0f27f5aff365ec905bd9a5e4c60cd8b3 nop
round:4 way: WASAWD md5: 9581209074985da9fa69047ce954c589 nop
round:5 way: WASAWDSA md5: 6d87d9bc85ccf44e938dcb242d6baee3 nop
round:6 way: WASAWDSAW md5: 30e6d405dd4d5e5af6d41f7ec1b1ab43 nop
round:7 way: WASAWDSAWD md5: 587264015d204347a510bf2c72723097 nop
round:8 way: WASAWDSAWDS md5: 6e3ab0e912c25e56c4283a011f2d2207 nop
round:9 way: WASAWDSAWDSAW md5: 0a7b22abe33900efc2ae7f25f9950bb5 nop
round:10 way: WASAWDSAWDSAWDS md5: 9c06c08f882d7981e91d663364ce5e2e found it
48894c240855574881ec18020000488d6c2420488d7c2420b94e000000b8ccccccccf3ab488b8c2438020000c7450400000000c74524b979379eb804000000486bc000c744054857333163b804000000486bc001c7440548306d332eb804000000486bc002c744054820322051b804000000486bc003c744054857427338b804000000486bc000b904000000486bc900488b95100200008b040289440d78b804000000486bc001b904000000486bc901488b95100200008b040289440d78b804000000486bc002b904000000486bc902488b95100200008b040289440d78b804000000486bc003b904000000486bc903488b95100200008b040289440d78c785a400000000000000eb0e8b85a4000000ffc08985a400000083bda4000000200f83db000000b804000000486bc000b904000000486bc9018b4c0d78c1e104ba04000000486bd2018b541578c1ea0533caba04000000486bd201034c15788b550483e2038bd28b549548448b45044403c2418bd033ca034c05788bc1b904000000486bc90089440d788b45248b4d0403c88bc1894504b804000000486bc001b904000000486bc9008b4c0d78c1e104ba04000000486bd2008b541578c1ea0533caba04000000486bd200034c15788b5504c1ea0b83e2038bd28b549548448b45044403c2418bd033ca034c05788bc1b904000000486bc90189440d78e90affffffc785c400000000000000eb0e8b85c4000000ffc08985c400000083bdc4000000200f83db000000b804000000486bc002b904000000486bc9038b4c0d78c1e104ba04000000486bd2038b541578c1ea0533caba04000000486bd203034c15788b550483e2038bd28b549548448b45044403c2418bd033ca034c05788bc1b904000000486bc90289440d788b45248b4d0403c88bc1894504b804000000486bc003b904000000486bc9028b4c0d78c1e104ba04000000486bd2028b541578c1ea0533caba04000000486bd202034c15788b5504c1ea0b83e2038bd28b549548448b45044403c2418bd033ca034c05788bc1b904000000486bc90389440d78e90affffffb804000000486bc000b904000000486bc9028b4c0d788b44057833c1b904000000486bc90089440d78b804000000486bc001b904000000486bc9038b4c0d788b44057833c1b904000000486bc90189440d78b804000000486bc003b904000000486bc9008b4c0d788b44057833c1b904000000486bc90389440d78b804000000486bc001b904000000486bc9028b4c0d788b44057833c1b904000000486bc90289440d78c685e800000098c685e9000000a0c685ea000000d9c685eb00000098c685ec000000bac685ed00000097c685ee0000001bc685ef00000071c685f00000009bc685f100000081c685f200000044c685f30000002fc685f400000055c685f5000000b8c685f600000037c685f7000000dfc7851401000000000000eb0e8b8514010000ffc089851401000083bd14010000107d25486385140100000fbe44057848638d140100000fbe8c0de80000003bc1740432c0eb04ebc4b001488bf890909090909090909090909090909090488bc7488da5f80100005f5dc3

然后吧解出来内容直接用 IDA 分析,是个魔改 XTea + 异或,主要魔改点就是四个密文,前两个 delta 从 0开始加起,后两组的 delta 从 32 * delta 开始算起

image-20241103202351128

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
#include<stdio.h>

int main() {
unsigned int enc[4] = {0x98d9a098,
0x711b97ba,
0x2f44819b,
0xdf37b855};
int n = 4;
unsigned int key[4] = {0x63313357,
0x2e336d30,
0x51203220,
0x38734257};
int i, j;
long sum = 0, delta = 0x9E3779B9;
enc[2] ^= enc[1];
enc[3] ^= enc[0];
enc[1] ^= enc[3];
enc[0] ^= enc[2];
for (i = 0; i < n; i += 2) {
sum = (32 * delta * (i / 2 + 1));
for (j = 0; j < 32; j++) {
enc[i + 1] -= (((enc[i] >> 5) ^ (16 * enc[i])) + enc[i]) ^ (key[((sum >> 11) & 3)] + sum);//容易魔改
sum -= delta;
enc[i] -= (((enc[i + 1] >> 5) ^ (16 * enc[i + 1])) + enc[i + 1]) ^ (key[(sum & 3)] + sum);//容易魔改
}
}
for (i = 0; i < n; i++) {
for (j = 0; j <= 3; j++) {
printf("%c", (enc[i] >> (j * 8)) & 0xFF);
}
}
return 0;
}

// flag{G0@d_Snake}

mips

一个魔改 qemu 题
题目给了一个 mips 架构的 qemu-user-static,mips 本来是可以直接运行的,出题人给个模拟器,那么肯定是有怪的,观察到 emu 被出题人去符号了,所以先查字符串搜索到这是一个 qemu 6.2.0 的 user-static,然后拉一份源码自行编译 bindiff

试过了许多优化等级,发现只有 -O0 的时候,符号还原率还有文件大小和题目给出的最相似
编译命令 ../configure --extra-cflags="-O0" --target-list=mips-linux-user

然后 bindiff 就还原出了大部分的符号

image-20241103203055451

一开始想用 -g 选项直接调试 mips_bin,但是发现 原本的调试功能被作者去掉了,于是去分析

image-20241103203226913

0x000000000034B7A3 下找到了原本的 gdbserver_start,然后 patch 模拟器,打开调试功能

image-20241103203326393 image-20241103203402143

然后使用 ./emu -g 1234 mips_bin2 打开gdb服务器,ida附加调试

程序中还有一个 fork 会影响分析,虽然可以 patch 掉但是会导致 flag 验证结果错误。在分析过程中会发现一个比较简单的假 flag,如果不用模拟器,这个 flag 就是对的,那么想到应该是模拟器上做了手脚,单步调试,最后发现在 syscall 之前 输出内容并没有被修改,那么只能想到是 qemu 中的 do_syscall 被修改了

image-20241103203959414

write 的调用号是 0xFA4
image-20241103204057973

果不其然,这里有额外的逻辑
通过交叉引用找到加密的逻辑

image-20241103204208954

sub_33D48E 是一个魔改的 RC4,其中还有一点花指令,让反编译不正常,修复后主要魔改在 RC4 最后一步的异或上

image-20241103204313464

然后是一个简单异或,异或的值在 qemu 中处理 fork() 指令的函数 do_fork() 中被修改了,所以说如果之前把 fork() patch掉了也会导致没法解出来

image-20241103204415794

sub_33D886 是一个换位函数,这里吧密文的 7,11 和 12,16 位进行了交换
在了解所有加密逻辑后,写一个解密脚本

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
#include <iostream>
#include "defs.h"
#include <cstdint>

unsigned char a1[60] = {
0xA8, 0xAC, 0x36, 0x6A, 0xC4, 0x0A, 0x9A, 0xDC, 0x12, 0x48, 0xF2, 0x60, 0xCB, 0xCC, 0x3A, 0x5E,
0xF2, 0x63, 0x9C, 0x94, 0xF5, 0x48, 0xCD, 0x17, 0x82, 0xCD, 0xF7, 0x71, 0x9F, 0x36, 0xB4, 0x88,
0xAF, 0x5F, 0xDD, 0x64, 0x85, 0x96, 0xF7, 0x5E, 0xC4, 0x09, 0xAD, 0xDD, 0xAB, 0x16, 0x99, 0x60,
0x9B, 0xDE, 0xF5, 0x53, 0xC3, 0x21, 0xFC, 0x80, 0xF8, 0x10, 0xC7, 0x26
};

unsigned char byte_4020[6] = {
0x4F, 0x7D, 0x8E, 0x2B, 0x31, 0x9C
};

unsigned int enced[24] = {
0x000000C4, 0x000000EE, 0x0000003C, 0x000000BB, 0x000000E7, 0x000000FD, 0x00000067, 0x0000001D,
0x000000F8, 0x00000097, 0x00000068, 0x0000009D, 0x0000000B, 0x0000007F, 0x000000C7, 0x00000080,
0x000000DF, 0x000000F9, 0x0000004B, 0x000000A0, 0x00000046, 0x00000091, 0x00000000, 0x00000000
};

unsigned char byte_B9CA60[32] = {
0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

uint8_t reverse_operation(uint8_t v3) {
uint8_t temp = v3;

uint8_t part1 = (temp & 0xC0) ^ 0xC0; // High bits
uint8_t part2 = (temp & 0x3F) ^ 0x3B; // Low bits

uint8_t middle = (part1 >> 6) | (part2 << 2);
uint8_t v2 = (middle >> 7) | (middle << 1);

return v2;
}

char *__fastcall decrypt(unsigned char *a1)
{
int v1; // edx
unsigned __int8 v3; // [rsp+15h] [rbp-17Bh]
int i; // [rsp+18h] [rbp-178h]
int v5; // [rsp+1Ch] [rbp-174h]
unsigned int v6; // [rsp+20h] [rbp-170h]
int i_22; // [rsp+24h] [rbp-16Ch]
int j_t12; // [rsp+28h] [rbp-168h]
int j; // [rsp+2Ch] [rbp-164h]
int v10; // [rsp+30h] [rbp-160h]
int v11; // [rsp+34h] [rbp-15Ch]
int v12; // [rsp+3Ch] [rbp-154h]
const char *v13; // [rsp+40h] [rbp-150h]
char *v14; // [rsp+48h] [rbp-148h]
__int8 sbox[256]; // [rsp+80h] [rbp-110h] BYREF
__int16 v16; // [rsp+180h] [rbp-10h]
unsigned __int64 v17; // [rsp+188h] [rbp-8h]

memset(sbox, 0, sizeof(sbox));
v16 = 0;
for ( i = 0; i <= 255; ++i )
sbox[i] = i;
v5 = 0;
v6 = 0;
v13 = "6105t3";
do
{
v10 = (unsigned __int8)sbox[v6];
v11 = (unsigned __int8)(v13++)[(int)(2 * (v6 / 6 - (((2863311531u * (unsigned __int64)v6) >> 32) & 0xFFFFFFFC)))];
v5 += v10 + v11;
v1 = v6++;
sbox[v1] = sbox[(unsigned __int8)v5];
sbox[(unsigned __int8)v5] = v10;
}
while ( v6 != 256 );
i_22 = 0;
j_t12 = 0;
v14 = (char *) malloc(256LL);
for ( j = 0; j != 22; ++j )
{
v12 = (unsigned __int8)sbox[(unsigned __int8)++i_22];
j_t12 += v12;
sbox[(unsigned __int8)i_22] = sbox[(unsigned __int8)j_t12];
sbox[(unsigned __int8)j_t12] = v12;
// v3 = ((((a1[j] << 7) | (a1[j] >> 1)) << 6) ^ 0xC0 | (((a1[j] << 7) | (a1[j] >> 1)) >> 2) ^ 0x3B) ^ 0xBE;
// v14[j] = sbox[(sbox[i_22] + v12)] ^ byte_B9CA60[j & 3] ^ (((((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | ((((32 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE) >> 5) | (8 * (((16 * (((32 * v3) | (v3 >> 3)) ^ 0xAD)) | ((((32 * v3) | (v3 >> 3)) ^ 0xAD) >> 4)) ^ 0xDE)));

uint8 step1 = a1[j] ^ sbox[(unsigned __int8)(sbox[(unsigned __int8)i_22] + v12)] ^ byte_B9CA60[j & 3];

uint8 s1_p1 = step1 >> 3 | step1 << 5;
s1_p1 ^= 0xDE;
s1_p1 = s1_p1 << 4 | s1_p1 >> 4;
s1_p1 ^= 0xAD;
s1_p1 = s1_p1 << 3 | s1_p1 >> 5;

uint8 v3 = s1_p1;
v3 ^= 0xBE;

uint8 dt = reverse_operation(v3);

printf("%c", dt);

}
return v14;
}

int main() {

uint32 tmp = enced[12];
enced[12] = enced[16];
enced[16] = tmp;

tmp = enced[7];
enced[7] = enced[11];
enced[11] = tmp;

uint8 css[24];

for (int i = 0; i < 22; ++i) {
enced[i] ^= 0xA;
css[i] = (uint8) enced[i];
}
decrypt(css);
return 0;
}

// flag{QeMu_r3v3rs3in9_h@ck6}
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
访客数 访问量