第二题
这是一到迷宫题目,从S处出发,走过0路径,走过的0置为1,并最后到达出口。
查看代码,发现有8种移动方法
解题思路如下:
- 找到满足将所有0置为1的路径,即最优路径。通过递归算法,即可找到最优路径。
- 找到最优路径后,根据题目算法还原switchcode。
- 还原switchcode后,根据题目算法还原位置,并还原flag。
根据以上思路,编些代码如下:
import random import copy def mark(maze,pos): maze[pos[0]][pos[1]] = 1 # mark代表走过,走过的就变成墙 def remark(maze,pos): maze[pos[0]][pos[1]] = 0 # 就把这一步恢复 def passable(maze,pos): status = maze[pos[0]][pos[1]] == 0 return status # 这个递归函数的功能就是 走这一步,看看是否8个方向可以走,不可以走就返回false,回退到上一步,继续看其他方向 def find_path(dirs,maze,pos,end): mark(maze,pos) # pos位置已经走过 if pos == end: correct_path.append(pos) # 到达了出口 return True # 判断6个方向是否可以走动 for i in range(8): nextpos = pos[0]+dirs[i][0],pos[1]+dirs[i][1] # 下一步,从左开始顺时针 if nextpos[0] < 0 or nextpos[1] < 0: # 不能走出界限 continue if nextpos[0] > 8 or nextpos[1] > 9: continue if passable(maze,nextpos): # 该方向是否可以走动 find_path_status = find_path(dirs,maze,nextpos,end) # 这个方向可以走动,就走动到这个方向 if find_path_status: correct_path.append(pos) # 这就说明下一层已经是出口路线 return True else: pass # 说明这个方向的下一步是死路,看看其他方向的下一步是否能走 remark(maze, pos) return False # 8个方向不可以走动 def search_correct_path(): global correct_path correct_sign = False # 正确标志位 dirsset = [] dirs = [(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)] originalmaze = [['S',0,1,0,0,1,0,0,1,1], \ [1,1,0,0,1,0,0,1,0,0], \ [0,0,1,0,1,1,1,1,1,0], \ [0,1,1,0,1,0,0,1,0,0], \ [0,0,1,0,0,1,0,0,1,1], \ [1,1,0,1,1,1,0,1,0,1], \ [0,0,1,1,1,1,0,1,0,1], \ [0,1,1,0,0,1,0,1,0,1], \ [0,0,0,1,0,0,1,1,0,0]] maze = originalmaze originalstart = (0, 0) originalend = (8, 9) status = True while status: for row in maze: if 0 not in row: correct_sign = True else: correct_sign = False break if correct_sign: calculationop(list(reversed(correct_path))) return else: correct_path = [] maze = copy.deepcopy(originalmaze) while dirs in dirsset: random.shuffle(dirs) dirsset.append(copy.deepcopy(dirs)) status = find_path(dirs,maze, originalstart, originalend) def calculationop(right_path): op = [] for i in range(46): raw = right_path[i][0] con = right_path[i][1] nexraw = right_path[i+1][0] nexcon = right_path[i+1][1] RAW = nexraw - raw CON = nexcon - con if CON==1 and RAW==0: op.append(1) elif raw%2==1 and CON==0 and RAW==1: op.append(2) elif raw%2==0 and CON==1 and RAW==1: op.append(2) elif raw%2==1 and CON==-1 and RAW==1: op.append(3) elif raw%2==0 and CON==0 and RAW==1: op.append(3) elif CON == -1 and RAW==0: op.append(4) elif raw%2==1 and CON ==-1 and RAW ==-1: op.append(5) elif raw%2==0 and CON==0 and RAW ==-1: op.append(5) elif raw%2==1 and CON==0 and RAW ==-1: op.append(0) elif raw%2==0 and CON==1 and RAW== -1: op.append(0) get_flag(op) return def get_flag(op): flag = [] strspace = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' def com(count, opfirst, opsecond): for i in range(36): first = 5 - (i + count) % 6 second = (count + i // 6) % 6 if first == opfirst and second == opsecond: flag.append(strspace[i]) return for count in range(23): com(count, op[0], op[1]) for i in range(2): op.pop(0) print(''.join(flag)) return if __name__ == '__main__': correct_path = [] search_correct_path()
第三题
这是一个若依管理系统登录界面,绕过登录界面,即可看到flag。
前后端分离系统,修改服务端返回数据包,满足前端验证条件即可进行路由跳转。
程序首先将用户名密码等信息POST到服务端,服务端进行验证,前端接收服务端返回数据并验证。
整体思路就是修改后端返回来的数据包。通过burpsuit截获返回数据包,进行修改,再传递给其前端,欺骗前端,进入首页。
第一层验证经分析发现其要求code=200以及token
因此构造数据包如下:
HTTP
/
1.1
200
OK
X
-
Powered
-
By: Express
vary: Origin, Access
-
Control
-
Request
-
Method, Access
-
Control
-
Request
-
Headers
access
-
control
-
allow
-
origin: http:
/
/
121.36
.
145.157
access
-
control
-
allow
-
credentials: true
x
-
content
-
type
-
options: nosniff
x
-
xss
-
protection:
1
; mode
=
block
cache
-
control: no
-
cache, no
-
store,
max
-
age
=
0
, must
-
revalidate
pragma: no
-
cache
expires:
0
content
-
type
: application
/
json;charset
=
UTF
-
8
date: Thu,
13
May
2021
11
:
22
:
05
GMT
connection: close
Content
-
Length:
49
{
"msg"
:
"用户/密码正确"
,
"code"
:
200
,
"token"
:
"123465"
}
第一层验证通过。
第二层验证经分析发现其要求roles,permissions,msg,code=200,user等信息。
因此构造数据包如下:
HTTP
/
1.1
200
OK
X
-
Powered
-
By: Express
x
-
content
-
type
-
options: nosniff
x
-
xss
-
protection:
1
; mode
=
block
cache
-
control: no
-
cache, no
-
store,
max
-
age
=
0
, must
-
revalidate
pragma: no
-
cache
expires:
0
content
-
type
: application
/
json;charset
=
utf
-
8
content
-
length:
84
date: Thu,
13
May
2021
11
:
14
:
06
GMT
connection: close
{
"roles"
:
"[admin]"
,
"permissions"
:
"all"
,
"msg"
:
"成功获取用户信息"
,
"code"
:
200
,
"user"
:{
"avatar"
:
"123456"
,
"useName"
:
"admin"
}}
第二层验证通过。
第三层验证经分析发现其要求code=200,data等信息。
因此构造数据包如下:
HTTP
/
1.1
200
OK
X
-
Powered
-
By: Express
x
-
content
-
type
-
options: nosniff
x
-
xss
-
protection:
1
; mode
=
block
cache
-
control: no
-
cache, no
-
store,
max
-
age
=
0
, must
-
revalidate
pragma: no
-
cache
expires:
0
content
-
type
: application
/
json;charset
=
utf
-
8
content
-
length:
84
date: Thu,
13
May
2021
11
:
39
:
19
GMT
connection: close
{
"msg"
:
"成功获取动态路由"
,
"code"
:
200
,
"data"
:[{
"path"
:
"/"
,
"name"
:
"index"
,
"component"
:
"index"
}]}
第三层验证通过。
成功进入到首页,获取flag
第三题官方思路解
该系统利用了springboot+mysql+redis搭建,通过阅读部署文档发现目标正好开启了redis的6379端口,并且可以未授权访问。
若依的用户信息是缓存在redis中的,并且通过JWT进行认证。
链接redis,发现了缓存了用户信息
找到若依演示网址http://vue.ruoyi.vip/login?redirect=%2Findex,登录一下,了解一下过程。发现在登陆成功后会返回一个token,这个toke就是jwt消息。把redis里面的token和名字都改成本地抓到的token
然后在登录页登录,修改返回的response包,就可以成功跳转到首页
HTTP/1.1 200
Server: nginx
Date: Sun, 16 May 2021 07:57:01 GMT
Content-Type: application/json;charset=UTF-8
Connection: close
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Content-Length: 228
{“msg”:”操作成功”,”code”:200,”token”:”eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImJhNTAzOWQ4LTUwYjAtNDMxOC1hMGEzLTBlNjhiYzM2MmYwNiJ9.KBBbhPRF1_0jybTuLywexorgvGlM0wZvDEwpKuMLG6JzsC3TQBcY-eKFzpscJ74sNtGRJB4NZcIXWua0tKC7vQ”}
第四题
打开main函数,基本思路如下
看条件2(401240函数):
整体思路就是,输入的字符,在buffer_temp里寻找其位置position,并做运算存储到buffer里面。如果遇到数字(0-9),与已经判断过的字符个数相加,如果是9则修改一些值。
再看条件3(401000):
将条件2中获得的数字,补充到以下0的位置。
补充后检查行数字是否存在相同的,再检查列是否相同,这就是数独。通过查看缓存区,发现是9*9数独。
而且条件3中需要填写的字符长度是54,但是调用401000函数传入的参数是长度-9,通过条件三可以确定54个数独字符,但是还缺9个字符无法确定。
条件2中如果传入数字,则会进行处理。如果是9*9数独,则每一行添加一个数字,就可以补充到缺失的9个字符。
if ( i + input_one_byte == ‘9’ )
{
i = 0;
v5 += 9;
goto LABEL_13;
}
继续看几个函数,发现4014e0和4015b0发现是对输入进行md5运算,然后401ed0对AES密钥初始化,然后在下文通过AES对代码进行解密,然后则执行这段解密后的代码。md5可以确定这九个数字是唯一的。
再研究一下条件2的代码,数独每一行有9个字符,以第一行为例,添加7个字符(此时i=7),那么输入是2则满足这个条件。输入其他1-9(排除2)的数字都是一样的效果。因此可以断定,这缺失的9个字符,就是代码每一行已填充的字符个数。
if ( i + input_one_byte == ‘9’ )
{
i = 0;
v5 += 9;
goto LABEL_13;
}
写出解密代码:
1. 先解出数独要添加的数字:
import re import copy # 默认模板-->在这里写准备求的数独 sudoku_template1 = [[0, 4, 0, 7, 0, 0, 0, 0, 0], [9, 2, 0, 0, 0, 0, 6, 0, 7], [8, 3, 0, 0, 0, 5, 4, 0, 0], [0, 1, 0, 0, 0, 3, 0, 0, 0], [0, 0, 0, 2, 0, 1, 0, 0, 0], [0, 0, 0, 5, 0, 0, 0, 4, 0], [0, 0, 4, 9, 0, 0, 0, 7, 1], [3, 0, 5, 0, 0, 0, 0, 9, 4], [0, 0, 0, 0, 0, 8, 0, 6, 0]] # 芬兰数学家英卡拉(Arto Inkala)设计的号称“最难数独” - 1000次平均耗时320ms/次 sudoku_template2 = [[8, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 3, 6, 0, 0, 0, 0, 0], [0, 7, 0, 0, 9, 0, 2, 0, 0], [0, 5, 0, 0, 0, 7, 0, 0, 0], [0, 0, 0, 0, 4, 5, 7, 0, 0], [0, 0, 0, 1, 0, 0, 0, 3, 0], [0, 0, 1, 0, 0, 0, 0, 6, 8], [0, 0, 8, 5, 0, 0, 0, 1, 0], [0, 9, 0, 0, 0, 0, 4, 0, 0]] def crack_it(sudoku=sudoku_template1): '''主函数,输入数独进行运算,如未输入则调用默认数独,格式为9x9的二维列表''' init_sudoku = str_to_num(copy.deepcopy(sudoku)) # Python的坑!列表或字典等对象作为函数参数时,函数可能修改其元素的指针,导致外部列表也会改变 if is_valid_sudoku(sudoku): # 判断输入的Sudoku是否合理(是否冲突) candidate_list = filter_candidate_list(init_sudoku, init_candidate_list(init_sudoku), start=0) # 针对Sudoku中的每一个空格(空格都默认填入数字0),都算出其可能的备选数,存入data_list中;每当空格被确认唯一值时,剩余data_list都需要再被刷新 cracked_sudoku = fill_blank(init_sudoku, candidate_list, start=0) # 破解数独 print_sudoku(cracked_sudoku) # 在控制台显示已破解的数独,默认开启 return cracked_sudoku else: return '请检查一下输入是否有误- -0' def str_to_num(data): '''初步校验+统一格式,空字符转0,无效字符转0''' for i in range(9): for j in range(9): if re.match('[1-9]', str(data[i][j])): # 1-9字符转int 1-9 data[i][j] = int(data[i][j]) elif re.match('', str(data[i][j])): # 空位转int 0 data[i][j] = 0 else: # 无效字符转int 0,或者也可以return False,拒绝服务 data[i][j] = 0 return data def is_valid_sudoku(data): '''判断整个数独是否有效''' for y in range(9): for x in range(9): if data[y][x] > 9: return False if data[y][x] != 0 and data[y].count(data[y][x]) > 1: return False for col in range(9): if data[y][x] != 0 and col != y: if data[col][x] == data[y][x]: return False for i in range(3): for j in range(3): if data[y][x] != 0 and (i + 3 * (y // 3), j + 3 * (x // 3)) != (y, x): if data[i + 3 * (y // 3)][j + 3 * (x // 3)] == data[y][x]: return False return True def init_candidate_list(data): '''初始化建立一个数独的备选数列表,一个空格就对应其坐标以及填上1~9的备选数字,格式为81x9的二维列表''' data_list = [] for y in range(9): for x in range(9): if data[y][x] == 0: data_list.append([(x, y), [1, 2, 3, 4, 5, 6, 7, 8, 9]]) return data_list def filter_candidate_list(data, data_list, start): '''对数独的备选数表进行过滤,删除无效的备选数''' for blank_index in range(start, len(data_list)): data_list[blank_index][1] = [] for num in range(1, 10): if is_valid_num(data, data_list[blank_index][0][0], data_list[blank_index][0][1], num): data_list[blank_index][1].append(num) return data_list def is_valid_num(data, x, y, num): '''输入数独、坐标、数字,判断该位置填入该数字是否合理''' if data[y].count(num) > 0: # 行判断 return False for col in range(9): # 列判断 if data[col][x] == num: return False for a in range(3): # 九宫格判断 for b in range(3): if data[a + 3 * (y // 3)][b + 3 * (x // 3)] == num: return False return True def fill_blank(data, data_list, start): ''' 核心函数,递归尝试代入备选数,类似深度优先遍历算法。 一旦某位置填入为True(由is_valid_num函数判断),则开始下一位置的填入;若某位置填入为False,则return回上一级。 参数解释: data: 数独矩阵,二维列表 data_list: 备选数表,二维列表 start: 递归进行的位置,对应data_list的下标 ''' all_data = [] if start < len(data_list): one = data_list[start] for num in one[1]: if is_valid_num(data, one[0][0], one[0][1], num): data[one[0][1]][one[0][0]] = num # 赋值,如果能给每一格成功赋值,则意味破解成功;如果出现失败,则需要将错误赋值清零 # data_list = filter_candidate_list(data, data_list, start) # 每一步赋值都会改变备选数表,但刷新备选数表的操作非常耗时,若加上这句,速度会慢100倍 tem_data = fill_blank(data, data_list, start + 1) # start+1,使递归进入下一格点 if tem_data: # 注意!如果下一格点return,分两种情况:1.成功破解所有格点;2.发生错误,for loop结束也会return,此时返回值为None return tem_data data[one[0][1]][one[0][0]] = 0 # 注意!可能向下递归了若干格才发现前面是错误的(即for loop结束,return None),此时需要将所有错误的赋值清零。 else: return data def print_sudoku(data): '''打印数独到控制台''' print('>>> 破解结果:') # for i in range(9): # for j in range(9): # print('{:^3}'.format(data[i][j]), end='') # print('') # print('') s = "" for i in range(9): for j in range(9): if sudoku_template1[i][j] == 0: s = s + str(data[i][j]) print(s) # 输出原始数独中是0的部分 if __name__ == '__main__': crack_it()
2. 解出来是
5619238183457621978469254539786692871328563617281793452
然后依据程序,解出来待输入得字符。
import re sudoku_sequence = '5619238183457621978469254539786692871328563617281793452' buffer = '3E4E74445D7D70596662753A5650422477274F6072482A23514A6D474D5E54612B6E402D7A5A6F7B796468213063696A5278555829575B653C672F5F7325263F282D4B766B45433D6C2E3B41524C5346' buffer1 = "$BPV:ubfYp}]DtN>aT^MGmJQ#*Hr`O'wjic0!hdy{oZz-@n+?&%s_/g<e[W)XUxRFSLRA;.l=CEkvK-(" buffer_list = re.sub(r"(?<=\w)(?=(?:\w\w)+$)", " ", buffer).split(' ') input_list = [] def find_str(sudokustr): for index,value in enumerate(buffer1): op = (index%9)+1 if op == sudokustr: input_list.append(value) break for i in sudoku_sequence: find_str(int(i)) print(''.join(input_list))
3. 解出来字符是:u$YBPf$fPV:buB$YbfVuYB:V:PYbfuuYBfb$PBf:uPu$bBf$bYPV:B
然后在每一行数独最后添加个数,这样flag就是::u$YBPf2pa]Dt4#QM^H4ic’j0`w2y{d-Zzo2%/n_s@+2<UW)e4AR;F.4=-qEkvC2