re-迷宫题学习

re中的迷宫问题有以下特点:

  • 在内存中布置一张 "地图"
  • 将用户输入限制在少数几个字符范围内.
  • 一般只有一个迷宫入口和一个迷宫出口

布置的地图可以由可显字符 (比如#*)组合而成 (这非常明显, 查看字符串基本就知道这是个迷宫题了.), 也可以单纯用不可显的十六进制值进行表示. 可以将地图直接组成一条非常长的字符串, 或是一行一行分开布置. 如果是一行一行分开布置的话, 因为迷宫一般都会比较大, 所以用于按行(注意, 布置并非按顺序布置, 每行都对应一个具体的行号, 你需要确定行号才能还原迷宫地图) 布置迷宫的函数会明显重复多次.

而被限制的字符通常会是一些方便记忆的组合 (不是也没办法), 比如w/s/a/d, h/j/k/l, l/r/u/d这样的类似组合. 当然各个键具体的操作需要经过分析判断 (像那种只用一条字符串表示迷宫的, 就可以用t键表示向右移动12个字符这样). 对于二维的地图, 一般作者都会设置一个X坐标和一个Y坐标用于保存当前位置. 我们也可以根据这个特点来入手分析.

一般情况下, 迷宫是只有 1 个入口和 1 个出口, 像入口在最左上角(0, 0)位置, 而出口在最右下角(max_X, max_Y)处. 但也有可能是出口在迷宫的正中心, 用一个Y字符表示等等. 解答迷宫题的条件也是需要根据具体情况判断的.

当然迷宫的走法可能不止 1 条, 也有情况是有多条走法, 但是要求某一个走法比如说代价最小. 那么这就可以变相为一个算法问题.

DFS和BFS算法讲解

深度优先遍历(Depth Fist Search)

什么是深度优先,用树来看就是从一个结点开始,沿着其中一条支路,一直搜索下去直到尽头;然后再原路返回起点,再找另一条路不断遍历下去。
用迷宫来讲的话,就是从起点开始,不断向四周的格子走,不考虑回头的话,总有到尽头的时候(终点或是一堵墙)。到尽头后,回到起点,再沿着没有走过的路走一次,不断重复这个过程。

深度优先算法有俩种实现方法,一种是利用递归实现,一种是非递归实现。

广度优先遍历(Breath First Search)

广度优先遍历指的是从图的一个未遍历的结点出发,先遍历这个点的相邻结点,再依次遍历每个相邻节点的相邻节点。
在迷宫的例子里,就是说我们按照走1步能到达的位置,走2步能到达的位置…这样的顺序逐一搜索,直到不再有能到达的位置。

深度优先利用的是栈实现,广度优先利用的是队列实现。

具体代码实现部分可以看[算法]搜索初步: 从迷宫最短路问题来理解DFS和BFS · Issue #14 · Locietta/blogs · GitHub
图文解析可以看图文详解 DFS 和 BFS

步骤和代码实现

maze

NSSCTF的 [SWPUCTF 2021 新生赛]老鼠走迷宫为例子,用深度优先遍历DFS。

maze = [[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],[1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1],[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],[1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1],[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],[1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],[1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1],[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],[1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],[1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1],[1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1],[1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],[1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1],[1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1]]

这里有一个地方进行了修改,最后一行的倒数第二个位置原本是0,我手动修改成了2;原题有明确该坐标是出口。maze[0][1]处是入口。

具体操作

先写从当前位置前往下一个位置的限制条件;(例如,通行的要求,是否遇到墙;走到终点了)

# 添加限制条件
def check(map, x, y):if (x >= 0) and (x <= 24) and (y >= 0) and (y <= 24):    # 这一行检查(x,y)是否在有效范围内;return (map[x][y] != 1) and ((map[x][y] == 0) or (map[x][y] == 2)) 	# 这里是如果坐标在地图范围内,就检测该坐标是不是障碍物;是不是可以前进的道路或者终点;else:return False

写出当前位置四周的情况,并存入一个列表里。该列表中的每一项包含三个信息(x坐标,y坐标,前进方向)

def gen_nex(map, x, y):all_dir = []if check(map, x - 1, y):all_dir.append((x - 1, y, 'w'))if check(map, x + 1, y):all_dir.append((x + 1, y, 's'))if check(map, x, y - 1):all_dir.append((x, y - 1, 'a'))if check(map, x, y + 1):all_dir.append((x, y + 1, 'd'))return all_dir# all_dir是一个列表,用于存储下一个可能的坐标和前进方向的信息。每个元素都是一个包含三个值的元组,格式如下:(x, y, direction)

写出DFS用递归查找路线

  • ①第一步先写判断dfs成功的条件
  • ②将访问过的值设置成其它特殊值,即我们走过一遍的地方不能再走,不然会出bug,俩个点来回横跳。
  • ③进行递归dfs调用
def check_success(map, x, y):if map[x][y] == 2:return Trueelse:return Falsedef dfs(maze, x, y, path):map = maze.copy()    # 这里用将maze复制给map,避免修改掉原地图。if map[x][y] != 2:map[x][y] = 1if check_success(map, x, y):print(path)return Truenext_point = gen_nex(map, x, y)for n in next_point:pathn = path + n[2]       # 将all_dir列表中的元组的第三个值,即方向传给pathndfs(map, n[0], n[1], pathn)        # 这里开始递归 用all_dir的元组第一二个值和pathn作为参数,进行当前位置的又一次深度优先遍历。

最后再加个尾巴

output = ""
dfs(maze, 0, 1, output)

直接运行

实战分析

[SWPUCTF 2021 新生赛]老鼠走迷宫

这一题是pyinstaller类的题目,前面是简单的python逆向,这里不再进行分析,直接默认分析迷宫步骤开始

在maze下面添加一个for循环,输出maze,会报错,把最下面的return none直接删了,这不是重点不要紧
修改之后的代码,可以直接输出maze

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.7import random
import msvcrt
(row, col) = (12, 12)
(i, j) = (0, 0)
maze = [[1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1],[1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1],[1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1],[1,0,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,0,1],[1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0,0,1],[1,1,1,0,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1],[1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1],[1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1],[1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1],[1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1],[1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1],[1,0,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1],[1,0,0,0,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1],[1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,0,1],[1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1],[1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1,0,1],[1,0,1,0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1],[1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1],[1,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1],[1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1],[1,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1],[1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1],[1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1]]
for row in maze:print(row)
print('Mice walk in a maze: wasd to move,q to quit')
print("flag is the shortest path's md5,example:if the shortest path is wasdsdw,the flag is md5('wasdsdw')")
(i, j) = (0, 1)
n = 0
while i == row * 2 and j == col * 2 - 1:print('ohhhh!!!!you did it')breakprint('your position:({},{})'.format(i, j))inp = msvcrt.getch()n += 1ti = itj = jif b'a' == inp and i > 0:tj -= 1elif b'w' == inp and j > 0:ti -= 1elif b's' == inp and j < row * 2:ti += 1elif b'd' == inp and i < col * 2:tj += 1elif b'q' == inp:exit('bye!!')else:print('What???')if maze[ti][tj] == 1:print(random.choice(['no wayy!!',"it's wall",'nop']))continueelif maze[ti][tj] == 0:print(random.choice(['nice!!','yeah!!','Go on']))i = tij = tj

接下来用上面的的DFS,这里不再重复

dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]  # 当前位置四个方向的偏移量
path = []  # 存找到的路径def mark(maze, pos):  # 给迷宫maze的位置pos标"2"表示“倒过了”maze[pos[0]][pos[1]] = 2def passable(maze, pos):  # 检查迷宫maze的位置pos是否可通行return maze[pos[0]][pos[1]] == 0def find_path(maze, pos, end):mark(maze, pos)if pos == end:print(pos, end=" ")  # 已到达出口,输出这个位置。成功结束path.append(pos)return Truefor i in range(4):  # 否则按四个方向顺序检查nextp = pos[0] + dirs[i][0], pos[1] + dirs[i][1]# 考虑下一个可能方向if passable(maze, nextp):  # 不可行的相邻位置不管if find_path(maze, nextp, end):  # 如果从nextp可达出口,输出这个位置,成功结束print(pos, end=" ")path.append(pos)return Truereturn Falsedef see_path(maze, path):  # 使寻找到的路径可视化for i, p in enumerate(path):if i == 0:maze[p[0]][p[1]] = "E"elif i == len(path) - 1:maze[p[0]][p[1]] = "S"else:maze[p[0]][p[1]] = 3print("\n")for r in maze:for c in r:if c == 3:print('\033[0;31m' + "*" + " " + '\033[0m', end="")elif c == "S" or c == "E":print('\033[0;34m' + c + " " + '\033[0m', end="")elif c == 2:print('\033[0;32m' + "#" + " " + '\033[0m', end="")elif c == 1:print('\033[0;;40m' + " " * 2 + '\033[0m', end="")else:print(" " * 2, end="")print()if __name__ == '__main__':maze = [[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],[1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1],[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],[1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1],[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],[1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],[1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1],[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],[1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],[1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1],[1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1],[1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],[1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1],[1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1],[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1]]start = (0, 1)end = (24, 23)find_path(maze, start, end)see_path(maze, path)

