VishwaCTF 2026 wp
字数 5547
更新时间 2026-04-12 12:49:42

VishwaCTF 2026 Writeup 教学文档

概述

本教学文档基于《VishwaCTF 2026 wp》链接内容整理,旨在详细解析各赛题的解题思路、技术原理与实现方法,涵盖 Cryptography、MISC、Reverse 和 Web 等多个方向。文档将按题目分类,逐一拆解关键步骤,并提供完整的解题脚本与操作指南。


一、Cryptography(密码学)

1.1 PROCEDURAL LABYRINTH

题目类型:迷宫路径编码转换

核心任务

  1. 从迷宫矩阵中找出从入口(>)到出口(<)的唯一可行路径。
  2. 将该路径转换为比特流,再解码为 ASCII 文本以获得 flag。

迷宫规则

  • #:墙(不可通行)
  • .:路(可通行)
  • >:入口
  • <:出口

解题步骤

步骤1:提取迷宫数据

  • 文档中迷宫为 701 行 × 51 列的字符矩阵。
  • 仅提取字符集为 #.>< 且行长一致的行作为有效迷宫网格。

步骤2:BFS 寻路

  • 将可通行字符(.><)视为可通过节点。
  • 从入口坐标开始进行广度优先搜索(BFS),记录每个节点的前驱节点,直至找到出口坐标。
  • 通过前驱数组回溯得到完整路径。

步骤3:路径编码转换

  • 遍历路径中相邻两点,计算列坐标变化(dc):
    • dc = +1(向右移动),记比特 0
    • dc = -1(向左移动),记比特 1
    • 若行坐标变化(上下移动),忽略
  • 将所有水平移动的比特顺序拼接成比特流。

步骤4:比特流转文本

  • 跳过第一个比特(对应进入迷宫的前导水平移动,不属于有效数据)。
  • 从第二个比特开始,每 8 位为一组转换为一个 ASCII 字符。
  • 拼接所有字符即得 flag。

关键脚本(Python)

from collections import deque
import sys

def load_grid(path: str):
    with open(path, "r", encoding="utf-8") as f:
        lines = [line.rstrip("\n") for line in f]
    grid = []
    width = None
    for line in lines:
        if not line:
            continue
        if set(line) <= set("#.><"):
            if width is None:
                width = len(line)
            if len(line) == width:
                grid.append(line)
    if not grid:
        raise ValueError("No maze grid found.")
    return grid

def find_points(grid):
    start = end = None
    for r, row in enumerate(grid):
        for c, ch in enumerate(row):
            if ch == ">":
                start = (r, c)
            elif ch == "<":
                end = (r, c)
    if start is None or end is None:
        raise ValueError("Start or end marker not found.")
    return start, end

def bfs_path(grid, start, end):
    R, C = len(grid), len(grid[0])
    walkable = {".", ">", "<"}
    q = deque([start])
    prev = {start: None}
    dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
    while q:
        r, c = q.popleft()
        if (r, c) == end:
            break
        for dr, dc in dirs:
            nr, nc = r + dr, c + dc
            if 0 <= nr < R and 0 <= nc < C:
                if grid[nr][nc] in walkable and (nr, nc) not in prev:
                    prev[(nr, nc)] = (r, c)
                    q.append((nr, nc))
    if end not in prev:
        raise ValueError("No path found from start to end.")
    path = []
    cur = end
    while cur is not None:
        path.append(cur)
        cur = prev[cur]
    path.reverse()
    return path

def decode_from_path(path):
    bits = []
    for (r1, c1), (r2, c2) in zip(path, path[1:]):
        dc = c2 - c1
        if dc == 1:
            bits.append("0")  # move right
        elif dc == -1:
            bits.append("1")  # move left
    bitstream = "".join(bits)
    bitstream = bitstream[1:]  # skip first horizontal bit
    out = []
    for i in range(0, len(bitstream) - 7, 8):
        byte = bitstream[i:i + 8]
        ch = chr(int(byte, 2))
        out.append(ch)
    return "".join(out)

def main():
    if len(sys.argv) != 2:
        print(f"Usage: python {sys.argv[0]} maze.txt")
        sys.exit(1)
    grid = load_grid(sys.argv[1])
    start, end = find_points(grid)
    path = bfs_path(grid, start, end)
    flag = decode_from_path(path)
    print("[+] Path length:", len(path))
    print("[+] Decoded:", flag)

if __name__ == "__main__":
    main()

运行与输出

