文章总结: 本文为PKWCTF新生赛MISC方向题解,涵盖图片隐写、文件分离、pyjail沙箱逃逸、流量分析与LSB隐写等题型。通过foremost分离、Python沙箱绕过、SQLite缓存与pcap分析、奥马哈扑克outs枚举等方法获取flag,展示了CTF杂项常见解题思路与工具链。 综合评分: 75 文章分类: CTF,安全工具,渗透测试
PKWCTF新生MISC-“看看就好”
原创
玄网安全 opis 玄网安全 opis
玄网安全
2026年9月27日 18:02 浙江
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
MISC1:你瞅啥
解题过程
附件结构:
browser_cache/cache.db:SQLite 缓存库network/traffic.pcapng:本机回环 HTTP 流量screenshots/:后台截图,其中喵喵喵.png为隐写载体
cache_meta 中有提示 same path, same time, different echo。request_log 记录了 /api/echo?id=101 到 id=160,response_hash 为 hidden_in_pcap。
pcap 中同一接口返回 JSON:{"echo":"<base64>","id":"...","msg":"hello user"}。解码后噪声为 noise:<id>,有效记录为 log_NN:<hex>。按序号拼出:
key=echo_key_2026;target=喵喵喵.png;method=red_lsb
对 喵喵喵.png 取红色通道 LSB(像素行优先、字节 MSB first),截到 cipher=...::END::,再用重复密钥 echo_key_2026 XOR 密文。
#!/usr/bin/env python3
from __future__ import annotations
import base64
import json
import re
from pathlib import Path
from PIL import Image
ROOT = Path(r"E:\opendata\challenge\浮生-challenge")
PCAP = ROOT / "network" / "traffic.pcapng"
IMG_DIR = ROOT / "screenshots"
def bits_to_bytes(bits: list[int]) -> bytes:
out = bytearray()
for i in range(0, len(bits) - 7, 8):
b = 0
for j in range(8):
b = (b << 1) | bits[i + j]
out.append(b)
return bytes(out)
def repeating_xor(data: bytes, key: bytes) -> bytes:
return bytes(d ^ key[i % len(key)] for i, d in enumerate(data))
def main() -> None:
raw = PCAP.read_bytes()
bodies = re.findall(rb'\{"echo":"[^"]+","id":"[^"]+","msg":"[^"]+"\}', raw)
logs: dict[int, str] = {}
for body in bodies:
obj = json.loads(body)
echo = base64.b64decode(obj["echo"]).decode("utf-8")
m = re.fullmatch(r"log_(\d+):([0-9a-fA-F]{2})", echo)
if m:
logs[int(m.group(1))] = m.group(2)
instruction = bytes(int(logs[i], 16) for i in sorted(logs)).decode("utf-8")
fields = dict(part.split("=", 1) for part in instruction.split(";"))
key = fields["key"].encode("utf-8")
target = IMG_DIR / fields["target"]
pixels = list(Image.open(target).convert("RGB").getdata())
payload = bits_to_bytes([p[0] & 1 for p in pixels])
cipher_hex = re.search(rb"cipher=([0-9a-fA-F]+)::END::", payload).group(1)
flag = repeating_xor(bytes.fromhex(cipher_hex.decode()), key).decode("utf-8")
print(flag)
if __name__ == "__main__":
main()
输出:
PKWCTF{cache_never_lies_but_logs_do}
dashboard.png / error.png 以及噪声 echo 是干扰项,可忽略。
MISC5:奥马哈
摘要
动态 nc 服务连续给出 5 局 Pot-Limit Omaha 翻牌圈后局面,要求在 15 秒内计算 k1ne 在河牌上能严格击败三名对手的 outs 数量。核心是枚举剩余 32 张未知牌,并按“手牌恰好 2 张 + 公共牌恰好 3 张”评估成牌。
解题过程
服务地址:nc nc1.ctfplus.cn 15185。每局公开:
- k1ne 的 4 张手牌
- 3 名对手(cq / F1iAz / ddn)各 4 张手牌
- 4 张公共牌(Turn)
- 剩余未知牌:
52 - 4 - 12 - 4 = 32
Outs 定义:河牌发出后,k1ne 的最终牌型必须严格大于所有对手;平局不计入。
第 1 步:实现奥马哈成牌比较
标准 5 张牌型从强到弱:同花顺 > 四条 > 葫芦 > 同花 > 顺子 > 三条 > 两对 > 一对 > 高牌。A 可作为 1 组成 A-2-3-4-5。
奥马哈强制组合:从 4 张手牌中选恰好 2 张,从 5 张公共牌中选恰好 3 张,取所有 C(4,2)*C(5,3)=60 种组合中的最大牌型。比较时用可排序元组(牌型等级 + 主牌 + kickers)。
第 2 步:枚举河牌计数 outs 并连打 5 轮
对每张剩余河牌,分别计算四人的最佳奥马哈牌型,统计 hero > max(villains) 的张数,在 15 秒超时前提交整数。五轮全部正确后给出 flag。
#!/usr/bin/env python3
import re
from itertools import combinations
from collections import Counter
from pwn import *
RANK_MAP = {c: i + 2 for i, c in enumerate("23456789TJQKA")}
SUIT_MAP = {"♠": 0, "♥": 1, "♣": 2, "♦": 3}
ALL_CARDS = [(r, s) for r in range(2, 15) for s in range(4)]
def parse_card(tok):
tok = tok.strip().strip("[],")
return (RANK_MAP[tok[0]], SUIT_MAP[tok[1]])
def parse_card_list(inner):
return [parse_card(p) for p in inner.split(",") if p.strip()]
def eval_5(cards):
ranks = sorted((c[0] for c in cards), reverse=True)
suits = [c[1] for c in cards]
is_flush = len(set(suits)) == 1
cnt = Counter(ranks)
counts = sorted(cnt.values(), reverse=True)
by_freq = sorted(cnt.keys(), key=lambda r: (cnt[r], r), reverse=True)
unique = sorted(set(ranks), reverse=True)
is_straight, straight_high = False, 0
if len(unique) == 5:
if unique[0] - unique[4] == 4:
is_straight, straight_high = True, unique[0]
elif unique == [14, 5, 4, 3, 2]:
is_straight, straight_high = True, 5
if is_straight and is_flush:
return (8, straight_high)
if counts == [4, 1]:
return (7, by_freq[0], by_freq[1])
if counts == [3, 2]:
return (6, by_freq[0], by_freq[1])
if is_flush:
return (5, *ranks)
if is_straight:
return (4, straight_high)
if counts == [3, 1, 1]:
kickers = sorted((r for r in ranks if r != by_freq[0]), reverse=True)
return (3, by_freq[0], *kickers)
if counts == [2, 2, 1]:
pairs = sorted((r for r, c in cnt.items() if c == 2), reverse=True)
kicker = next(r for r, c in cnt.items() if c == 1)
return (2, pairs[0], pairs[1], kicker)
if counts == [2, 1, 1, 1]:
kickers = sorted((r for r in ranks if r != by_freq[0]), reverse=True)
return (1, by_freq[0], *kickers)
return (0, *ranks)
def omaha_best(hole, board):
best = None
for h in combinations(hole, 2):
for b in combinations(board, 3):
score = eval_5(h + b)
if best is None or score > best:
best = score
return best
def count_outs(hero, villains, board):
known = set(hero) | set(board)
for v in villains:
known.update(v)
outs = 0
for river in (c for c in ALL_CARDS if c not in known):
full = board + [river]
hs = omaha_best(hero, full)
if all(hs > omaha_best(v, full) for v in villains):
outs += 1
return outs
def parse_round(text):
def grab(pat):
return parse_card_list(re.search(pat, text).group(1))
return (
grab(r"k1ne[^\n]*\[([^\]]+)\]"),
[
grab(r"cq[^\n]*\[([^\]]+)\]"),
grab(r"F1iAz[^\n]*\[([^\]]+)\]"),
grab(r"ddn[^\n]*\[([^\]]+)\]"),
],
grab(r"Board[^\n]*\[([^\]]+)\]"),
)
def main():
r = remote("nc1.ctfplus.cn", 15185, timeout=20)
for _ in range(5):
data = r.recvuntil(b"(15s) > ", timeout=18)
hero, villains, board = parse_round(data.decode("utf-8", errors="replace"))
r.sendline(str(count_outs(hero, villains, board)).encode())
print(r.recvall(timeout=8).decode("utf-8", errors="replace"))
r.close()
if __name__ == "__main__":
main()
实战五轮答案分别为 32 / 3 / 8 / 0 / 4,服务返回:
[+] Incredible! You stacked them all!
[+] Here is your flag: PKWCTF{2dea34f3-8ecc-46e5-8067-992ae3505a7a}
注意:recvuntil(b"> ") 会在规则文本 Full House > Flush 处提前截断,必须匹配完整提示 (15s) >。
MISC6:黑白之间
附件 1.png 是一张 19×19 围棋棋盘。空交叉点是分隔符,黑子和白子按行读成二进制,黑为 0、白为 1。
解题过程
第 1 步:定位棋子
图片只有少量纯色。黑子中心是 (16, 16, 16),白子中心是接近纯白的像素。对这两类连通块取质心,再把横坐标、纵坐标各自聚类,得到 19×19 的交叉点。结果是 110 颗黑子、122 颗白子,其余交叉点为空。
第 2 步:按行解码
空点不编码。从左到右、从上到下只读取有棋子的点:黑子记为 0,白子记为 1。232 bit 正好是 29 字节,按 8 bit 一组转成 ASCII。
import numpy as np
from PIL import Image
from scipy import ndimage
PNG_PATH = r"1.png"
def stone_centers(mask):
labels, count = ndimage.label(mask)
centers = []
for i in range(1, count + 1):
ys, xs = np.where(labels == i)
centers.append((float(xs.mean()), float(ys.mean())))
return centers
def cluster(values, tolerance=8):
values = sorted(values)
groups = [[values[0]]]
for value in values[1:]:
if value - np.mean(groups[-1]) <= tolerance:
groups[-1].append(value)
else:
groups.append([value])
return [float(np.mean(group)) for group in groups]
def nearest(value, lines):
return int(np.argmin(np.abs(np.array(lines) - value)))
def main():
arr = np.array(Image.open(PNG_PATH))
black = stone_centers(np.all(arr < 40, axis=2))
white = stone_centers(np.all(arr > 230, axis=2))
xs = cluster([x for x, _ in black + white])
ys = cluster([y for _, y in black + white])
grid = np.full((len(ys), len(xs)), -1, np.int8)
for x, y in black:
grid[nearest(y, ys), nearest(x, xs)] = 0
for x, y in white:
grid[nearest(y, ys), nearest(x, xs)] = 1
bits = [int(v) for row in grid for v in row if v != -1]
out = bytearray()
for i in range(0, len(bits) - 7, 8):
byte = 0
for bit in bits[i : i + 8]:
byte = (byte << 1) | bit
out.append(byte)
print(out.decode("ascii"))
if __name__ == "__main__":
main()
依赖:numpy、Pillow、scipy。输出:
PKWCTF{b1ack_is_0_whit3_1S_1}
MISC7:正在发动鬼脑
附件 电脑也是脑.zip 的注释是密钥,data.txt 用四种零宽字符携带一份 zlib 压缩的像素编码器说明。按说明把可见字符还原成黑白图,图中的文字就是 flag。
解题过程
第 1 步:取出密钥和编码器说明
ZIP 全局注释是 53d26425,即 4 字节密钥。data.txt 中夹有 U+200B、U+200C、U+200D、U+2060。按这个顺序各对应 00、01、10、11,高位在前拼成字节。结果以 ZW1 开头,偏移 7 处是 zlib,解压后得到编码器规则。
规则要点:
- 忽略换行和这四种零宽字符,可见字符每 9 个一组。
- 每组用
SHA256(K || uint32_be(组号) || 字符)对 62 个字母数字排序。前 31 个表示黑(1),其余表示白(0)。 - 校验位在
s % 9,初始s = 93。去掉校验位后得到 8 个数据位v,再执行s = (33*s + v + 组号) % 256。 - 数据位按 8×8 小块存放。块来源下标
t = (5*k + 173) % 块数,奇数块的对角扫描倒序,旋转为r = (7*t + 3) % 4。 - 还原后的像素必须匹配规则中的 SHA256。
第 2 步:逆变换并读图
216576 个数据位等于 3384 个小块,排成 94×36。按上面的规则逆置换、逆扫描、逆旋转后,白底黑字图片的 SHA256 为 de46c7564c271c524cf4ff6df51309e49711b23fbb78cde7a11f3e1a879940e9,与规则一致。图片内容是:
PKWCTF{F4q_m3rm3r_z3n_m
3_zh3_m3_hu41}
import hashlib
import struct
import zipfile
import zlib
import numpy as np
from PIL import Image
ZIP_PATH = r"电脑也是脑.zip"
OUT_PATH = r"brain_flag.png"
ZW = {"\u200b": 0, "\u200c": 1, "\u200d": 2, "\u2060": 3}
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
IGNORE = set("\r\n\u200b\u200c\u200d\u2060")
def zw_to_zlib(text):
bits = []
for ch in text:
if ch not in ZW:
continue
value = ZW[ch]
bits.append((value >> 1) & 1)
bits.append(value & 1)
raw = bytearray()
for i in range(0, len(bits) - 7, 8):
byte = 0
for bit in bits[i : i + 8]:
byte = (byte << 1) | bit
raw.append(byte)
start = raw.find(b"\x78\xda")
return zlib.decompress(bytes(raw[start:]))
def char_bit(key, group, ch):
scores = []
prefix = key + struct.pack(">I", group)
for item in ALPHABET:
digest = hashlib.sha256(prefix + item.encode("ascii")).digest()
scores.append((digest, item))
scores.sort()
order = [item for _, item in scores]
return 1 if ch in order[:31] else 0
def recover_bits(text, key):
visible = [ch for ch in text if ch not in IGNORE]
state = 93
bits = []
for group, start in enumerate(range(0, len(visible), 9)):
chunk = visible[start : start + 9]
nine = [char_bit(key, group, ch) for ch in chunk]
check_at = state % 9
eight = nine[:check_at] + nine[check_at + 1 :]
value = 0
for bit in eight:
value = (value << 1) | bit
bits.extend(eight)
state = (33 * state + value + group) % 256
return bits
def diagonal_scan(size=8):
order = []
for s in range(2 * size - 1):
coords = [(x, s - x) for x in range(size) if 0 <= s - x < size]
if s % 2:
coords.reverse()
order.extend(coords)
return order
def rotate(rotation, x, y, size=8):
if rotation == 0:
return x, y
if rotation == 1:
return y, size - 1 - x
if rotation == 2:
return size - 1 - x, size - 1 - y
return size - 1 - y, x
def pixels(bits, tiles_x=94, tiles_y=36):
tile_count = tiles_x * tiles_y
tiles = [bits[i * 64 : (i + 1) * 64] for i in range(tile_count)]
source = [None] * tile_count
for k, tile in enumerate(tiles):
t = (5 * k + 173) % tile_count
source[t] = tile
scan = diagonal_scan()
image = np.zeros((tiles_y * 8, tiles_x * 8), np.uint8)
for t, tile in enumerate(source):
seq = scan[::-1] if t % 2 else scan
rotation = (7 * t + 3) % 4
canvas = np.zeros((8, 8), np.uint8)
for bit, (x, y) in zip(tile, seq):
sx, sy = rotate(rotation, x, y)
canvas[sy, sx] = bit
ty, tx = divmod(t, tiles_x)
image[ty * 8 : (ty + 1) * 8, tx * 8 : (tx + 1) * 8] = canvas
return image
def main():
with zipfile.ZipFile(ZIP_PATH) as zf:
text = zf.read("data.txt").decode("utf-8")
key = bytes.fromhex(zf.comment.decode("ascii"))
image = pixels(recover_bits(text, key))
digest = hashlib.sha256(image.reshape(-1).astype(np.uint8).tobytes()).hexdigest()
assert digest == "de46c7564c271c524cf4ff6df51309e49711b23fbb78cde7a11f3e1a879940e9"
Image.fromarray((1 - image) * 255).save(OUT_PATH)
print("PKWCTF{F4q_m3rm3r_z3n_m3_zh3_m3_hu41}")
if __name__ == "__main__":
main()
依赖:numpy、Pillow。把 ZIP_PATH 改成附件解压后的 电脑也是脑.zip。脚本校验像素哈希并保存 brain_flag.png,图中文字与打印的 flag 相同。
PKWCTF{F4q_m3rm3r_z3n_m3_zh3_m3_hu41}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:玄网安全 玄网安全 opis 玄网安全 opis《PKWCTF新生MISC-“看看就好”》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论