NSSCTF [GDOUCTF 2023]润!

UPX壳但是是魔改过的upx壳,将FUK都改成UPX就好;再看就是正常的upx壳了,用upx -d

之后用IDA分析就好,直接反汇编来看主函数

init是创建迷宫, 让我们输入长度为31的字符串,moving是在迷宫中的移动,这里的511是个hint

init点进去看看

这里具体迷宫的生成具体可以不用在意,可以明显看出这是一个 8 * 8大小的迷宫;

moving这里是三维的,还有上下楼这种操作。注意看:在case ‘u’:处又调用了一次init创建了一个迷宫,还有layer也是个hint,layer是层的意思,这里就是有八层。 8 * 8 * 8 = 512 ;即意味着我们的终点就是左下角的位置,511;起点是一楼的左上角。我觉得难点就在这了,后面就按部就班。

哦还有,需要动态调试,输入uuuuuuuuuuuuuuuuuuuu就好,连续创建七次迷宫,将迷宫copy出来就好,4015CE下断点

x表示楼层,y是w和s表示行,z是列。

maze = [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2]# maze[x * 64 + y * 8 + z]
def check_point_valid(map, x, y, z):if (x >= 0) and (x <= 7) and (y >= 0) and (y <= 7) and (z >= 0) and (z <= 7):return (map[x * 64 + y * 8 + z] != 1) and ((map[x * 64 + y * 8 + z] == 0) or (map[x * 64 + y * 8 + z] == 2))else:return Falsedef gen_nex(map, x, y, z):all_dir = []if check_point_valid(map, x - 1, y, z):all_dir.append((x - 1, y, z, 'n'))if check_point_valid(map, x + 1, y, z):all_dir.append((x + 1, y, z, 'u'))if check_point_valid(map, x, y - 1, z):all_dir.append((x, y - 1, z, 'w'))if check_point_valid(map, x, y + 1, z):all_dir.append((x, y + 1, z, 's'))if check_point_valid(map, x, y, z - 1):all_dir.append((x, y, z - 1, 'a'))if check_point_valid(map, x, y, z + 1):all_dir.append((x, y, z + 1, 'd'))return all_dirdef check_success(map, x, y, z):if map[x * 64 + y * 8 + z] == 2:return Trueelse:return Falsedef dfs(mapb, x, y, z, path):map = mapb.copy()if map[x * 64 + y * 8 + z] != 2:map[x * 64 + y * 8 + z] = 1if check_success(map, x, y, z):print(path)return Truenext_point = gen_nex(map, x, y, z)for n in next_point:pathn = path + n[3]dfs(map, n[0], n[1], n[2], pathn)outpus = ""
dfs(maze, 0, 0, 0,  outpus)

