CTF-Reverse 迷宫地图类题目分析‘‘DFS和BFS算法‘‘(学习笔记)【详】

前言

新鸟入门,听师兄们说已经蛮久没有遇到过迷宫类题目了;但是我觉得出不出是一回事,我起码要会吧hh。所以还是学一学;而且最近新生赛遇到了好几道,总结一下;

–真看的时候好多不会,DFS和BFS要学,数据结构还欠着o(╥﹏╥)o

迷宫问题

迷宫问题一般有以下特点:

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

地图一般会用0和1或者是*号和#号表示,有一些地图是存储在内存中的,分析的时候可以直观的看到,有一些是动态生成的,这个就需要结合动态调试来完成。
用户的输入通常会是awds或是jkli这类明显的用来表示方向的按键,具体看题目分析。
一般情况下, 迷宫是只有 1 个入口和 1 个出口,也有可能用一些符号来表示入口和出口;解密时需要根据具体情况判断,迷宫的路线route也有可能不止一条,题目可能会要求一些特定条件,例如最短时间内走完。

DFS和BFS算法讲解

深度优先遍历(Depth Fist Search)

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

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

广度优先遍历(Breath First Search)

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

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

具体代码实现部分可以看github算法
图文解析可以看图文详解

具体步骤和代码实现

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 True
    else:
        return False

def dfs(maze, x, y, path):
    map = maze.copy()    # 这里用将maze复制给map,避免修改掉原地图。
    if map[x][y] != 2:
        map[x][y] = 1
    if check_success(map, x, y):
        print(path)
        return True

    next_point = gen_nex(map, x, y)
    for n in next_point:
        pathn = path + n[2]       # 将all_dir列表中的元组的第三个值,即方向传给pathn
        dfs(map, n[0], n[1], pathn)        # 这里开始递归 用all_dir的元组第一二个值和pathn作为参数,进行当前位置的又一次深度优先遍历。

最后再加个尾巴

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

运行结果:
在这里插入图片描述

‘’

实战分析

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

简单再讲一下;这一题是pyinstaller类的题目;
在这里插入图片描述
题目附件,再没有任何有用信息的情况下先查壳;
在这里插入图片描述

用pyinstxtractor.py反编译一下;得到一个附件_extracted文件夹,打开
在这里插入图片描述

在这里插入图片描述

再用uncompyle6,将5.pyc反编译成py文件。
在这里插入图片描述
在这里插入图片描述

打开5.py就好了,上面是地图,下面关于输入也没有什么值得注意的。最后flag是将route进行一次md5加密。

‘’

NSSCTF [GDOUCTF 2023]润!

在这里插入图片描述

拿到题目先查壳,这里看出UPX壳但是是魔改过的upx壳;随便拿个正常的exe加个壳看看,再对比一下魔改后的壳

在这里插入图片描述

在这里插入图片描述

这里用十六进制编辑器进行修改,winhex, 将FUK都改成UPX就好;
在这里插入图片描述
在这里插入图片描述

保存就好,再看就是正常的upx壳了,用upx -d
在这里插入图片描述

第一次upx -d出错了,原来是忘记还有个FUK!
在这里插入图片描述
在这里插入图片描述

之后用IDA分析就好,这个真的 今天磨了一天的迷宫。上来随便找道题就是三维地图,虽然做出来了就挺简单了hh,我做完才发现这一题和最近个新生赛的题是一样的(o)/~。。其实本来就是因为那道题不会做才来学迷宫了,截止到现在好像才16解,斯 ~

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

‘’

init点进去看看,

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

在这里插入图片描述

moving这里一开始真没看懂怎么走的,后面才明白是三维的,还有上下楼这种操作。注意看:在case ‘u’:处又调用了一次init创建了一个迷宫,还有layer也是个hint。这里就体现了英文水平了,我一点也不敏感,layer是层的意思,这里就是有八层。 8 * 8 * 8 = 512 ;即意味着我们的终点就是左下角的位置,511;起点是一楼的左上角。我觉得难点就在这了,后面就按部就班。

哦还有,需要动态调试,输入uuuuuuuuuuuuuuuuuuuu就好,连续创建七次迷宫,将迷宫copy出来就好,上exp了

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 False


def 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_dir


def check_success(map, x, y, z):
    if map[x * 64 + y * 8 + z] == 2:
        return True
    else:
        return False


