# VNCTF2026 出题笔记

很高兴能在 VNCTF2026 与大家见面,希望大家都能够玩的开心,有所收获

Ciallo~(∠・ω< )⌒★

# delicious obf

打开程序后根据字符串定位到主要函数,根据功能重命名函数

其中 check 函数就是主要的 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
_Z11check_debugv:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR -8[rbp], 0
mov rax, gs:[0x60]
movzx eax, byte ptr [rax+0x02]
mov edx, eax
mov DWORD PTR -8[rbp], edx
cmp DWORD PTR -8[rbp], 0
jne .L10
mov DWORD PTR -4[rbp], 80
jmp .L11
.L12:
mov eax, DWORD PTR -4[rbp]
cdqe
lea rdx, padding[rip]
add rax, rdx
movzx eax, BYTE PTR [rax]
xor eax, 16
mov ecx, eax
mov eax, DWORD PTR -4[rbp]
cdqe
lea rdx, padding[rip]
add rax, rdx
mov edx, ecx
mov BYTE PTR [rax], dl
add DWORD PTR -4[rbp], 1
.L11:
cmp DWORD PTR -4[rbp], 95
jle .L12
.L10:
mov eax, 0
leave
ret

将其混淆后

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
_Z11check_debugv:
lea r10, [rip + lable1_pad]
mov r11d, 0x5653D986
xor r11d, 0x5653D982
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable18_pad:
.byte 0x64,0x93,0x83,0xB0
mov ecx, eax
lea r10, [rip + lable19_pad]
mov r11d, 0xC671D7D8
xor r11d, 0xC671D7DC
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable22_pad:
.byte 0x1F,0xD3,0x15,0xF4
add rax, rdx
lea r10, [rip + lable23_pad]
mov r11d, 0x3C55B4B5
xor r11d, 0x3C55B4B1
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable2_pad:
.byte 0xFB,0xF3,0xBE,0xCF
mov rbp, rsp
lea r10, [rip + lable3_pad]
mov r11d, 0xE6B33F96
xor r11d, 0xE6B33F92
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable16_pad:
.byte 0xF2,0xE7,0x71,0x5B
movzx eax, BYTE PTR [rax]
lea r10, [rip + lable17_pad]
mov r11d, 0xA5DC377A
xor r11d, 0xA5DC377E
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable11_pad:
.byte 0x1C,0x49,0x35,0xD9
jmp .L11
.L12:
lea r10, [rip + lable12_pad]
mov r11d, 0x80454DE7
xor r11d, 0x80454DE3
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable20_pad:
.byte 0x20,0xE2,0xF6,0x44
cdqe
lea r10, [rip + lable21_pad]
mov r11d, 0x1BCA8E7C
xor r11d, 0x1BCA8E78
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable17_pad:
.byte 0x64,0x06,0xCA,0x20
xor eax, 16
lea r10, [rip + lable18_pad]
mov r11d, 0xDA5675FD
xor r11d, 0xDA5675F9
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable26_pad:
.byte 0x01,0xFC,0x8F,0x6D
popfq
jle .L12
.L10:
lea r10, [rip + lable27_pad]
mov r11d, 0xC41A2ADD
xor r11d, 0xC41A2AD9
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable3_pad:
.byte 0x9D,0xB6,0x00,0xEA
sub rsp, 16
lea r10, [rip + lable4_pad]
mov r11d, 0x23E8D05A
xor r11d, 0x23E8D05E
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable12_pad:
.byte 0x83,0x36,0xEC,0x01
mov eax, DWORD PTR -4[rbp]
lea r10, [rip + lable13_pad]
mov r11d, 0x6D1CB5A0
xor r11d, 0x6D1CB5A4
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable10_pad:
.byte 0x2C,0xEE,0xD7,0x18
mov DWORD PTR -4[rbp], 80
lea r10, [rip + lable11_pad]
mov r11d, 0x8D8D96CF
xor r11d, 0x8D8D96CB
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable24_pad:
.byte 0xC8,0x4C,0x7E,0x55
mov BYTE PTR [rax], dl
lea r10, [rip + lable25_pad]
mov r11d, 0x3D410CFE
xor r11d, 0x3D410CFA
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable4_pad:
.byte 0x96,0xDC,0xBA,0x3F
mov DWORD PTR -8[rbp], 0
mov rax, gs:[0x60]
lea r10, [rip + lable5_pad]
mov r11d, 0x427AB61B
xor r11d, 0x427AB61F
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable19_pad:
.byte 0x9D,0x38,0x34,0x2F
mov eax, DWORD PTR -4[rbp]
lea r10, [rip + lable20_pad]
mov r11d, 0xCD9CCB93
xor r11d, 0xCD9CCB97
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable14_pad:
.byte 0x92,0x53,0x9D,0xB9
lea rdx, padding[rip]
lea r10, [rip + lable15_pad]
mov r11d, 0xEC3E8EE3
xor r11d, 0xEC3E8EE7
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable9_pad:
.byte 0xB1,0xA6,0x91,0x63
popfq
jne .L10
lea r10, [rip + lable10_pad]
mov r11d, 0xD3937C0D
xor r11d, 0xD3937C09
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable29_pad:
.byte 0xE2,0x8F,0x1F,0x2B
ret
lable25_pad:
.byte 0x91,0xB5,0x1C,0x66
add DWORD PTR -4[rbp], 1
.L11:
cmp DWORD PTR -4[rbp], 95
pushfq
lea r10, [rip + lable26_pad]
mov r11d, 0xA6E7F96F
xor r11d, 0xA6E7F96B
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable27_pad:
.byte 0x49,0x3E,0x11,0x83
mov eax, 0
lea r10, [rip + lable28_pad]
mov r11d, 0x9C7D0DE0
xor r11d, 0x9C7D0DE4
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable13_pad:
.byte 0xE2,0x43,0x7F,0x74
cdqe
lea r10, [rip + lable14_pad]
mov r11d, 0x48D6290B
xor r11d, 0x48D6290F
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable8_pad:
.byte 0x1D,0x0D,0x9E,0xCA
mov DWORD PTR -8[rbp], edx
cmp DWORD PTR -8[rbp], 0
pushfq
lea r10, [rip + lable9_pad]
mov r11d, 0x6EF3B240
xor r11d, 0x6EF3B244
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable23_pad:
.byte 0x1A,0x20,0xB8,0x41
mov edx, ecx
lea r10, [rip + lable24_pad]
mov r11d, 0x865EBC90
xor r11d, 0x865EBC94
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable28_pad:
.byte 0x78,0xA1,0x1A,0x8D
leave
lea r10, [rip + lable29_pad]
mov r11d, 0x4E68C223
xor r11d, 0x4E68C227
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable15_pad:
.byte 0x7B,0xDA,0x5C,0x95
add rax, rdx
lea r10, [rip + lable16_pad]
mov r11d, 0x94730946
xor r11d, 0x94730942
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable5_pad:
.byte 0x7A,0x9D,0x9C,0xAC
nop
lea r10, [rip + lable6_pad]
mov r11d, 0x3081A06C
xor r11d, 0x3081A068
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable7_pad:
.byte 0x33,0x43,0xC4,0x87
mov edx, eax
lea r10, [rip + lable8_pad]
mov r11d, 0x62236227
xor r11d, 0x62236223
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable1_pad:
.byte 0x48,0x70,0xDC,0x95
push rbp
lea r10, [rip + lable2_pad]
mov r11d, 0xB5707DFD
xor r11d, 0xB5707DF9
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable6_pad:
.byte 0x9A,0x6A,0x2A,0xEA
movzx eax, byte ptr [rax+0x02]
lea r10, [rip + lable7_pad]
mov r11d, 0x47352D23
xor r11d, 0x47352D27
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret
lable21_pad:
.byte 0xE5,0x3D,0x90,0x9D
lea rdx, padding[rip]
lea r10, [rip + lable22_pad]
mov r11d, 0xBE8DD235
xor r11d, 0xBE8DD231
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret

可以看到混淆后的代码是一块一块的,我们以其中具体几块来分析

1
2
3
4
5
6
7
8
9
_Z11check_debugv:
lea r10, [rip + lable1_pad]
mov r11d, 0x5653D986
xor r11d, 0x5653D982
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret

rip 是指令指针寄存器,指向下一条要执行的汇编指令的地址,加上 lable1_pad 实际就是加了一个偏移,假设原本 rip 的值为 0x4000001 ,加上一个偏移 0x1145,那么接下来执行的汇编指令地址就会跳到 0x401146

1
2
3
4
5
0x400000         push rax         //当前执行到的汇编
0x400001 push rbx // rip所指汇编 也就是即将执行的汇编
......
0x401146 call $5 // 给原本的rip加上0x1145,就会跳到这个地方继续执行
......

所以混淆块中第一行其实是把 rip + lable1_pad 这个地址存放到了 r10 寄存器中

接下来两行执行了异或操作,0x5653D986 ^ 0x5653D982 结果为 4,将 r10 中存放的地址又加上 4(按刚刚的例子,现在的地址 = rip + 0x1145 + 0x4)

下一行是字节码。 E8call 指令的字节码,后面跟的 DWORD 值是偏移,现在偏移为 0 ,那么这行汇编代码应该为 call $5 ,直接继续执行下一行汇编,因为 call 指令同时会把下一行的地址压栈,所以将栈指针上移 8,清除压入的地址,再将 r10 存放的地址压栈,此时栈顶为 r10,最后 ret 就会跳转到 r10 存放的地址。