BUUCTF:[HDCTF2019]Maze

前面正常的upx32,脱壳

进去发现函数不完整,解析也出现错误,分析一下有花指令的干扰,直接去花

花指令,nop掉他!这里call直接nop就好

nop完后,全选红色的地址用p键得到main函数,再次反编译

现在就是找地图了,查看字符串

没有找到迷宫的规模在哪里,结合一共有70个字符,而且一共走了14步,asc_408078 = 5,dword_40807C = -4,可以大概猜出 7 * 10 的迷宫大小。

可知起始:408078=7 40807c=0(即+(7,0)),结束:408078=5 40807c=-4(F(5,-4)),画出地图。(a:左,s:下,d:右,w:上)

EXP:

map = ['*', '*', '*', '*', '*', '*', '*', '+', '*', '*', '*', '*', '*', '*', '*', '*', '*', ' ', '*', '*', '*', '*', '*', '*', ' ', ' ', ' ', ' ', '*', '*', '*', '*', ' ', ' ', ' ', '*', '*', '*', '*', '*', '*', '*', ' ', '*', '*', 'F', '*', '*', '*', '*', '*', '*', ' ', ' ', ' ', ' ', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*']# 添加限制条件
def check(map, x, y):if (x >= 0) and (x <= 7) and (y >= 0) and (y <= 9):    # 这一行检查(x,y)是否在有效范围内;return (map[10 * x + y] != '*') and ((map[10 * x + y] == ' ') or (map[10 * x + y] == 'F')) 	# 这里是如果坐标在地图范围内,就检测该坐标是不是障碍物;是不是可以前进的道路或者终点;else:return Falsedef gen_nex(map, x, y):all_dir = []if check(map, x - 1, y):all_dir.append((x - 1, y, 'w'))if check(map, x + 1, y):all_dir.append((x + 1, y, 's'))if check(map, x, y - 1):all_dir.append((x, y - 1, 'a'))if check(map, x, y + 1):all_dir.append((x, y + 1, 'd'))return all_dir
# all_dir是一个列表,用于存储下一个可能的坐标和前进方向的信息。每个元素都是一个包含三个值的元组,格式如下:(x, y, direction)def check_success(map, x, y):if map[10 * x + y] == 'F':return Trueelse:return Falsedef dfs(maze, x, y, path):map = maze.copy()    # 这里用将maze复制给map,避免修改掉原地图。if map[10 * x + y] != 'F':map[10 * x + y] = 1if check_success(map, x, y):print(path)return Truenext_point = gen_nex(map, x, y)for n in next_point:pathn = path + n[2]       # 将all_dir列表中的元组的第三个值,即方向传给pathndfs(map, n[0], n[1], pathn)        # 这里开始递归 用all_dir的元组第一二个值和pathn作为参数,进行当前位置的又一次深度优先遍历。output = ""
dfs(map, 0, 7, output)

2019华南师大CTF新生赛maze

题目地址:https://github.com/scnu-sloth/hsctf-2019-freshmen

找走迷宫的规则和方法,点开check函数看到

分析可知:这是一个含14*12的迷宫;'*'是迷宫的墙;'#'是迷宫终点

d为向右走一步;s为向右13步,在本二维数组中为向左下方一步或向右13步;w为向右上方一步或向左13步;a为向左一步

exp,直接借用了师傅BSF的算法:

#include<bits/stdc++.h>
using namespace std;
char mp[12*15];
int vis[12*15];
int dir[4]={1,-1,13,-13};
char S[4]={'d','a','s','w'};
struct node
{int x;string s;
};
queue<node> Q;
void bfs()
{node tmp;tmp.x=15;tmp.s="";Q.push(tmp);while(!Q.empty()){node now=Q.front();Q.pop();vis[now.x]=1;if(mp[now.x]=='#'){cout<<"flag{"<<now.s<<"}"<<endl;return;}for(int k=0;k<4;k++){int ux=now.x+dir[k];if(ux<1||ux>168||mp[ux]=='X'||vis[ux]==1)continue;tmp.x=ux;tmp.s=now.s+S[k];Q.push(tmp);}}
}
int main()
{int t=0;for(int i=1;i<=12;i++){for(int j=1;j<=14;j++){cin>>mp[++t];}}memset(vis,0,sizeof(vis));bfs();return 0;
}

还有突然发现了169 个字符,不就是 13 的平方,发现了13*13也对,上下左右就出来了,直接画个图就可以手解

flag{sssssdsssddsdddwwdwwaaaw}

攻防世界新手区  NJUPT CTF 2017 maze

64位ELF文件,无壳,先扔入IDA中查看伪代码

分析代码:

  puts("Input flag:");scanf("%s", &input_flag, 0LL);if ( strlen(&input_flag) != 24 || strncmp(&input_flag, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )		//这里要求输入的flag是24个字符,且前5个和最后一个都确定了{
LABEL_22:puts("Wrong flag!");exit(-1);}v3 = 5LL;if ( strlen(&input_flag) - 1 > 5 )		 {while ( 1 ){singleflag = *(&input_flag + v3);         // 这里v3是从5开始递增的数,目的是从第5个字符开始判断是否符合下述条件v5 = 0;if ( singleflag > 78 )	//这里给个范围,ASCII码大于78的划为第一类{singleflag = (unsigned __int8)singleflag;if ( (unsigned __int8)singleflag == 'O' )//如果第一个取O{v6 = sub_400650((_DWORD *)&v9 + 1);   goto LABEL_14;}if ( singleflag == 'o' )//如果第一个取o{v6 = sub_400660((int *)&v9 + 1);      // 有符号32位高字节操作,r15寄存器goto LABEL_14;}}else{singleflag = (unsigned __int8)singleflag;if ( (unsigned __int8)singleflag == '.' )//如果取到.{v6 = sub_400670(&v9);                 // 无符号底字节32位操作,r14寄存器goto LABEL_14;}if ( singleflag == '0' ){v6 = sub_400680((int *)&v9);          // 有符号底字节32位,r14寄存器
LABEL_14:v5 = v6;goto LABEL_15;}}
LABEL_15:if ( !(unsigned __int8)sub_400690((__int64)asc_601060, SHIDWORD(v9), v9) )goto LABEL_22;if ( ++v3 >= strlen(&input_flag) - 1 )   //在flag范围内v3加1,对应前面singleflag取第6、7、8~个一个个比较{if ( v5 )		//如果flag取完了,且sub_这些函数没有返回flase,也就是没有越界,就可以判断是否抵达终点了break;
LABEL_20:v7 = "Wrong flag!";goto LABEL_21;}}}if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != '#' )	  //判断是否为#这个终点。goto LABEL_20;v7 = "Congratulations!";
LABEL_21:puts(v7);return 0LL;
}

