Hack-A-Sat 4 Qualifiers 部分 Reverse WP

发布时间 2023-04-03 18:23:30作者: SinkDev

这周末打了 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 对比结果
image

基本操作就是对输入进行一系列的运算然后和某个数字对比,但是量太大了(78个文件手动做有点难为人啦)关键就是自动化,

image

下面是脚本:

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