VishwaCTF 2026 Writeup 教学文档
概述
本教学文档基于《VishwaCTF 2026 wp》链接内容整理,旨在详细解析各赛题的解题思路、技术原理与实现方法,涵盖 Cryptography、MISC、Reverse 和 Web 等多个方向。文档将按题目分类,逐一拆解关键步骤,并提供完整的解题脚本与操作指南。
一、Cryptography(密码学)
1.1 PROCEDURAL LABYRINTH
题目类型:迷宫路径编码转换
核心任务:
- 从迷宫矩阵中找出从入口(
>)到出口(<)的唯一可行路径。 - 将该路径转换为比特流,再解码为 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}
flag:VishwaCTF{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())
flag:VishwaCTF{s33ds_4r3_n3v3r_s4f3_1ns1d3_pr1m3s_4nd_n01s3}
二、MISC(杂项)
2.1 Fragmented Evidence
题目类型:日志中的 Base64 片段拼接
解题步骤:
- 打开日志文件
server.log,搜索异常字段packet anomaly detected id=和backup failed code=。 - 提取等号后的字符串,按出现顺序拼接:
Zmxh Z3tP YmZ1 c2Nh dGVk VHJ1dGh9 - 拼接后得到完整 Base64 字符串:
ZmxhZ3tPYmZ1c2NhdGVkVHJ1dGh9 - Base64 解码得到:
flag{ObfuscatedTruth} - 转换为标准 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}}}")
flag:VishwaCTF{ObfuscatedTruth}
2.2 BOOT
题目类型:ISO 镜像隐藏数据提取(Base58 编码)
背景:
- 题目提供的 ISO 镜像基于 TinyCore 17.0 修改,系统本体未变,但在引导层
isolinux.bin中隐藏了数据。 - 在
isolinux.bin的偏移0x6000处发现重复三行的字符串:9d5zYNcj9f1S46vC74aruPgE9b6T3ceX4Qsa2VQAbVnDFfTr
解题步骤:
- 该字符串类似 Base64 但解码为乱码,尝试 Base58 解码。
- 使用 Base58 字母表:
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz - 解码得到 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())
flag:VishwaCTF{iM4G35_4Re_n0t_th4T_c00L}
2.3 UnderWorld - P1
题目类型:Minecraft 地图探索
解题步骤:
- 在 Minecraft 地图中,于龙身上的桶内发现钻石,其自定义名称为:
No Emeralds here - 根据 CTF 命名惯例,转换为 flag 格式。
flag:VishwaCTF{no_emeralds_here}
2.4 UnderWorld - P2
题目类型:Minecraft 地图探索
解题步骤:
- 在龙头附近的箱子中,27 个钻石按槽位自定义名称拼出完整字符串。
flag:VishwaCTF{m1n3cr4f7_15_fun}
2.5 The-Lost-Client
题目类型:PNG 元数据分析与推理
解题步骤:
- 分析 PNG 文件的 XMP 元数据,发现以下字段:
creator = "Tata and........."(9 个点暗示 9 字母姓氏)UserComment = "Its a generational business family"title = "V!"
- 结合“generational business family”和“V!”,推测指代人物为 Vijaypat Singhania(印度著名商业家族成员)。
- 进一步查询公开资料,确认 Vijaypat Singhania 曾担任 Indian Institute of Management Ahmedabad(IIMA)的董事会主席(任期 2007-03-29 至 2012-03-28)。
- 因此,题目要求的机构缩写为 IIMA。
flag:IIMA(需根据题目上下文确认是否为最终提交格式)
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
解题步骤:
- 将 16 个碎片图片按上述顺序排列成 4×4 网格。
- 拼接成完整二维码图片。
- 使用二维码解码库(如
qrcode、pyzbar)或手动扫描得到 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()
flag:VishwaCTF{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-0x48至rsp-0x10) - 第二组地址(如
rsp-0x08至rsp+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())
flag:VishwaCTF{50rry_17_w4s_r3411y_m3s53d_UP!!!}
3.2 ArtGallery
题目类型:Android APK 逆向、Native SO 分析、自定义解密算法
背景:
- APK 包名:
com.ctf.artgallery - 主要 Activity:
MainActivity、GalleryActivity、CanvasActivity - 正常流程进入
GalleryActivity后,长按图片可跳转到隐藏界面CanvasActivity。 CanvasActivity通过 JNI 调用 native 函数进行反调试检查、信号计算和 flag 解密。
解题步骤:
步骤1:定位关键函数
CanvasActivity.g()函数为核心逻辑,依次执行:- 检查 native 库是否加载。
- 调用
h()进行反调试检查(检测调试器连接与执行时间)。 - 调用
computeRuntimeSignal()计算签名相关的信号值。 - 调用 native 函数
verifyGateNative(signal)验证信号。 - 调用 native 函数
decryptFlagNative(signal)解密 flag。
步骤2:分析 Native SO
- 库文件:
libartvault.so - 导出函数:
verifyGateNative、decryptFlagNative verifyGateNative逻辑:bool verifyGateNative(int x) { x ^= 0x6c8e9cf5; x = mix32(x); // 类似 murmur hash 的混合运算 return x == 0xd15ea5ed; }decryptFlagNative逻辑:- 从
.rodata读取三段 11 字节数据(a, b, c)并按规则交错拼接为 33 字节数组。 - 通过固定置换表(33 字节)对数组进行重排。
- 使用 LCG 生成密钥流,与重排后的数组逐字节异或。
- 对每个字节的高 4 位和低 4 位分别进行 S-box 替换。
- 根据字节位置进行循环右移(移位量 = (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())
flag:VishwaCTF{secret_gallery_exposed}
四、Web(Web 安全)
4.1 Heap of Secrets
题目类型:Chrome 内存快照分析
解题步骤:
- 使用 Chrome 浏览器打开题目网页。
- 按 F12 打开开发者工具,进入 Memory 面板。
- 选择 Heap snapshot,点击 Take snapshot 获取堆内存快照。
- 在快照中搜索字符串(如
VishwaCTF{),即可找到 flag。
flag:VishwaCTF{h34p_5n4p5h0t_1s_D3ep_M3m0ry}
4.2 Keymaster Secrets
题目类型:Web 信息搜集、源码泄露、XXE 漏洞利用
背景:
- 站点伪装成 Apache Syncope v4.0.3 控制台。
- 题目描述暗示 XML 相关漏洞,关键词:Keymaster Parameters、XXE、外部实体“已修复”。
解题步骤:
步骤1:信息搜集
- 访问
robots.txt,发现隐藏路径:User-agent: * Disallow: /maintenance Disallow: /api/docs Disallow: /rest/ - 访问
/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。
flag:VishwaCTF{XXE_1nj3ct10n_4p4ch3_sync0p3_CVE-2026-23795}
总结
本教学文档详细解析了 VishwaCTF 2026 中多个赛题的解题思路与关键技术点,涵盖密码学、杂项、逆向工程和 Web 安全等领域。关键知识点包括:
- 迷宫路径编码:BFS 寻路、坐标变换、比特流转 ASCII。
- LCG 参数恢复:利用连续输出求模逆元、生成密钥流。
- Base64/Base58 编码:日志片段拼接、ISO 隐藏数据解码。
- 二维码拼图:图像处理、碎片重组、解码。
- 栈数据隐藏:常量异或、静态分析。
- Android Native 逆向:JNI 分析、自定义解密算法、S-box 与置换。
- Web 漏洞利用:信息泄露、XXE 漏洞、API 探索。
通过逐步跟随上述方法,可独立复现各题的解密与攻击流程,深入理解相关安全技术。