由伪代码可见,这是个8*8的迷宫,有四个判断条件,分别进入了四个函数。
‘O’,‘o’,‘0’,’.'这四个字符分别控制不同方向
O左,o右,0下,.上
四个方向函数如下

bool __fastcall sub_400650(_DWORD *a1)//O
{int v1; // eaxv1 = (*a1)--;//向左一步return v1 > 0;//防止越界
}
bool __fastcall sub_400660(int *a1)//o
{int v1; // eaxv1 = *a1 + 1;//向右一步*a1 = v1;return v1 < 8;
}
bool __fastcall sub_400670(_DWORD *a1)//.
{int v1; // eaxv1 = (*a1)--;//向上一步return v1 > 0;
}
bool __fastcall sub_400680(int *a1)//0
{int v1; // eaxv1 = *a1 + 1;*a1 = v1;//向下一步return v1 < 8;
}

怎么分辨v9和v10各自控制的是上下还是左右呢?
点开sub_400690函数,看到

__int64 __fastcall sub_400690(__int64 a1, int a2, int a3)
{//a1是数组的首地址,a2是v10,a3是v9__int64 result; // raxresult = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);//a3乘以8表明v9在二维数组中表示上下LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';return result;
}

得知v9表示行,就是控制上下。v10表示列,就是控制左右。
而且只有‘ ’ 和#可以走,否则返回return 0。’*'是迷宫的墙。
写代码生成迷宫,0可走,X为墙。