python solve.py maze.txt
[+] Path length: 1089
[+] Decoded: VishwaCTF{p4th_1s_th3_m3ss4g3_00_57d006d5}

flagVishwaCTF{p4th_1s_th3_m3ss4g3_00_57d006d5}


1.2 The Curator

题目类型:线性同余生成器(LCG)参数恢复

背景

  • 题目看似 RSA,但核心是利用公开的 LCG 前 8 个输出值恢复参数,进而生成密钥流解密 flag。
  • 已知 LCG 公式:\(x_{n+1} = (A \cdot x_n + C) \bmod M\),其中 \(M = 2^{32}\)
  • 前 8 个输出值已知,后续输出用于 XOR 流加密。

解题步骤

步骤1:恢复 LCG 参数
已知连续三个输出 \(x_1, x_2, x_3\),可计算:
\(d_1 = (x_2 - x_1) \bmod M\)
\(d_2 = (x_3 - x_2) \bmod M\)
\(A = (d_2 \cdot d_1^{-1}) \bmod M\)
\(C = (x_2 - A \cdot x_1) \bmod M\)
\(seed = ((x_1 - C) \cdot A^{-1}) \bmod M\)

步骤2:验证与密钥流生成

  • 使用恢复的 \(A, C, seed\) 重新生成前 8 个输出,验证是否与已知输出一致。
  • 继续生成后续输出,跳过前 8 个公开值,将后续输出转换为字节流作为密钥流。

步骤3:XOR 解密

  • 将密文与密钥流逐字节异或,得到明文 flag。

关键脚本(Python)

outs = [980887876, 699919547, 2058135724, 3888182547, 3474875028, 107192043, 215891708, 1328661059]
ct = bytes.fromhex("b2723f1b432adff7c2009fe0e7cf4f5c10a9bf6c1a38aa50b6645fa7725823f20ae8fd9787946c5135965f200b1fd2e7fb353c8287b821")

M = 2**32
x1, x2, x3 = outs[:3]

# recover A, C, seed
d1 = (x2 - x1) % M
d2 = (x3 - x2) % M
A = (d2 * pow(d1, -1, M)) % M
C = (x2 - A * x1) % M
seed = ((x1 - C) * pow(A, -1, M)) % M

print(f"A = {hex(A)}")
print(f"C = {hex(C)}")
print(f"seed = {hex(seed)}")

# verify first 8 outputs
x = seed
check = []
for _ in range(8):
    x = (A * x + C) % M
    check.append(x)
print("verify:", check == outs)

# generate stream, skip first 8 public outputs
x = seed
stream = []
for _ in range(8 + len(ct)):
    x = (A * x + C) % M
    stream.append(x)
keystream = bytes(v & 0xff for v in stream[8:8 + len(ct)])
pt = bytes(c ^ k for c, k in zip(ct, keystream))
print(pt.decode())

flagVishwaCTF{s33ds_4r3_n3v3r_s4f3_1ns1d3_pr1m3s_4nd_n01s3}


二、MISC(杂项)

2.1 Fragmented Evidence

题目类型:日志中的 Base64 片段拼接

解题步骤

  1. 打开日志文件 server.log,搜索异常字段 packet anomaly detected id=backup failed code=
  2. 提取等号后的字符串,按出现顺序拼接:
    Zmxh
    Z3tP
    YmZ1
    c2Nh
    dGVk
    VHJ1dGh9
    
  3. 拼接后得到完整 Base64 字符串:ZmxhZ3tPYmZ1c2NhdGVkVHJ1dGh9
  4. Base64 解码得到:flag{ObfuscatedTruth}
  5. 转换为标准 flag 格式:VishwaCTF{ObfuscatedTruth}

关键脚本(Python)

import base64
import re

data = open("server.log", "r", encoding="utf-8").read()
parts = re.findall(r'id=([A-Za-z0-9+/=]+)', data)
parts += re.findall(r'code=([A-Za-z0-9+/=]+)', data)
b64 = ''.join(parts)
print("[+] joined:", b64)
decoded = base64.b64decode(b64).decode()
print("[+] decoded:", decoded)
inner = decoded.removeprefix("flag{").removesuffix("}")
print("[+] submit:", f"VishwaCTF{{{inner}}}")

flagVishwaCTF{ObfuscatedTruth}


2.2 BOOT

题目类型:ISO 镜像隐藏数据提取(Base58 编码)