接下来看下一个块

1
2
3
4
5
6
7
8
9
10
11
lable1_pad:
.byte 0x48,0x70,0xDC,0x95
push rbp
lea r10, [rip + lable2_pad]
mov r11d, 0xB5707DFD
xor r11d, 0xB5707DF9
add r10, r11
.byte 0xE8, 0x00, 0x00, 0x00, 0x00
add rsp, 8
push r10
ret

rip 加上了偏移 lable1_pad ,应该跳转到 lable1_pad 处,开头的四个字节是垃圾字节,又加上的 4 正好就能跳过这部分垃圾字节,执行真正有用的汇编 push rbp ,接下来又是重复的计算跳转地址,和刚刚相同。

如何去除这种混淆呢?

刚才我们说到,r10 的地址为 rip + offest ,图中 r10 为 0x1400018F6,rip 为 0x14000149C,相减,得到的偏移为 0x45A,观察字节码,可以发现正好是这行汇编的后四个,异或的值固定为 4,所以跳转地址的计算公式 = 后四个 dword 的值 + 4 + 7(这条指令本身的长度)

把从 lea 这条指令到 retn 之间的所有指令全部 nop 掉,改为 jmp addr ,就可以有效去混淆

注意实际 patch 时偏移要减 5,因为这条 jmp 指令长度为 5

程序中还存在另一种混淆

jzjnz 跳转,最后其实还是 jmp 到 r10 的地址,和前一种区别不大,也是计算出地址,nop 掉无用指令改为 jmp 即可

去混淆脚本如下

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
#include <idc.idc>

static NopCode(Addr, Length)
{
auto i;
for (i = 0; i < Length; i++)
{
PatchByte(Addr + i, 0x90);
}
}