找到类似迷宫的字符串

exp:

import numpy as np
q='nctf{'
h='}'
asc='  *******   *  **** * ****  * ***  *#  *** *** ***     *********'
mg=np.array(list(asc))
print(str(mg.reshape(int(len(asc)/8),8)).replace('\'',''))
# [[    * * * * * *]
#  [*       *     *]
#  [* * *   *   * *]
#  [* *     *   * *]
#  [*     * #     *]
#  [* *   * * *   *]
#  [* *           *]
#  [* * * * * * * *]]
mid_str='右下右右下下左下下下右右右右上上左左'.replace('上','.').replace('下','0').replace('左','O').replace('右','o')
print(q+mid_str+h)

BUUCTF:不一样的flag

拖进IDA查看main函数

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{char v3[29]; // [esp+17h] [ebp-35h] BYREFint v4; // [esp+34h] [ebp-18h]int v5; // [esp+38h] [ebp-14h] BYREFint i; // [esp+3Ch] [ebp-10h]_BYTE v7[12]; // [esp+40h] [ebp-Ch] BYREF__main();v4 = 0;strcpy(v3, "*11110100001010000101111#");//这就是迷宫while ( 1 ){puts("you can choose one action to execute");puts("1 up");puts("2 down");puts("3 left");printf("4 right\n:");//控制方向scanf("%d", &v5);if ( v5 == 2 ){++*(_DWORD *)&v3[25];}else if ( v5 > 2 ){if ( v5 == 3 ){--v4;}else{if ( v5 != 4 )
LABEL_13:exit(1);++v4;}}else{if ( v5 != 1 )goto LABEL_13;--*(_DWORD *)&v3[25];}for ( i = 0; i <= 1; ++i ){if ( *(int *)&v3[4 * i + 25] < 0 || *(int *)&v3[4 * i + 25] > 4 )exit(1);}if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == '1' )exit(1);//是个5*5的迷宫且1为墙if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == '#' ){//‘#’是迷宫终点puts("\nok, the order you enter is the flag!");exit(0);}}
}

