第三届黄河流域公安院校网络安全技能挑战赛 2025

Misc

数学天才

Challenge

森莫?要爆零了!那来签个到吧。

hint: 试炼一和试练二两个一起看解压葵花宝典

Solution

数学天才.txt 给出的提示如下:

小伙子,我看你骨骼清奇,必是旷世奇才,那就考验一下你的悟性吧!试炼一:斜下对角线的数字,是打开葵花宝典的密钥。试炼二:为师不想要死,为师喜欢 $。试炼三:你是第 60 位前来考核的人员,想想该怎么读懂葵花宝典呢?

sdpcctf2025-1

题目给的图片是一个杀手数独(Killer Sudoku),要求实现:

  1. 标准数独的规则(行、列和 3×3 方块不能重复)
  2. Killer Sudoku 特有的笼子 (cage) 约束
  3. 使用回溯算法寻找解决方案
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
# 初始化问题
N = 9
sudoku = [[0,0,0,0,0,3,0,8,0],
[3,0,0,1,0,0,0,0,0],
[6,0,0,0,0,0,3,0,0],
[0,0,0,0,6,0,7,0,0],
[0,0,0,9,0,0,0,0,4],
[0,6,0,0,0,5,0,0,0],
[0,0,8,0,0,0,4,0,9],
[0,7,0,3,0,0,0,0,0],
[0,0,4,0,9,0,0,0,0]]

cages = [
(6,[(0,0),(0,1)]),
(6,[(0,2),(0,3)]),
(25,[(0,4),(0,5),(1,5),(2,5)]),
(23,[(0,6),(0,7),(1,6),(1,7)]),
(11,[(0,8),(1,8)]),
(24,[(1,0),(2,0),(3,0),(4,0)]),
(17,[(1,1),(2,1)]),
(10,[(1,2),(1,3),(2,3)]),
(12,[(1,4),(2,4)]),
(14,[(2,6),(2,7),(3,6),(3,7)]),
(9,[(2,8),(3,8)]),
(14,[(2,2),(3,2)]),
(6,[(3,1),(4,1)]),
(16,[(3,3),(4,2),(4,3)]),
(19,[(3,4),(3,5),(4,4),(5,3),(5,4)]),
(19,[(4,5),(4,6),(5,5)]),
(14,[(4,7),(5,7)]),
(22,[(4,8),(5,8),(6,8),(7,8)]),
(9,[(5,0),(6,0)]),
(19,[(5,1),(5,2),(6,1),(6,2)]),
(5,[(5,6),(6,6)]),
(26,[(6,3),(7,3),(8,3),(8,4)]),
(6,[(6,4),(7,4)]),
(14,[(6,5),(7,5),(7,6)]),
(9,[(6,7),(7,7)]),
(10,[(7,0),(8,0)]),
(19,[(7,1),(7,2),(8,1),(8,2)]),
(12,[(8,5),(8,6)]),
(9,[(8,7),(8,8)]),
]

# 创建cage_map,将每个单元格映射到它的笼子
cage_map = {}
for cage_sum, cells in cages:
for cell in cells:
cage_map[cell] = (cage_sum, cells)

def print_sudoku(board):
"""打印数独"""
for i in range(N):
if i % 3 == 0 and i != 0:
print("-" * 22)
for j in range(N):
if j % 3 == 0 and j != 0:
print(" |", end="")
print(f"{board[i][j]:2d}", end="")
print()
print()

def is_valid(board, row, col, num):
"""检查在指定位置放置数字是否合法"""
# 检查行
for x in range(N):
if board[row][x] == num:
return False

# 检查列
for x in range(N):
if board[x][col] == num:
return False