def dfs(mapb, x, y, z, path):
    map = mapb.copy()
    if map[x * 64 + y * 8 + z] != 2:
        map[x * 64 + y * 8 + z] = 1
    if check_success(map, x, y, z):
        print(path)
        return True

    next_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)

一样的步骤,只是上一题二维的用map[x][y]我觉得比用map[25 * x + y]好点,这一题我又觉得map[ 64 * x + 8 * y + z] 好点hh。x表示楼层,y是w和s表示行,z是列。后面的也一样进行微调就好。

解出的route:ssddssuuwwddndduuussdussasauudd

BUUCTF:[HDCTF2019]Maze

在这里插入图片描述

在这里插入图片描述
先查壳,脱壳勒,最简单upx -d了

在这里插入图片描述
花指令,nop掉他!这里第一处call直接nop就好,然后 将数据转换为代码,要用force。这里会有3个黄色的nop掉再转为代码就好

下面还有一处

在这里插入图片描述
在这里插入图片描述
这里如果nop掉 or的话 后面会出错,所以选择把jmp直接nop掉,nop掉后用 p 创建函数,再反编译就好

在这里插入图片描述

这里就清晰多了,没有找到地图在哪,按Shift+F12碰碰运气,
在这里插入图片描述
提取出来,因为也没有找到迷宫的规模在哪里,结合一共有70个字符,而且一共走了14步,asc_408078 = 5,dword_40807C = -4,可以大概猜出 7 * 10 的迷宫大小。

map = ['*', '*', '*', '*', '*', '*', '*', '+', '*', '*', '*', '*', '*', '*', '*', '*', '*', ' ', '*', '*', '*', '*', '*', '*', ' ', ' ', ' ', ' ', '*', '*', '*', '*', ' ', ' ', ' ', '*', '*', '*', '*', '*', '*', '*', ' ', '*', '*', 'F', '*', '*', '*', '*', '*', '*', ' ', ' ', ' ', ' ', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*']


def check_point_valid(map, x, y):
    if (x >= 0) and (x <= 7) and (y >= 0) and (y <= 9):
        return (map[10 * x + y] != '*') and ((map[10 * x + y] == ' ') or (map[10 * x + y] == 'F'))
    else:
        return False

def gen_nex(map, x, y):
    all_dir = []
    if check_point_valid(map, x - 1, y):
        all_dir.append((x - 1, y, 'w'))
    if check_point_valid(map, x + 1, y):
        all_dir.append((x + 1, y, 's'))
    if check_point_valid(map, x, y - 1):
        all_dir.append((x, y - 1, 'a'))
    if check_point_valid(map, x, y + 1):
        all_dir.append((x, y + 1, 'd'))
    return all_dir


def check_success(map, x, y):
    if map[10 * x + y] == 'F':
        return True
    else:
        return False


def dfs(mapb, x, y, path):
    map = mapb.copy()
    if map[10 * x + y] != 'F':
        map[10 * x + y] = 1
    if check_success(map, x, y):
        print(path)
        return True

    next_point = gen_nex(map, x, y)
    for n in next_point:
        pathn = path + n[2]
        dfs(map, n[0], n[1], pathn)


outpus = ""
dfs(map, 0, 7, outpus)

差不多的exp,修修补补,应该大部分迷宫题都能解吧,大概吧,如果再加别的知识点那就是别的事了。这里map本来是字符串的形式,但是py会报类型出错,所以就改了一下,写个小脚本输出一下就好。

后言

没有细讲DFS和BFS的原因是我自己也一知半解的,数据结构和算法分析还欠着,找时间该补上了。
又到了碎碎念的环节了hh反正没几个人看,自说自话,今天想写这个才发现自己以前很多东西都是没有学到位的,因为当我想把自己理解到的东西转换成自己的再输出出来时,才发现,真的1好难a。DFS和BFS都还不是很理解,想写也不知道该怎么组织语言,不想完全复制粘贴一些大佬的笔记o(╥﹏╥)o没有意义a,本来写博客的原因之一就是记录学习,那么记录下来的东西肯定要是自己学会的,而且如果能对看到的人有帮助就更好啦。碎碎念结束,明天继续加油,迟早把今天欠的补上!

部分文献来源:

https://ctf-wiki.org/reverse/maze/maze/
https://mp.weixin.qq.com/s/T6ML7zwA57JXTRwOZqcxhw?spm=a2c6h.12873639.article-detail.7.19f31041PU5YhX
https://blog.csdn.net/weixin_40519529/article/details/113081269
https://github.com/Locietta/blogs/issues/14

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sciurdae.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值