显然是一道迷宫题。
1,2,3,4分别控制上下左右

走迷宫即可

参考资料:

迷宫问题 - CTF Wiki (ctf-wiki.org)

bugku上一道迷宫逆向题的分析 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

逆向迷宫题总结(持续更新) 2020华南师大CTF新生赛maze,攻防世界新手区:NJUPT CTF 2017,BUUCTF:不一样的flag_ctf lostmaze-CSDN博客

CTF-Reverse 迷宫地图类题目分析‘‘DFS和BFS算法‘‘(学习笔记)【详】_ctf 迷宫-CSDN博客【2021.12.28】ctf逆向中的迷宫问题(含exe及wp)_ctf迷宫-CSDN博客CTF-Reverse 迷宫地图类题目分析‘‘DFS和BFS算法‘‘(学习笔记)【详】_ctf 迷宫-CSDN博客

[算法]搜索初步: 从迷宫最短路问题来理解DFS和BFS · Issue #14 · Locietta/blogs (github.com)

【bugku】【ZSCTF】【迷宫RE】Take The Maze WriteUp - Reddest - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2803265.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【鸿蒙 HarmonyOS 4.0】UIAbility、页面及组件的生命周期

一、背景 主要梳理下鸿蒙系统开发中常用的生命周期 二、UIAbility组件 UIAbility组件是一种包含UI界面的应用组件&#xff0c;主要用于和用户交互。 UIAbility组件是系统调度的基本单元&#xff0c;为应用提供绘制界面的窗口&#xff1b;一个UIAbility组件中可以通过多个页…

