Hack-A-Sat 4 Qualifiers 部分 Reverse WP
这周末打了 Hack-A-Sat 4 Qualifiers,纯逆向不多,动态 flag 的这种操作倒是第一次见,还挺好的。
The Magician:As Below
这个题给了一堆的 wasm 编译的二进制文件,使用 wasm-decompile
反编译后可以看出来基本的逻辑非常简单,伪代码如下
i = int(input())
if quick_maths(i):
print('Congratulations')
else:
print('Wrong')
关键在于,每个文件的算法不同,但是大同小异,具体来说就是 quick_maths
函数的实现不同,下图其中两个文件 wasm-decompile
对比结果
基本操作就是对输入进行一系列的运算然后和某个数字对比,但是量太大了(78个文件手动做有点难为人啦)关键就是自动化,
下面是脚本:
import re
import os
import math
E = re.compile(r'var e:.+? = (\d+)L?;')
D1 = re.compile(r'd\[3] = [a-zA-Z]+;')
D2 = re.compile(r'var [a-zA-Z]+:int = d\[3];')
D3 = re.compile(r'var [a-zA-Z]+:int = (\d+);')
D4 = re.compile(r'var [a-zA-Z]+:int = [a-zA-Z]+ (.+?) [a-zA-Z]+;')
def solve(p: str):
f = open(p, 'r')
line = f.readline().strip()
e = -1
exps = []
flag = False
while line is not None and line != '':
if not flag:
if line.startswith('function quick_maths(a:int):int {'):
flag = True
line = f.readline()
continue
if line.startswith('}'):
break
# 判断 E
tmp = E.match(line)
if tmp:
e = int(tmp.group(1))
# 判断指令
tmp = D1.match(line)
if tmp:
line = f.readline().strip()
tmp = D2.match(line)
if tmp:
line = f.readline().strip()
tmp = D3.match(line)
exp = {}
if tmp:
line = f.readline().strip()
exp['val'] = int(tmp.group(1))
tmp = D4.match(line)
if tmp:
exp['op'] = tmp.group(1)
exps.append(exp)
else:
print(f'D4寄啦:{line}, {p}')
else:
print(f'D3寄啦:{line}, {p}')
else:
print(f'D2寄啦:{line}, {p}')
line = f.readline().strip()
f.close()
if e == -1:
print(f'e not found, {p}')
exit(-1)
exps.reverse()
for exp in exps:
if exp['op'] == '+':
e -= exp['val']
elif exp['op'] == '-':
e += exp['val']
elif exp['op'] == '*':
e /= exp['val']
e = math.ceil(e)
elif exp['op'] == '/':
e *= exp['val']
elif exp['op'] == '<<':
e >>= exp['val']
elif exp['op'] == '>>':
e <<= exp['val']
else:
print(f'{exp["op"]} unk')
e &= 0xffffffff
return e
if __name__ == '__main__':
ans = {}
for filename in os.listdir(r'as-below'):
os.system(f'wasm-decompile ./as-below/{filename} -o ./{filename}.dcmp')
v = solve(f'./{filename}.dcmp')
ans[filename] = v
print(ans)
拿到 ans
之后,就可以使用 nc 连接服务器回答对应的文件的答案了,回答几个之后服务器就会下发动态 flag
import base64
from pwn import *
import re
ans = {'seven-of-swords': 61580499, 'page-of-pentacles': 3658203024, 'six-of-cups': 210505255, 'moon': 2787276480, 'temperance': 3805934783, 'hierophant': 4262516974, 'wheel-of-fortune': 3611340840, 'empress': 1310207335, 'two-of-cups': 3607122282, 'five-of-pentacles': 3607485368, 'queen-of-cups': 2349515631, 'world': 3852015110, 'devil': 2923591907, 'star': 2957373504, 'page-of-wands': 255030510, 'knight-of-cups': 4066247905, 'hermit': 3290710972, 'nine-of-swords': 511991706, 'strength': 1166252640, 'emperor': 839496975, 'justice': 2358965466, 'ace-of-cups': 611277720, 'ace-of-swords': 2750197427, 'ten-of-swords': 1513147120, 'ace-of-pentacles': 12963199, 'five-of-cups': 2050383881, 'four-of-swords': 604779525, 'eight-of-pentacles': 198270564, 'six-of-wands': 786883784, 'eight-of-swords': 2839462626, 'queen-of-pentacles': 1884769186, 'knight-of-swords': 1070886071, 'lovers': 2217781702, 'judgement': 2035617936, 'seven-of-cups': 1031666068, 'ten-of-wands': 1854568544, 'king-of-cups': 1453951683, 'seven-of-wands': 2910290968, 'four-of-pentacles': 2550853537, 'king-of-swords': 1319265320, 'seven-of-pentacles': 3533000571, 'four-of-wands': 1435129727, 'six-of-swords': 2164453522, 'page-of-cups': 216847846, 'king-of-pentacles': 380634964, 'eight-of-cups': 1007962682, 'ace-of-wands': 514591455, 'queen-of-wands': 2247727288, 'priestess': 1603947416, 'ten-of-cups': 2737841242, 'fool': 648889351, 'five-of-wands': 1124621399, 'five-of-swords': 3564338418, 'knight-of-wands': 2178221029, 'nine-of-cups': 930696156, 'three-of-cups': 2468563332, 'chariot': 4073942385, 'chillen': 2418454517, 'two-of-wands': 3484165854, 'six-of-pentacles': 1606210675, 'page-of-swords': 2199394019, 'king-of-wands': 1197160526, 'eight-of-wands': 88764413, 'sun': 341298391, 'ten-of-pentacles': 282613890, 'three-of-wands': 3307859850, 'knight-of-pentacles': 1017423675, 'death': 3645291899, 'two-of-pentacles': 3594297532, 'three-of-swords': 3774527429, 'nine-of-pentacles': 2961091017, 'magician': 326651680, 'nine-of-wands': 838248472, 'queen-of-swords': 7636720, 'four-of-cups': 1478539387, 'three-of-pentacles': 1397317383, 'two-of-swords': 2766859517, 'tower': 3062497761}
r = remote('as_below.quals2023-kah5aiv9.satellitesabove.me', -1) # -1 改成官方给的端口
r.recvuntil(b'Ticket please:', drop=True)
r.sendline(b'ticket{官方给的 ticket}')
# 丢弃
r.recvline()
r.recvline()
r.recvline()
R = re.compile('([-a-z]+)\t[0-9a-f]{64}')
line = r.recvline()
print(line)
tmp = R.match(line.decode())
while tmp:
k = tmp.group(1)
if k not in ans.keys():
print(f'没找到,{k}, {line}')
break
r.sendline(base64.b64encode(str(ans[k]).encode()))
line = r.recvline()
print(line)
tmp = R.match(line.decode())
print(r.recvall())
The Magician:Leavenworth Street
这个题就更新颖了,官方给了一个网站供你提交 TypeScript
脚本,使用 TypeScript
脚本完成随机下发的迷宫问题即可,因此关键在于 TypeScript
如何与服务交互,题目附件中给出了题目的 docker 镜像,提取去其中的二进制文件后逆向即可。
二进制是用 crystal-lang 语言编写的,但是二进制符号表是在的逆向难度不大.TypeScript
是用的 deno, 交互是基于 stdio 的,配置环境写一个 dfs 算法即可,直接祭出 ChatGPT 写完稍加修改。
type Maze = string[][];
type Coordinate = {
row: number;
col: number;
};
type PathStep = {
coordinate: Coordinate;
direction: string;
};
const directions: Record<string, Coordinate> = {
E: { row: 0, col: 1 }, // East
W: { row: 0, col: -1 }, // West
S: { row: 1, col: 0 }, // South
N: { row: -1, col: 0 }, // North
};
function solveMaze(maze: Maze, start: Coordinate, end: Coordinate): PathStep[] | null {
const visited: boolean[][] = new Array(maze.length)
.fill(false)
.map(() => new Array(maze[0].length).fill(false));
const path: PathStep[] = [];
function dfs(row: number, col: number): boolean {
if (row < 0 || row >= maze.length || col < 0 || col >= maze[0].length) {
return false;
}
if (maze[row][col] === "X" || visited[row][col]) {
return false;
}
visited[row][col] = true;
if (row === end.row && col === end.col) {
return true;
}
for (const [direction, { row: dRow, col: dCol }] of Object.entries(directions)) {
const nextRow = row + dRow;
const nextCol = col + dCol;
if (dfs(nextRow, nextCol)) {
path.push({ coordinate: { row, col }, direction });
return true;
}
}
return false;
}
dfs(start.row, start.col);
if (path.length === 0) {
return null;
}
path.reverse();
return path;
}
const decoder = new TextDecoder();
const buffer = new Uint8Array(1024);
const bytesCount = await Deno.stdin.read(buffer)
const text = decoder.decode(buffer.slice(0, bytesCount!))
const lines = text.split("\n").slice(1)
console.error(lines);
const maze: Maze = []
const start: Coordinate = { row: -1, col: -1 };
const end: Coordinate = { row: -1, col: -1 };
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const temp = [];
for (let j = 0; j < line.length; j++) {
temp.push(line[j]);
if(line[j] === 'S') {
start.row = i;
start.col = j;
}
if(line[j] === 'F') {
end.row = i;
end.col = j;
}
}
maze.push(temp)
}
let path = solveMaze(maze, start, end)!.map(m => m.direction).join('');
const encoder = new TextEncoder()
await Deno.stdout.write(encoder.encode(path))