static main()
{
auto seg;
// 遍历所有段
for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg))
{
// 获取段名称
auto seg_name = get_segm_name(seg);
msg(seg_name + "\n");
if (seg_name == ".text")
{
// 从当前段头地址开始遍历
auto current_addr = seg;
// 结束地址
auto end_addr = current_addr + 0x10000;
while (current_addr != BADADDR && current_addr < end_addr)
{
auto current_byte = Byte(current_addr);
auto op = Word(current_addr + 1);
auto next = Word(current_addr + 7);
auto call = Byte(current_addr + 23);
auto jz = Byte(current_addr + 23);
// call + retn
//设置一些条件判断是否为我们想处理的地方
if(current_byte == 0x4C && op == 0x158D && next == 0xBB41 && call == 0xE8)
{
auto offest1 = Dword(current_addr + 3);
auto offest_neg = Byte(current_addr + 6);
auto addr = offest1 + 4 + 7 - 5;
auto gar_code = offest1 + 7 + current_addr; // 计算出垃圾指令的位置
if(offest_neg == 0xFF)
{
gar_code = gar_code - 0x100000000;
}
NopCode(current_addr,35); // nop掉从lea开头到retn结尾的所有代码
NopCode(gar_code,4); // nop掉四字节垃圾指令
PatchByte(current_addr,0xE9); // 改为jmp指令
PatchDword(current_addr + 1,addr); // jmp跳转的偏移
}

// jz jnz
if(current_byte == 0x4C && op == 0x158D && next == 0xBB41 && jz == 0x74)
{
offest1 = Dword(current_addr + 3);
offest_neg = Byte(current_addr + 6);
addr = offest1 + 4 + 7 - 5;
gar_code = offest1 + 7 + current_addr;
if(offest_neg == 0xFF)
{
gar_code = gar_code - 0x100000000;
}
//msg("code : %X\n",gar_code);
NopCode(current_addr,30);
NopCode(gar_code,4);
PatchByte(current_addr,0xE9);
PatchDword(current_addr + 1,addr);
}
current_addr = current_addr + 1;
}

}
}
}

去完之后可能发现 ida 没能正常识别函数,这个时候我们就要手动的整理一下

这里有一个函数 sub_1400054BB ,点进去是由 jmp 连接的代码块。

假如出现 ida 把代码块识别成函数的情况

那么先 Edit -> Functions -> Delete function 删除函数,再选中这些汇编 Append function tail 把他们并入到函数 sub_1400054BB

去混淆后效果如下

去的非常干净了,和未混淆的函数反编译效果一样

# 逻辑分析

去除完混淆就可以分析这个题目流程了

本来在 type_change 和 memcmp 函数中间加了 call + retn 花,但是去完混淆之后这个花也被一起去除了。

细心的师傅可以发现,tea 加密传入的参数为从 input 复制过去的 buf1,input 本身未受影响,最后返回的也是 loc_140001977 函数的值而非 memcmp 函数的值

但是这个 loc_140001977 函数为乱码,看起来也不是我们之前说过的任何一种混淆,怎么办呢

观察到这个函数被另一个神秘函数交叉引用过,我们也可以逆向思考一下,别的正常函数都不加混淆,只有一些函数加了混淆,证明这些函数就是出题人自己写的妙妙小函数

发现如果有 int 3 中断,就对 loc_140001977 进行 smc,这里其实可以起一个反调试的作用,因为软件断点的原理就是把断点处汇编的第一个字节改为 0xCC。

之前一共找到了两处 int 3,不过不知道有几处 int 3 也没关系,直接调试看就可以,还原函数的时候应当能发现有反调试。

那么下面这个看起来很像系统函数的字符串是什么,看一下源码

1
2
3
4
5
6
7
8
9
10
uint8_t padding[] = "GCC: (x86_64-win32-seh-rev0, Built by MinGW-Builds project) 14.2.";
uint8_t key[] = "welcome to vnctf";
......
if (!isdebug)
{
for(int i = 0x50; i < 0x50 + 16; i++)
{
*(uint8_t*)(padding + i) ^= 0x1919810;
}
}

这里其实是在隐藏 key 的交叉调用,这个字符串和 key 之间正好差了 0x50,对 (padding + 0x50) 处修改其实就是在对 key 修改。

最后调试得到加密

注意算法进行了魔改,sum 值没有在循环内重置,这里写解密脚本时要注意。key 直接调试得到即可

解密脚本如下

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 <iostream>
#include <windows.h>
#include <stdint.h>