【大厂AI课学习笔记NO.50】2.3深度学习开发任务实例(3)任务背景与目标

我们经常在做项目的时候&#xff0c;觉得分析背景和目标是浪费时间&#xff0c;觉得不过如此。 其实目标梳理特别重要&#xff0c;直接决定你数据的需求分析&#xff0c;模型的选择&#xff0c;决定你交付的质量。 人工智能项目也和其他项目一样&#xff0c;不要想当然&#…

7-liunx服务器规范

目录 概况liunx日志liunx系统日志syslog函数openlog 可以改变syslog默认输出方式 &#xff0c;进一步结构化 用户信息进程间的关系会话ps命令查看进程关系 系统资源限制改变工作目录和根目录服务器程序后台话 概况 liunx服务器上有很多细节需要注意 &#xff0c;这些细节很重要…

Redis进阶篇

Redis线程模型 redis是基于内存运行的高性能k-v数据库&#xff0c;6.x之前是单线程, 对外提供的键值存储服务的主要流程 是单线程&#xff0c;也就是网络 IO 和数据读写是由单个线程来完成&#xff0c;6.x之后引入多线程而键值对读写命 令仍然是单线程处理的&#xff0c;所以 …

智能未来之路:《NIST AI RMF 1.0》与负责任的AI发展

引言 在当今快速发展的人工智能领域&#xff0c;美国国家标准与技术研究院&#xff08;NIST&#xff09;发布的《NIST AI RMF 1.0》框架是一个标志性的里程碑。这一框架不仅为AI技术的负责任和可信赖使用提供了重要指导&#xff0c;而且对于推动可持续的AI发展具有深远影响。本…

CrossOver虚拟机软件2024有哪些功能?最新版本支持哪些游戏?

CrossOver由codewaver公司开发的类虚拟机软件&#xff0c;目的是使linux和Mac OS X操作系统和window系统兼容。CrossOver不像Parallels或VMware的模拟器&#xff0c;而是实实在在Mac OS X系统上运行的一个软件。CrossOvers能够直接在Mac上运行Windows软件与游戏&#xff0c;而不…

创建型设计模式 - 原型设计模式 - JAVA

原型设计模式 一 .简介二. 案例三. 补充知识 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一 .简介 原型模式提供了一种机制&#xff0c;可以将原始对象复制到新对象&#xff0…

一文读懂什么是 IP 欺骗

IP欺骗被认为是最容易发起且最具破坏性的攻击之一。这种攻击方式通过伪造源IP地址来隐藏攻击者的真实身份&#xff0c;从而可以逃避追踪和封锁。由于IP欺骗的隐蔽性和难以追踪性&#xff0c;它经常被用于发起各种恶意攻击&#xff0c;如DDoS攻击、网络钓鱼和诈骗、内部网络攻击…

DM数据库学习之路(十八)DMHS数据实时同步软件部署及迁移测试

​​​​​ DMDRS介绍 产品介绍 达梦数据实时同步软件&#xff08;以下简称 DMDRS&#xff09;是支持异构环境的高性能、高可靠、高可扩展数据库实时同步复制系统。该产品采用基于日志的结构化数据复制技术&#xff0c;不依赖主机上源数据库的触发器或者规则&#xff0c;对主…