# 检查3x3方块
start_row, start_col = 3 * (row // 3), 3 * (col // 3)
for i in range(3):
for j in range(3):
if board[start_row + i][start_col + j] == num:
return False

# 检查笼子约束
cage_sum, cage_cells = cage_map[(row, col)]
current_sum = num

# 计算当前笼子中已经填充的数字之和
for cell_row, cell_col in cage_cells:
if board[cell_row][cell_col] != 0:
current_sum += board[cell_row][cell_col]

# 如果已填充的数字之和超过了笼子的和,则不合法
if current_sum > cage_sum:
return False

# 如果这是最后一个未填充的单元格,检查总和是否正好等于笼子的和
remaining_cells = sum(1 for r, c in cage_cells if board[r][c] == 0)
if remaining_cells == 1 and current_sum != cage_sum:
return False

return True

def find_smallest_empty_cage(board, cage_map):
"""找到具有最小可能值的空单元格(用于优化)"""
for i in range(N):
for j in range(N):
if board[i][j] == 0:
return (i, j)
return None

def solve_killer_sudoku(board, cage_map):
"""使用回溯法解决killer数独"""
# 找到一个空位置
empty_pos = find_smallest_empty_cage(board, cage_map)

if not empty_pos:
return True # 没有空位置,问题已解决

row, col = empty_pos

# 尝试填入1-9之间的数字
for num in range(1, N+1):
if is_valid(board, row, col, num):
# 如果当前位置有效,则尝试填入
board[row][col] = num

# 递归尝试解决剩余部分
if solve_killer_sudoku(board, cage_map):
return True

# 如果当前数字导致后面无解,回溯
board[row][col] = 0

# 尝试了1-9都不行,返回False
return False

# 解决 Killer Sudoku
solution = [row[:] for row in sudoku]
if solve_killer_sudoku(solution, cage_map):
print_sudoku(solution)
else:
print("无解")

运行即可得到输出

1
2
3
4
5
6
7
8
9
10
11
 2 4 1 | 5 7 3 | 9 8 6
3 9 7 | 1 8 6 | 2 4 5
6 8 5 | 2 4 9 | 3 1 7
----------------------
8 5 9 | 4 6 1 | 7 3 2
7 1 3 | 9 2 8 | 6 5 4
4 6 2 | 7 3 5 | 1 9 8
----------------------
5 3 8 | 6 1 2 | 4 7 9
9 7 6 | 3 5 4 | 8 2 1
1 2 4 | 8 9 7 | 5 6 3

提取出主对角线上的数字 295425423,根据试练二给出的提示用 “$” 替换 “4”(谐音 “死”),得到解压密码 295$25$23

解压得到的葵花宝典.txt 内容如下:

1
DJ?ELtbo`0+o8F0Eb2G9dPN

Rot47->Rot13 解码得到 flag:

sdpcctf2025-2

1
flag{R3@1_M@th_g3niu5!}

small_challenge

Challenge

看了半天,没有收获?其实亦有收获。

Solution

解压得到

sdpcctf2025-3

binwalk 提取得到一个压缩包,里面包含压缩包 flag.zip 和下面这张图片

sdpcctf2025-4

用 StegSolver 的 Image Combiner 将两张图 XOR 处理,得到下面这个 DataMatrix 条码

sdpcctf2025-5

在线阅读 Data Matrix 条码扫码得到:

1
<E:8E?W^Z<=tEZ)=lP6n>;.Tg>q@+!/6=B)/6_%hLg*.rH<gLN

base85 解码得到:

1
UV!W_X_YZ,U,Y∈[0,9], V,W,X,Z∈[A,z]

这就是压缩包密码的范围

先用 John the Ripper 提出哈希

1
zip2john flag.zip > hash.txt
1
flag.zip/flag.txt:$pkzip2$1*1*2*0*26*1c*405453ef*0*26*8*26*4054*5c53*c8ca4c0435c8b2f6eeb7fe58830b8956e16b669b8c970b8086fea94f45902d111e440e655857*$/pkzip2$:flag.txt:flag.zip::flag.zip

前后删一下把它修改成 hashcat 能识别的格式

1
$pkzip2$1*1*2*0*26*1c*405453ef*0*26*8*26*4054*5c53*c8ca4c0435c8b2f6eeb7fe58830b8956e16b669b8c970b8086fea94f45902d111e440e655857

然后用 hashcat 进行掩码攻击

1
hashcat -a 3 -m 17200 --custom-charset1=?d --custom-charset2=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "hash.txt" ?1?2!?2_?2_?1?2

不到 1 分钟就解出来了

1
$pkzip2$1*1*2*0*26*1c*405453ef*0*26*8*26*4054*5c53*c8ca4c0435c8b2f6eeb7fe58830b8956e16b669b8c970b8086fea94f45902d111e440e655857*$/pkzip2$:9h!Y_a_8D

因此解压密码就是 9h!Y_a_8D,解压得到 flag

1
flag{It3_s0_3@syIlIlIIlIllI}