int main()
{
uint32_t data[] = {0x738EA1B9, 0xF5B06584, 0xDCF952D5, 0x6FC28041, 0x1DA40CF1, 0x07572A62, 0xB4C49903, 0x9BA536D8};
uint32_t key[] = {0xF9B2917F, 0x2A9D0847, 0x0C874A13, 0xA0253AD3};
int sum = (32 * 0x61C88647) * 4;
for(int i = 6; i >= 0; i -= 2)
{
uint32_t A = data[i];
uint32_t B = data[i + 1];
for(int j = 0; j < 32; j++)
{
A += (B + ((16 * B) ^ (B >> 5))) ^ (key[sum & 3] + sum);
sum -= 0x61C88647;
B += (A + ((16 * A) ^ (A >> 5))) ^ (key[(sum >> 11) & 3] + sum);
}
data[i] = A;
data[i + 1] = B;
}

for(int i = 0; i < 8; i++)
{
for(int j = 0; j < 4; j++)
{
printf("%c",(data[i] >> (j * 8)) & 0xFF);
}
}

return 0;
}

// VNCTF{N0w_Y0u_Kn0w_SMC_4nd_@bf!}

# ez_maze

放工具查壳可以发现是魔改 upx

用 xdbg 打开

发现入口处加花,单步

这里可以看到一个非常像入口点的地方,只是没有 push,因为我把它们 nop 掉了,但是依旧可以用 rsp 大法脱壳

对于 mfc 逆向,可以使用 xspy 来找控件函数

直接看 OnCommand 消息,有两个偏移,脱壳程序找到相应地址,在 0x1920 偏移处可以找到主要逻辑

是一个简单的迷宫,把算法同构下来找到最短路径即可,注意 wasd 键位有所改变

迷宫生成算法

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
void CMFCApplication2Dlg::create_map()
{
srand(100);

for (int y = 0; y < 20; y++) {
for (int x = 0; x < 20; x++) {
m_map[y][x] = 1;
}
}


int stackX[400];
int stackY[400];
int top = 0;

int cx = 0;
int cy = 0;
m_map[cy][cx] = 0;


stackX[top] = cx;
stackY[top] = cy;
top++;


int dirs[4][2] = { {2, 0}, {-2, 0}, {0, 2}, {0, -2} };

while (top > 0) {

cx = stackX[top - 1];
cy = stackY[top - 1];


int validNeighbors[4];
int vnCount = 0;

for (int i = 0; i < 4; i++) {
int nx = cx + dirs[i][0];
int ny = cy + dirs[i][1];

if (nx >= 0 && nx < 20 && ny >= 0 && ny < 20) {

if (m_map[ny][nx] == 1) {
validNeighbors[vnCount++] = i;
}
}
}

if (vnCount > 0) {

int choice = rand() % vnCount;
int dirIndex = validNeighbors[choice];

int nx = cx + dirs[dirIndex][0];
int ny = cy + dirs[dirIndex][1];
int wx = cx + dirs[dirIndex][0] / 2;
int wy = cy + dirs[dirIndex][1] / 2;
m_map[wy][wx] = 0;

m_map[ny][nx] = 0;

stackX[top] = nx;
stackY[top] = ny;
top++;
}
else {
top--;
}
}

m_map[19][19] = 0;

if (m_map[18][19] == 1) {
m_map[19][18] = 0;
}
else {
m_map[18][19] = 0;
}
}

迷宫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
S # . . . . . . . . . # . . . . . . . # 
* # # # # # # # # # . # . # # # . # . #
* * * * * * * * * # . . . # . . . # . #
# # # # # # # # * # . # # # . # # # . #
. . . . . . . # * # . # . # . . . # . #
# # . # . # # # * # . # . # # # . # # #
. . . # . # * * * # . . . . . # . . . #
. # . # # # * # # # # # . # # # # # . #
. # . # * * * # . . . # . # * * * # . #
. # . # * # # # # # . # . # * # * # . #
. # . # * * * * * # . . . # * # * # . #
. # # # # # # # * # # # # # * # * # . #
. . . . . . . # * * * * * * * # * * * #
. # # # # # . # # # # # # # # # # # * #
. . . # . . . # . . . . . . . . . # * #
# # . # . # # # . # # # # # # # . # * #
. # . # . # . . . # . . . . . # . # * #
. # . # . # . # # # . # # # . # . # * #
. . . # . . . . . . . # . . . # . . * #
# # # # # # # # # # # # # # # # # # * E