docker部署seata1.6.0

docker部署seata1.6.0 Seata 是 阿里巴巴 开源的 分布式事务中间件&#xff0c;解决 微服务 场景下面临的分布式事务问题。需要先搭建seata服务端然后与springcloud的集成以实现分布式事务控制的过程 &#xff0c;项目中只需要在远程调用APi服务的方法上使用注解 GlobalTransa…

电商+支付双系统项目------电商系统中收货模块的开发

本篇文章是讲关于项目的收货地址模块的设计。这个就比较简单了&#xff0c;我就不像之前的文章讲的那么详细了&#xff0c;就简单讲讲就好。 首先先设计 DAO 层&#xff1a; package com.imooc.mall.dao;import com.imooc.mall.pojo.Shipping; import org.apache.ibatis.annot…

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测 目录 分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测分类效果基本描述程序设计参考资…

Unity接入SQLite (一):SQLite介绍

1.简介 SQLite是一个开源的嵌入式关系数据库管理系统。它是一种轻量级的数据库引擎&#xff0c;不需要单独的服务器进程&#xff0c;可以直接嵌入到应用程序中使用。Sqlite使用简单、高效&#xff0c;并且具有对标准SQL的完整支持。它适用于需要在本地存储和访问数据的应用程序…

wordpress免费主题模板

免费大图wordpress主题 首页是一张大图的免费wordpress主题模板。简洁实用&#xff0c;易上手。 https://www.jianzhanpress.com/?p5857 wordpress免费模板 动态效果的wordpress免费模板&#xff0c;banner是动态图片效果&#xff0c;视觉效果不错。 https://www.jianzhan…

C++从入门到精通 第六章(函数)

写在前面&#xff1a; 本系列专栏主要介绍C的相关知识&#xff0c;思路以下面的参考链接教程为主&#xff0c;大部分笔记也出自该教程&#xff0c;笔者的原创部分主要在示例代码的注释部分。除了参考下面的链接教程以外&#xff0c;笔者还参考了其它的一些C教材&#xff08;比…

学习Markdown

https://shadows.brumm.af 欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些…

什么是MapReduce

1.1 MapReduce到底是什么 Hadoop MapReduce是一个软件框架&#xff0c;基于该框架能够容易地编写应用程序&#xff0c;这些应用程序能够运行在由上千个商用机器组成的大集群上&#xff0c;并以一种可靠的&#xff0c;具有容错能力的方式并行地处理上TB级别的海量数据集。这个定…

Flutter插件开发指南01: 通道Channel的编写与实现

Flutter插件开发指南01: 通道Channel的编写与实现 视频 https://www.bilibili.com/video/BV1ih4y1E7E3/ 前言 本文将会通过一个加法计算&#xff0c;来实现 Channel 的双向通讯&#xff0c;让大家有个一个体会。 Flutter插件 Flutter插件是Flutter应用程序与原生平台之间的桥…

1.CSS单位总结

CSS 单位总结 经典真题 px 和 em 的区别 CSS 中的哪些单位 首先&#xff0c;在 CSS 中&#xff0c;单位分为两大类&#xff0c;绝对长度单位和相对长度单位。 绝对长度单位 我们先来说这个&#xff0c;绝对长度单位最好理解&#xff0c;和我们现实生活中是一样的。在我们…

WordPress关键漏洞影响25000+站点;Cisco漏洞被勒索软件利用;朝鲜黑客瞄准全球国防公司 | 安全周报 0223

1. CISA警告&#xff1a;Akira勒索软件正在利用Cisco ASA/FTD 漏洞 近日&#xff0c;美国网络安全和基础设施安全局&#xff08;CISA&#xff09;发布了一份警告&#xff0c;指出Akira勒索软件正在积极利用Cisco的Adaptive Security Appliance (ASA) 和 Firepower Threat Defe…