逆向 ak,但感觉自己代码阅读能力还是很差,耐心也不够,很多地方都是借助 AI 的

ps:不知道为什么图好像不见了,懒得再找了

# encrypted

简单异或,用前缀 palu {猜一下异或数字

1
2
3
4
a='qcoq~Vh{e~bccocH^@Lgt{gtg'
for i in range(len(a)):
print(chr(ord(a[i])^(i+1)),end='')
# palu{PosltionalXOR_sample}

# CatchPalu

这一段 hook 了 messageboxa 的函数地址,在这段代码之后调用的 messageboxa 都会变成 sub_401360

动态调试看看

这里 flag 输入要输入附件里面的,也就是 palu {P1au_D0nt_Bel1eve},才能进入下面的 messageboxa 逻辑

进去就可以发现真正的比对函数,这里给硬编码的 v8 加密,似乎是魔改 rc4,但不需要编写代码,直接进函数 sub_CE1270,等函数走完看 v8 内存就行

ps:这里我做题的时候密钥被改变了,我当时手动修改回了 forpalu,但这里复现写 wp 的时候密钥却是正确的,不知道什么情况...

# Asymmetric

又是一个 go,长得很丑得耐心点看

动态调试慢慢分析,可以发现其实是一个 rsa,数据都给了,但是要因式分解,随便找个网站分一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from Crypto.Util.number import inverse
N_str = "100000000000000106100000000000003093"
e = 65537
C_str = "94846032130173601911230363560972235"

N = int(N_str)
C = int(C_str)
p1 = 3
p2 = 47
p3 = 2287
p4 = 3101092514893
p5 = 100000000000000003
phi_N = (p1 - 1) * (p2 - 1) * (p3 - 1) * (p4 - 1) * (p5 - 1)
d = inverse(e, phi_N)

M = pow(C, d, N)
M_str = str(M)

print("计算出的输入字符串是:", M_str)
# 2279348573780051194351488552157565
a=2279348573780051194351488552157565
print(a.to_bytes(16,'big'))
# palu{3a5Y_R$A}

# PaluArray

简单的 crackme,只不过函数长得很恶心

先根据字符串定位到主函数逻辑

其实就是要让 v13=1145141919810

至于 v13 的生成逻辑,要看 sub_7FF6987E1994

简单来说,就是根据传入的 v4,在 off_7FF6987E9B48 里找索引值

动态调试可以发现,v4 其实就是我们输入的字符串,那么先解出来我们输入的字符串是什么

1
2
3
4
5
a=[0x50,0x61,0x6C,0x75,0x5F,0x39,0x39,0x36,0x21,0x3F]
b='1145141919810'
for i in b:
print(chr(a[int(i,10)]),end='')
# aa_9a_a?a?!aP

把这个输入就得到 flag 了

# PaluFlat

.com 文件,用 7zip 解压,解压得到一个 1g 的文件,有点吓人。。。

不过主要逻辑不难

我们的输入经过函数 401550 加密,再与硬编码的密文比对即可

sub_401550 被控制流平坦化混淆,有些难看

但我把这个函数丢给 ai 它直接就分析出来了... 运气还行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enc=[0x54,0x84,0x54,0x44,0xA4,0xB2,0x84,0x54,0x62,0x32,0x8F,0x54,0x62,0xB2,0x54,0x3,0x14,0x80,0x43]
def decrypt(ciphertext):
key1 = "palu"
key2 = "flat"
plaintext = []
for i, c in enumerate(ciphertext):
# 选择密钥
key = key1 if i % 2 == 0 else key2
k = key[i % len(key)]
# 解密步骤
v11 = ~c & 0xFF # 取反(注意处理符号)
v11 = (v11 + 85) & 0xFF # 加 85
v11 = ((v11 << 4) (v11 >> 4)) & 0xFF # 交换高低四位
plaintext.append(chr(v11 ^ ord(k))) # 异或密钥
return ''.join(plaintext)

print(decrypt(enc))

# palu{Fat_N0t_Flat!}

# palugogogo

简单的 go 逆向,我用的 ida9 版本可以直接显示函数符号,不知道低版本的怎么样,go 逆向就是函数长得太丑了很难看,但动态调试分析并不难

这里一开始有个反调试,hook 掉返回值过掉就行

具体比对逻辑在下面

输入的 flag 经过 encrypt 这个函数加密,再与硬编码的密文比对,其中 value 与我们输入的无关,它是程序内部自生成的密钥,直接动调拿就可以,值是 0x4F

至于这个加密也是耐心动调分析就能出,一开始我测试了一下,发现是逐字节加密,加密逻辑如下

1
2
3
4
a='palu{asdasdsajkhfjasf}'
value=0x4f
for i in range(len(a)):
print(hex(ord(a[i])+value+(i%5)),end=',')

照着解密就可以

1
2
3
4
5
enc=[0xbf,0xb1,0xbd,0xc7,0xce,0x96,0x80,0x98,0x82,0x9a,0x7f,0xaf,0xc1,0xb3,0xbf,0xc4,0xcd]
value=0x4f
for i in range(len(enc)):
print(chr(enc[i]-value-(i%5)),end='')
# palu{G0G0G0_palu}

# ParlooChecker

个人感觉最难的一道题,太考验耐心了

apk 逆向,但 jadx 无法分析,转到 jeb

主要加密比对逻辑被藏在本地方法 parloo 里面,解压 apk 看一下 so 文件,so 文件的导出表只有一个函数,就是 oncreat3

函数没任何符号,动调 so 文件又有些麻烦,只能一个个看了,前面的没什么用,直接看 else 部分

sub_29390、sub_291D0 两个函数进行初始化,这部分使用 rc4 创建了后面加密要用的密钥,后面有很多函数都不用过多分析,大多起到混淆、分配内存或是格式化字符串作用

sub_28D30 里面藏了一个 tea

大致逻辑如上,主要调用 rc4 生成密钥之类的,再调用 tea 加密原文,大多数函数都没有任何作用

ps:如果不用 AI 我可能要花更多时间。。。

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
import struct
import sys

a=[0x99,0xDD,0x56,0xFF,0x6D,0xD9,0x55,0x54,0x42,0x4D,0x79,0x1A,0x34,0xB7,0x81,0x2F]
UNK_10170 = b''
for i in a:
UNK_10170+=i.to_bytes(1)
b=[0x87,0xC1,0x56,0xC0,0x4C,0xF4,0x63,0x4F]
UNK_10180 = b''
for i in b:
UNK_10180+=i.to_bytes(1)
enc=[0xA9,0xB,0x5C,0x1C,0xA3,0x41,0x88,0xCA,0x66,0xD9,0x77,0x1D,0x78,0x3,0x8E,0x7A,0xBA,0x7B,0xD4,0x90,0xCD,0x50,0x7,0x83,0x41,0x4A,0x82,0x9C,0x79,0x1D,0xCC,0x6F,0x9D,0x2F,0x39,0x2D,0xA2,0xDA,0x83,0x1B]
TARGET_CIPHERTEXT = b''
for i in enc:
TARGET_CIPHERTEXT+=i.to_bytes(1)
def rc4_ksa(key):
"""RC4 Key-Scheduling Algorithm (based on sub_2C740)"""S = list(range(256))
j = 0
key_bytes = key
key_len = len(key_bytes)

for i in range(256):
j = (j + S[i] + key_bytes[i % key_len]) % 256
S[i], S[j] = S[j], S[i]
return S

def rc4_prga_sub_293E0(initial_S, input_data):
"""RC4 Pseudo-Random Generation Algorithm (based on sub_293E0 loop)"""S = list(initial_S)
output_data = bytearray(len(input_data))

v8 = 0
v7 = 0

for k in range(len(input_data)):
v8 = (v8 + 1) % 256
v7 = (v7 + S[v8]) % 256
S[v8], S[v7] = S[v7], S[v8]

keystream_index = (S[v7] + S[v8]) % 256
keystream_byte = S[keystream_index]
output_data[k] = keystream_byte ^ input_data[k]

return bytes(output_data)

XTEA_DELTA_CONSTANT = 1640531527 # 0x61C88647

def calculate_encryption_v4_sequence(key_bytes):
"""计算加密轮次中 v4 值的序列。"""v4_sequence = [0]
v4_current = 0
k = [
struct.unpack('<I', key_bytes[i*4 : i*4+4])[0]
for i in range(4)
]

for round_index in range(32):
k2_index = round_index & 3
k2 = k[k2_index]
v4_update_term = (round_index ^ k2) - XTEA_DELTA_CONSTANT
v4_current = (v4_current + v4_update_term) & 0xFFFFFFFF
v4_sequence.append(v4_current)

return v4_sequence

def decrypt_block_round(v6_end, v5_end, v4_start_of_round, v4_end_of_round, round_index, key_bytes):
"""解密定制 XTEA 变种的一轮。"""v6_end = v6_end & 0xFFFFFFFF
v5_end = v5_end & 0xFFFFFFFF
v4_start_of_round = v4_start_of_round & 0xFFFFFFFF
v4_end_of_round = v4_end_of_round & 0xFFFFFFFF

k = [
struct.unpack('<I', key_bytes[i*4 : i*4+4])[0]
for i in range(4)
]

k3_index = (v4_end_of_round >> 11) & 3
k3 = k[k3_index]
intermediate_term2 = v6_end + ((v6_end >> 5) ^ (v6_end << 4))
term2 = (k3 + v4_end_of_round) ^ intermediate_term2
v5_start = (v5_end - term2) & 0xFFFFFFFF

k1_index = v4_start_of_round & 3
k1 = k[k1_index]
intermediate_term1 = v5_start + ((v5_start >> 5) ^ (v5_start << 4))
term1 = (k1 + v4_start_of_round) ^ intermediate_term1
v6_start = (v6_end - term1) & 0xFFFFFFFF

return (v6_start, v5_start)

def cbc_decrypt(ciphertext, key, iv):
"""使用定制 XTEA 变种以 CBC 模式解密密文。"""block_size = 8 # 字节
if len(ciphertext) % block_size != 0:
print(f"错误:密文长度 ({len(ciphertext)}) 不是分组大小 ({block_size}) 的倍数。无法执行 CBC 解密。")
sys.exit(1)

v4_sequence = calculate_encryption_v4_sequence(key)

plaintext_bytes = bytearray()
previous_ciphertext_block = iv

for i in range(0, len(ciphertext), block_size):
current_ciphertext_block = ciphertext[i : i + block_size]

c0, c1 = struct.unpack('<II', current_ciphertext_block)

decrypted_block_state = (c0, c1)

for reverse_round_index in range(31, -1, -1):
v4_start = v4_sequence[reverse_round_index]
v4_end = v4_sequence[reverse_round_index + 1]

decrypted_block_state = decrypt_block_round(
decrypted_block_state[0], decrypted_block_state[1],
v4_start, v4_end,
reverse_round_index,
key
)

p_prime_bytes = struct.pack('<II', decrypted_block_state[0], decrypted_block_state[1])

plaintext_block = bytes([
p_prime_bytes[j] ^ previous_ciphertext_block[j] for j in range(block_size)
])

plaintext_bytes.extend(plaintext_block)

previous_ciphertext_block = current_ciphertext_block

return bytes(plaintext_bytes)
if not UNK_10170 or len(UNK_10170) != 16:
print("错误:请提供 UNK_10170 的 16 字节数据,需从 SO 文件中提取。")
elif not UNK_10180 or len(UNK_10180) != 8:
print("错误:请提供 UNK_10180 的 8 字节数据,需从 SO 文件中提取。")
elif not TARGET_CIPHERTEXT or len(TARGET_CIPHERTEXT) != 40: # <-- 检查长度为 40 字节
print(f"错误:请提供 TARGET_CIPHERTEXT 的 **完整的 40 字节** 数据,需从 SO 文件中提取。")
print(f"(当前提供的长度为 {len(TARGET_CIPHERTEXT)})")
else:
print("占位符数据似乎已提供。尝试解密...")

rc4_key_material = b"DoNotHackMe"
S_box_for_73040 = rc4_ksa(rc4_key_material)
KEY_QWORD_73040 = rc4_prga_sub_293E0(list(S_box_for_73040), UNK_10170)

S_box_for_73050 = rc4_ksa(rc4_key_material)
KEY_QWORD_73050 = rc4_prga_sub_293E0(list(S_box_for_73050), UNK_10180)

print(f"派生出的加密密钥 (qword_73040): {KEY_QWORD_73040.hex()}")
print(f"派生出的 IV (qword_73050): {KEY_QWORD_73050.hex()}")
print(f"目标密文 (40字节): {TARGET_CIPHERTEXT.hex()}")

decrypted_padded_plaintext = cbc_decrypt(TARGET_CIPHERTEXT, KEY_QWORD_73040, KEY_QWORD_73050)

plaintext = decrypted_padded_plaintext.rstrip(b'\x00')

print(f"\n解密后的填充明文 (40 字节): {decrypted_padded_plaintext.hex()}") # <-- 输出提示长度为 40

try:
flag = plaintext.decode('utf-8')
print(f"恢复的 Flag (解码为 UTF-8): {flag}")
except UnicodeDecodeError:
print(f"无法将恢复的明文解码为 UTF-8。")
print(f"恢复的明文字节 (十六进制): {plaintext.hex()}")
except Exception as e:
print(f"在最终处理过程中发生未知错误: {e}")

# palu{thiS_T1Me_it_seeM5_tO_8e_ReAl_te@}

# Game

迷宫,bfs 求解最短路径

但这道题明显多解,我最开始写的那个代码死活不对(其实也是 AI 给的)。。。

然后叫 ai 给我出了一个全解代码(一开始是 128 个答案,但题目后面给的 hint 把范围缩小到 64 个了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
import collections
import itertools
import hashlib

# Keep the original BFS to get distances, but modify it slightly or create a new one
# to just compute the distance grid efficiently.
def bfs_get_dist_grid(grid, start):
""" 使用 BFS 计算从 start 到所有可达点的最短距离,并返回距离网格。 """rows = len(grid)
cols = len(grid[0])
dist = [[float('inf') for _ in range(cols)] for _ in range(rows)] # 使用 inf 表示不可达或未访问

queue = collections.deque([(start[0], start[1])]) # (row, col)
dist[start[0]][start[1]] = 0

dr = [-1, 1, 0, 0] # 方向:上, 下, 左, 右
dc = [0, 0, -1, 1]

# 可行走的字符集合 (保持与之前一致)
walkable_chars = {'0', ' ', 'X', 'Y'}

while queue:
r, c = queue.popleft()

for i in range(4):
nr, nc = r + dr[i], c + dc[i]

if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != '#' and dist[nr][nc] == float('inf'):
dist[nr][nc] = dist[r][c] + 1
queue.append((nr, nc))

return dist # 返回完整的距离网格

def get_all_shortest_paths(grid, start, end, dist_grid):
""" 给定距离网格,递归回溯从 start 到 end 的所有最短路径。 """if dist_grid[end[0]][end[1]] == float('inf'):
return [] # 如果终点不可达

all_paths = []

# 递归回溯函数
def collect_paths(current_node, path_so_far):
r, c = current_node

if (r, c) == start:
all_paths.append(list(reversed(path_so_far + [current_node]))) # 到达起点,添加完整路径 (反转)
return

# 探索邻居,只回溯到距离小 1 的邻居 (即在最短路径上)
dr = [-1, 1, 0, 0]
dc = [0, 0, -1, 1]

for i in range(4):
pr, pc = r + dr[i], c + dc[i] # Parent row/col

# 检查边界
if 0 <= pr < len(grid) and 0 <= pc < len(grid[0]):
# 确保是从距离小 1 的点过来的
if dist_grid[r][c] == dist_grid[pr][pc] + 1:
# 检查这个点不是障碍
if grid[pr][pc] != '#':
collect_paths((pr, pc), path_so_far + [current_node]) # 递归回溯

# 从终点开始回溯
collect_paths(end, [])

return all_paths # 返回所有找到的最短路径坐标列表


def coords_to_wasd(path_coords_list):
""" 将坐标路径列表转换为 WASD 字符串。 (与之前相同) """wasd_string = ""
for i in range(len(path_coords_list) - 1):
r1, c1 = path_coords_list[i]
r2, c2 = path_coords_list[i+1]

dr = r2 - r1
dc = c2 - c1

if dr == 1:
wasd_string += 'S' # 向下
elif dr == -1:
wasd_string += 'W' # 向上
elif dc == 1:
wasd_string += 'D' # 向右
elif dc == -1:
wasd_string += 'A' # 向左

return wasd_string

# 您提供的已经替换空格的迷宫地图字符串
maze_string_0_filled = """
##############################0#
#Y#0000000000000000000#0000000X
#0#0#############0###0#0###0##0#
#0#000#000000000#0#000#000#000##
#0#####0#######0###0#0###0###0##
#000#000#00000#0#000#000#0#000##
###0#0###0#####0#0#######0#0####
#0#000#0#00000#0#00000#000#000##
#0#####0#0###0#0#0###0#0#####0##
#000000000#000#0#000#0000000#0##
#0#########0#0#0#############0##
#000#0#00000#0#000#00000000000##
###0#0#0#########0#0#########0##
#0#0#0#00000000000#0#0000000#0##
#0#0#0#############0#0#####0#0##
#000#0000000#0000000#0#000#000##
0X0##0###0###0#0#####0#0########
#000#000#00000#000#0#0#000#000##
###0###0#########0#0#0#0#0#0#0##
#0#0#000#000#000#0#0#0#0#0#0#0##
#0#0###0#0#0#0#0#0#0#0###0#0#0##
#0#000#0#0#000#000#000#000#0#0##
#0###0###0###########0#0#0#0#0##
#000#000#00000000000#0#0#0#0#0##
#0#####0#0#########0#0#0###0#0##
#000#000#000#0000000#0#0#000#0##
###0#0#######0#######0#0#0###0##
#000#000#000#0#00000#0#000#000##
#0#####0#0#0#0#0#####0#####0#0##
#000000000#000#0000000000000#00#
0X0############0X0###########0X
#0##############0#############0#
"""

# --- 解析迷宫字符串到网格 ---
lines = maze_string_0_filled.strip().split('\n')
grid = []
expected_cols = 32

for i, line in enumerate(lines):
if len(line) < expected_cols:
processed_line = line.ljust(expected_cols, '0')
elif len(line) > expected_cols:
processed_line = line[:expected_cols]
else:
processed_line = line
grid.append(list(processed_line))

rows = len(grid)
cols = len(grid[0]) if grid else 0
print(f"解析后的迷宫尺寸: {rows}x{cols}")

# 在网格中找到起始点 'Y' 和所有出口 'X' 的坐标
start_coord = None
exit_coords = []

for r in range(rows):
for c in range(cols):
if grid[r][c] == 'Y':
start_coord = (r, c)
elif grid[r][c] == 'X':
exit_coords.append((r, c))

print(f"在地图中找到的起始点 Y: {start_coord}")
print(f"在地图中找到的出口点 X: {exit_coords} (共 {len(exit_coords)} 个)")

# 在继续之前,验证是否找到一个 Y 和 5 个 X
if start_coord is None:
print("\n错误:未在地图中找到起始点 Y。")
elif len(exit_coords) != 5:
print(f"\n错误:在地图中找到的出口数量不为 5 个,而是 {len(exit_coords)} 个。无法进行计算。")
else:
print("\n成功在地图中找到所有关键点 (1个 Y, 5个 X)。正在计算关键点之间的最短路径距离...")

# 预先计算所有关键点对之间的最短距离
points = [start_coord] + exit_coords
dist_data = {} # 存储 ((p1_r, p1_c), (p2_r, p2_c)): distance

# 为了获取所有路径,我们需要从每个起点运行 BFS 来获取距离网格
# 存储从每个关键点出发的距离网格
dist_grids = {}
for p in points:
dist_grids[p] = bfs_get_dist_grid(grid, p)

# 填充 dist_data 使用计算好的距离网格
for i in range(len(points)):
for j in range(i + 1, len(points)):
p1 = points[i]
p2 = points[j]
# 从 p1 出发的距离网格中查找 p2 的距离
dist = dist_grids[p1][p2[0]][p2[1]]
if dist != float('inf'):
dist_data[(p1, p2)] = dist
dist_data[(p2, p1)] = dist # 距离是双向的


# 找到访问所有出口的最短总路径 (从 Y 开始,遍历所有 X)
min_total_dist = float('inf')
optimal_permutations = [] # 存储所有达到最小距离的出口访问顺序

print("\n正在查找访问所有出口的最短总距离...")

for perm in itertools.permutations(exit_coords):
current_total_dist = 0
current_point = start_coord
valid_permutation = True

# 计算当前排列顺序的总距离
for next_point in perm:
pair = (current_point, next_point)
if pair not in dist_data:
valid_permutation = False
break # 某段不可达

current_total_dist += dist_data[pair]
current_point = next_point

if valid_permutation:
if current_total_dist < min_total_dist:
min_total_dist = current_total_dist
optimal_permutations = [perm] # 找到更短的,清空并记录新的
elif current_total_dist == min_total_dist:
optimal_permutations.append(perm) # 找到同样短的,添加记录

print(f"\n计算出的访问所有出口的最短总路径距离 (从 Y 开始): {min_total_dist} 步。")
print(f"提示的最短路径距离: 290 步。")

if min_total_dist == 290:
print("计算结果与提示相符!正在生成所有最短路径的 WASD 序列...")

all_shortest_wasd_paths = set() # 使用集合存储唯一的 WASD 序列 (大写)

# 现在为每个达到最短总距离的排列组合生成所有可能的路径
for opt_perm in optimal_permutations:
segment_paths_lists = [] # 存储每个路径段的所有最短路径列表

current_point = start_coord
# 对于排列中的每个段 (Y -> X1, X1 -> X2, ...)
for next_point in opt_perm:
# 从当前点的距离网格中获取该段的所有最短路径
segment_dist_grid = dist_grids[current_point]
# 调用新的函数获取所有路径
all_segment_paths = get_all_shortest_paths(grid, current_point, next_point, segment_dist_grid)
if not all_segment_paths:
segment_paths_lists = []
break
segment_paths_lists.append(all_segment_paths)
current_point = next_point

if segment_paths_lists:
# 使用 itertools.product 组合所有路径段的所有可能组合
for path_combination in itertools.product(*segment_paths_lists):
combined_path_coords = []
for i, segment_path in enumerate(path_combination):
if i == 0:
combined_path_coords.extend(segment_path)
else:
combined_path_coords.extend(segment_path[1:])

wasd_path = coords_to_wasd(combined_path_coords) # 生成大写 WASD 路径
all_shortest_wasd_paths.add(wasd_path)

# --- 筛选路径 (最后四步 SS DS), 转小写, 计算 MD5 ---
filtered_md5s = []
filter_suffix_uppercase = "SSDS" # 筛选条件 (大写)

print(f"\n过滤路径:寻找最后四步为 '{filter_suffix_uppercase}' 的路径...")

# 遍历所有找到的最短路径 (大写)
for wasd_path_uppercase in all_shortest_wasd_paths:
# 检查路径是否足够长且以指定的后缀结束
if len(wasd_path_uppercase) >= len(filter_suffix_uppercase) and wasd_path_uppercase.endswith(filter_suffix_uppercase):
# 将符合条件的路径转为小写
wasd_path_lowercase = wasd_path_uppercase.lower()
# 计算小写路径的 MD5 哈希值
md5_hash = hashlib.md5(wasd_path_lowercase.encode('utf-8')).hexdigest()
filtered_md5s.append(md5_hash)

# 排序筛选后的哈希值以便输出顺序稳定
sorted_filtered_md5s = sorted(filtered_md5s)


print(f"\n共找到 {len(sorted_filtered_md5s)} 条长度为 {min_total_dist} 且最后四步为 '{filter_suffix_uppercase}' 的唯一最短路径。")
print("这些路径(转为小写后)的 MD5 哈希值如下:")

for i, md5_hash in enumerate(sorted_filtered_md5s):
print(f"Filtered Path {i+1} MD5: {md5_hash}")

print("\n对应的达到最短总距离的出口访问顺序 (从 Y 出发) 可能有:")
point_names = {start_coord: 'Y'}
for i, coord in enumerate(exit_coords):
point_names[coord] = f'X{i+1}'

for i, opt_perm in enumerate(optimal_permutations):
path_points_sequence = [start_coord] + list(opt_perm)
print(f"顺序 {i+1}: " + " -> ".join([f"{point_names.get(c, str(c))}{c}" for c in path_points_sequence]))


else:
print("计算结果与提示不符,请检查地图、坐标或算法实现。")
print("\n未能找到访问所有出口的有效路径或最短距离不为290。")

一个个试,试到第 41 个对了

palu