背景

  • 题目提供的 ISO 镜像基于 TinyCore 17.0 修改,系统本体未变,但在引导层 isolinux.bin 中隐藏了数据。
  • isolinux.bin 的偏移 0x6000 处发现重复三行的字符串:9d5zYNcj9f1S46vC74aruPgE9b6T3ceX4Qsa2VQAbVnDFfTr

解题步骤

  1. 该字符串类似 Base64 但解码为乱码,尝试 Base58 解码。
  2. 使用 Base58 字母表:123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
  3. 解码得到 flag。

关键脚本(Python)

alphabet='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
s='9d5zYNcj9f1S46vC74aruPgE9b6T3ceX4Qsa2VQAbVnDFfTr'
n=0
for ch in s:
    n = n*58 + alphabet.index(ch)
print(n.to_bytes((n.bit_length()+7)//8, 'big').decode())

flagVishwaCTF{iM4G35_4Re_n0t_th4T_c00L}


2.3 UnderWorld - P1

题目类型:Minecraft 地图探索

解题步骤

  • 在 Minecraft 地图中,于龙身上的桶内发现钻石,其自定义名称为:No Emeralds here
  • 根据 CTF 命名惯例,转换为 flag 格式。

flagVishwaCTF{no_emeralds_here}


2.4 UnderWorld - P2

题目类型:Minecraft 地图探索

解题步骤

  • 在龙头附近的箱子中,27 个钻石按槽位自定义名称拼出完整字符串。

flagVishwaCTF{m1n3cr4f7_15_fun}


2.5 The-Lost-Client

题目类型:PNG 元数据分析与推理

解题步骤

  1. 分析 PNG 文件的 XMP 元数据,发现以下字段:
    • creator = "Tata and........."(9 个点暗示 9 字母姓氏)
    • UserComment = "Its a generational business family"
    • title = "V!"
  2. 结合“generational business family”和“V!”,推测指代人物为 Vijaypat Singhania(印度著名商业家族成员)。
  3. 进一步查询公开资料,确认 Vijaypat Singhania 曾担任 Indian Institute of Management Ahmedabad(IIMA)的董事会主席(任期 2007-03-29 至 2012-03-28)。
  4. 因此,题目要求的机构缩写为 IIMA

flagIIMA(需根据题目上下文确认是否为最终提交格式)


2.6 Jigsaw QR

题目类型:二维码碎片拼图

背景

  • 原始二维码被分割为 4×4 共 16 个碎片,顺序被打乱。
  • 需要将碎片按正确顺序拼接还原二维码,再扫描得到 flag。

正确排列顺序(按碎片编号 0~15):

2 6 15 13
11 14 12 3
5 4 1 7
0 8 10 9

解题步骤

  1. 将 16 个碎片图片按上述顺序排列成 4×4 网格。
  2. 拼接成完整二维码图片。
  3. 使用二维码解码库(如 qrcodepyzbar)或手动扫描得到 flag。

关键脚本(Python + PIL)

from PIL import Image
import pyzbar.pyzbar as pyzbar

IMG_PATH = "qr_pieces.png"
OUT_PATH = "recovered_qr.png"
ORDER = [2, 6, 15, 13, 11, 14, 12, 3, 5, 4, 1, 7, 0, 8, 10, 9]

def split_4x4(img):
    w, h = img.size
    xs = [round(i * w / 4) for i in range(5)]
    ys = [round(i * h / 4) for i in range(5)]
    tiles = []
    for r in range(4):
        for c in range(4):
            tile = img.crop((xs[c], ys[r], xs[c + 1], ys[r + 1]))
            tiles.append(tile)
    return tiles

def rebuild(tiles, order):
    max_w = max(t.size[0] for t in tiles)
    max_h = max(t.size[1] for t in tiles)
    canvas = Image.new("RGB", (max_w * 4, max_h * 4), "white")
    for pos, src_idx in enumerate(order):
        r, c = divmod(pos, 4)
        tile = tiles[src_idx].resize((max_w, max_h), Image.NEAREST)
        canvas.paste(tile, (c * max_w, r * max_h))
    return canvas

def decode(img):
    decoded = pyzbar.decode(img)
    return decoded

def main():
    img = Image.open(IMG_PATH).convert("RGB")
    tiles = split_4x4(img)
    qr = rebuild(tiles, ORDER)
    qr.save(OUT_PATH)
    print(f"[+] saved: {OUT_PATH}")
    res = decode(qr)
    if res:
        print("[+] flag:", res[0].data.decode())
    else:
        print("[-] decode failed, scan recovered_qr.png manually")

if __name__ == "__main__":
    main()

flagVishwaCTF{t00_sm4rt_t0_us3_brut3_f0rc3}


三、Reverse(逆向工程)

3.1 Messed Up

题目类型:ELF 逆向、栈上隐藏数据异或

背景

  • 程序为 64 位 ELF,静态链接,未 strip。
  • 运行后无论输入何值,均输出 Your Secret "xxx" is invalid!,无成功分支。
  • flag 隐藏于程序初始化时压入栈的两组 8 字节常量中,通过异或运算还原。

解题步骤

步骤1:定位栈上常量
在反汇编中,可发现两组 8 字节常量被压入栈,例如:

  • 第一组地址(如 rsp-0x48rsp-0x10
  • 第二组地址(如 rsp-0x08rsp+0x30

步骤2:异或还原字符串
将对应偏移的常量进行异或,即可得到原始字符串:

[rsp-0x48] ^ [rsp-0x08] = "Enter Se"
[rsp-0x40] ^ [rsp+0x00] = "cret: "
[rsp-0x38] ^ [rsp+0x08] = "VishwaCT"
[rsp-0x30] ^ [rsp+0x10] = "F{50rry_"
[rsp-0x28] ^ [rsp+0x18] = "17_w4s_r"
[rsp-0x20] ^ [rsp+0x20] = "3411y_m3"
[rsp-0x18] ^ [rsp+0x28] = "s53d_UP!"
[rsp-0x10] ^ [rsp+0x30] = "!!}"

拼接后得到完整 flag。

关键脚本(Python)

parts = [
    0x5b70de7570bc89c2 ^ 0x0f33bf0218cfe094,  # VishwaCT
    0x794b3d6bbf3f238f ^ 0x26324f198f0a58c9,  # F{50rry_
    0x212aa49b64d7e65d ^ 0x5375d7af1388d16c,  # 17_w4s_r
    0x90bafc29c34b62bd ^ 0xa3d7a350f27a568e,  # 3411y_m3
    0xa0564f32e8a3db99 ^ 0x81061a6d8c90eeea,  # s53d_UP!
    0x0000000000b4c144 ^ 0x0000000000c9e065,  # !!}
]

flag = b"".join(x.to_bytes(8, "little").rstrip(b"\x00") for x in parts)
print(flag.decode())

flagVishwaCTF{50rry_17_w4s_r3411y_m3s53d_UP!!!}


3.2 ArtGallery

题目类型:Android APK 逆向、Native SO 分析、自定义解密算法

背景

  • APK 包名:com.ctf.artgallery
  • 主要 Activity:MainActivityGalleryActivityCanvasActivity
  • 正常流程进入 GalleryActivity 后,长按图片可跳转到隐藏界面 CanvasActivity
  • CanvasActivity 通过 JNI 调用 native 函数进行反调试检查、信号计算和 flag 解密。

解题步骤

步骤1:定位关键函数

  • CanvasActivity.g() 函数为核心逻辑,依次执行:
    1. 检查 native 库是否加载。
    2. 调用 h() 进行反调试检查(检测调试器连接与执行时间)。
    3. 调用 computeRuntimeSignal() 计算签名相关的信号值。
    4. 调用 native 函数 verifyGateNative(signal) 验证信号。
    5. 调用 native 函数 decryptFlagNative(signal) 解密 flag。

步骤2:分析 Native SO

  • 库文件:libartvault.so
  • 导出函数:verifyGateNativedecryptFlagNative
  • verifyGateNative 逻辑:
    bool verifyGateNative(int x) {
        x ^= 0x6c8e9cf5;
        x = mix32(x);  // 类似 murmur hash 的混合运算
        return x == 0xd15ea5ed;
    }
    
  • decryptFlagNative 逻辑:
    1. .rodata 读取三段 11 字节数据(a, b, c)并按规则交错拼接为 33 字节数组。
    2. 通过固定置换表(33 字节)对数组进行重排。
    3. 使用 LCG 生成密钥流,与重排后的数组逐字节异或。
    4. 对每个字节的高 4 位和低 4 位分别进行 S-box 替换。
    5. 根据字节位置进行循环右移(移位量 = (i % 5) + 1)。

步骤3:编写解密脚本
根据逆向算法,直接实现解密过程得到 flag。

关键脚本(Python)

def ror8(x, r):
    r &= 7
    return ((x >> r) | ((x << (8 - r)) & 0xff)) & 0xff

perm = bytes.fromhex("201c1110080713010500040f12150e1d02160d031e061b090b19140c1f1a180a17")
a = bytes.fromhex("c1e81ff4ac02c6080f6135")
b = bytes.fromhex("999f79b553f3dedc237cf9")
c = bytes.fromhex("9a105eee235d979e4b1fb3")
sbox = bytes.fromhex("06040c050007020e010f030d080a090b")

# step1: interleave 3 chunks into 33 bytes
tmp = bytearray(33)
ia = ib = ic = 0
for i in range(33):
    if i % 3 == 0:
        tmp[i] = a[ia]
        ia += 1
    elif i % 3 == 1:
        tmp[i] = b[ib]
        ib += 1
    else:
        tmp[i] = c[ic]
        ic += 1

# step2: permutation
buf = bytearray(33)
for i, p in enumerate(perm):
    buf[i] = tmp[p]

# step3~5: PRNG xor -> nibble substitution -> rotate right
state = 0x7f4a7c15
res = bytearray(33)
for i in range(33):
    state = (state * 0x19660d + 0x3c6ef35f) & 0xffffffff
    key = (state >> ((i & 3) * 8)) & 0xff
    key ^= ((i * 31) + 0x11) & 0xff
    x = buf[i] ^ key
    hi = sbox[(x >> 4) & 0xf]
    lo = sbox[x & 0xf]
    x = ((hi << 4) | lo) & 0xff
    x = ror8(x, (i % 5) + 1)
    res[i] = x

print(res.decode())

flagVishwaCTF{secret_gallery_exposed}


四、Web(Web 安全)

4.1 Heap of Secrets

题目类型:Chrome 内存快照分析

解题步骤

  1. 使用 Chrome 浏览器打开题目网页。
  2. 按 F12 打开开发者工具,进入 Memory 面板。
  3. 选择 Heap snapshot,点击 Take snapshot 获取堆内存快照。
  4. 在快照中搜索字符串(如 VishwaCTF{),即可找到 flag。

flagVishwaCTF{h34p_5n4p5h0t_1s_D3ep_M3m0ry}


4.2 Keymaster Secrets

题目类型:Web 信息搜集、源码泄露、XXE 漏洞利用

背景

  • 站点伪装成 Apache Syncope v4.0.3 控制台。
  • 题目描述暗示 XML 相关漏洞,关键词:Keymaster Parameters、XXE、外部实体“已修复”。

解题步骤

步骤1:信息搜集

  1. 访问 robots.txt,发现隐藏路径:
    User-agent: *
    Disallow: /maintenance
    Disallow: /api/docs
    Disallow: /rest/
    
  2. 访问 /maintenance,查看页面源码,在注释中发现泄露的凭据:
    <!--
    TODO (ops-team): remove before go-live
    Emergency console access for maintenance window:
     Username : admin
     Password : S3cur3Syncop3!@dm1n
    Temp access expires: 2026-06-01
    NOTE: BI reporting service coming soon on separate port — token in runtime dir
    -->
    

步骤2:利用泄露凭据

  • 使用用户名 admin、密码 S3cur3Syncop3!@dm1n 登录系统。

步骤3:探索 API 文档

  • 访问 /api/docs,发现关键接口 POST /rest/keymaster/params,该接口解析 XML 并提取 <value> 字段。
  • 文档注明“已阻止包含 SYSTEM entity 的 DOCTYPE”,提示可能存在 XXE 漏洞(CVE-2026-23795)。

步骤4:获取 flag

  • 在相关页面源码或响应中直接找到 flag。

flagVishwaCTF{XXE_1nj3ct10n_4p4ch3_sync0p3_CVE-2026-23795}


总结

本教学文档详细解析了 VishwaCTF 2026 中多个赛题的解题思路与关键技术点,涵盖密码学、杂项、逆向工程和 Web 安全等领域。关键知识点包括:

  • 迷宫路径编码:BFS 寻路、坐标变换、比特流转 ASCII。
  • LCG 参数恢复:利用连续输出求模逆元、生成密钥流。
  • Base64/Base58 编码:日志片段拼接、ISO 隐藏数据解码。
  • 二维码拼图:图像处理、碎片重组、解码。
  • 栈数据隐藏:常量异或、静态分析。
  • Android Native 逆向:JNI 分析、自定义解密算法、S-box 与置换。
  • Web 漏洞利用:信息泄露、XXE 漏洞、API 探索。

通过逐步跟随上述方法,可独立复现各题的解密与攻击流程,深入理解相关安全技术。

相似文章
相似文章
 全屏