PKWCTF新生MISC-“看看就好”

admin 2026-09-30 05:41:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文为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&nbsp;__future__&nbsp;import&nbsp;annotations

import&nbsp;base64
import&nbsp;json
import&nbsp;re
from&nbsp;pathlib&nbsp;import&nbsp;Path

from&nbsp;PIL&nbsp;import&nbsp;Image

ROOT = Path(r"E:\opendata\challenge\浮生-challenge")
PCAP = ROOT /&nbsp;"network"&nbsp;/&nbsp;"traffic.pcapng"
IMG_DIR = ROOT /&nbsp;"screenshots"

def&nbsp;bits_to_bytes(bits: list[int])&nbsp;-> bytes:
&nbsp; &nbsp; out = bytearray()
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(bits) -&nbsp;7,&nbsp;8):
&nbsp; &nbsp; &nbsp; &nbsp; b =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;in&nbsp;range(8):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b = (b <<&nbsp;1) | bits[i + j]
&nbsp; &nbsp; &nbsp; &nbsp; out.append(b)
&nbsp; &nbsp;&nbsp;return&nbsp;bytes(out)

def&nbsp;repeating_xor(data: bytes, key: bytes)&nbsp;-> bytes:
&nbsp; &nbsp;&nbsp;return&nbsp;bytes(d ^ key[i % len(key)]&nbsp;for&nbsp;i, d&nbsp;in&nbsp;enumerate(data))

def&nbsp;main()&nbsp;->&nbsp;None:
&nbsp; &nbsp; raw = PCAP.read_bytes()
&nbsp; &nbsp; bodies = re.findall(rb'\{"echo":"[^"]+","id":"[^"]+","msg":"[^"]+"\}', raw)

&nbsp; &nbsp; logs: dict[int, str] = {}
&nbsp; &nbsp;&nbsp;for&nbsp;body&nbsp;in&nbsp;bodies:
&nbsp; &nbsp; &nbsp; &nbsp; obj = json.loads(body)
&nbsp; &nbsp; &nbsp; &nbsp; echo = base64.b64decode(obj["echo"]).decode("utf-8")
&nbsp; &nbsp; &nbsp; &nbsp; m = re.fullmatch(r"log_(\d+):([0-9a-fA-F]{2})", echo)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;m:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; logs[int(m.group(1))] = m.group(2)

&nbsp; &nbsp; instruction = bytes(int(logs[i],&nbsp;16)&nbsp;for&nbsp;i&nbsp;in&nbsp;sorted(logs)).decode("utf-8")
&nbsp; &nbsp; fields = dict(part.split("=",&nbsp;1)&nbsp;for&nbsp;part&nbsp;in&nbsp;instruction.split(";"))
&nbsp; &nbsp; key = fields["key"].encode("utf-8")
&nbsp; &nbsp; target = IMG_DIR / fields["target"]

&nbsp; &nbsp; pixels = list(Image.open(target).convert("RGB").getdata())
&nbsp; &nbsp; payload = bits_to_bytes([p[0] &&nbsp;1&nbsp;for&nbsp;p&nbsp;in&nbsp;pixels])
&nbsp; &nbsp; cipher_hex = re.search(rb"cipher=([0-9a-fA-F]+)::END::", payload).group(1)
&nbsp; &nbsp; flag = repeating_xor(bytes.fromhex(cipher_hex.decode()), key).decode("utf-8")
&nbsp; &nbsp; print(flag)

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; 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&nbsp;re
from&nbsp;itertools&nbsp;import&nbsp;combinations
from&nbsp;collections&nbsp;import&nbsp;Counter
from&nbsp;pwn&nbsp;import&nbsp;*

RANK_MAP = {c: i +&nbsp;2&nbsp;for&nbsp;i, c&nbsp;in&nbsp;enumerate("23456789TJQKA")}
SUIT_MAP = {"♠":&nbsp;0,&nbsp;"♥":&nbsp;1,&nbsp;"♣":&nbsp;2,&nbsp;"♦":&nbsp;3}
ALL_CARDS = [(r, s)&nbsp;for&nbsp;r&nbsp;in&nbsp;range(2,&nbsp;15)&nbsp;for&nbsp;s&nbsp;in&nbsp;range(4)]

def&nbsp;parse_card(tok):
&nbsp; &nbsp; tok = tok.strip().strip("[],")
&nbsp; &nbsp;&nbsp;return&nbsp;(RANK_MAP[tok[0]], SUIT_MAP[tok[1]])

def&nbsp;parse_card_list(inner):
&nbsp; &nbsp;&nbsp;return&nbsp;[parse_card(p)&nbsp;for&nbsp;p&nbsp;in&nbsp;inner.split(",")&nbsp;if&nbsp;p.strip()]

def&nbsp;eval_5(cards):
&nbsp; &nbsp; ranks = sorted((c[0]&nbsp;for&nbsp;c&nbsp;in&nbsp;cards), reverse=True)
&nbsp; &nbsp; suits = [c[1]&nbsp;for&nbsp;c&nbsp;in&nbsp;cards]
&nbsp; &nbsp; is_flush = len(set(suits)) ==&nbsp;1
&nbsp; &nbsp; cnt = Counter(ranks)
&nbsp; &nbsp; counts = sorted(cnt.values(), reverse=True)
&nbsp; &nbsp; by_freq = sorted(cnt.keys(), key=lambda&nbsp;r: (cnt[r], r), reverse=True)
&nbsp; &nbsp; unique = sorted(set(ranks), reverse=True)
&nbsp; &nbsp; is_straight, straight_high =&nbsp;False,&nbsp;0
&nbsp; &nbsp;&nbsp;if&nbsp;len(unique) ==&nbsp;5:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;unique[0] - unique[4] ==&nbsp;4:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_straight, straight_high =&nbsp;True, unique[0]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;unique == [14,&nbsp;5,&nbsp;4,&nbsp;3,&nbsp;2]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_straight, straight_high =&nbsp;True,&nbsp;5
&nbsp; &nbsp;&nbsp;if&nbsp;is_straight&nbsp;and&nbsp;is_flush:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(8, straight_high)
&nbsp; &nbsp;&nbsp;if&nbsp;counts == [4,&nbsp;1]:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(7, by_freq[0], by_freq[1])
&nbsp; &nbsp;&nbsp;if&nbsp;counts == [3,&nbsp;2]:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(6, by_freq[0], by_freq[1])
&nbsp; &nbsp;&nbsp;if&nbsp;is_flush:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(5, *ranks)
&nbsp; &nbsp;&nbsp;if&nbsp;is_straight:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(4, straight_high)
&nbsp; &nbsp;&nbsp;if&nbsp;counts == [3,&nbsp;1,&nbsp;1]:
&nbsp; &nbsp; &nbsp; &nbsp; kickers = sorted((r&nbsp;for&nbsp;r&nbsp;in&nbsp;ranks&nbsp;if&nbsp;r != by_freq[0]), reverse=True)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(3, by_freq[0], *kickers)
&nbsp; &nbsp;&nbsp;if&nbsp;counts == [2,&nbsp;2,&nbsp;1]:
&nbsp; &nbsp; &nbsp; &nbsp; pairs = sorted((r&nbsp;for&nbsp;r, c&nbsp;in&nbsp;cnt.items()&nbsp;if&nbsp;c ==&nbsp;2), reverse=True)
&nbsp; &nbsp; &nbsp; &nbsp; kicker = next(r&nbsp;for&nbsp;r, c&nbsp;in&nbsp;cnt.items()&nbsp;if&nbsp;c ==&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(2, pairs[0], pairs[1], kicker)
&nbsp; &nbsp;&nbsp;if&nbsp;counts == [2,&nbsp;1,&nbsp;1,&nbsp;1]:
&nbsp; &nbsp; &nbsp; &nbsp; kickers = sorted((r&nbsp;for&nbsp;r&nbsp;in&nbsp;ranks&nbsp;if&nbsp;r != by_freq[0]), reverse=True)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(1, by_freq[0], *kickers)
&nbsp; &nbsp;&nbsp;return&nbsp;(0, *ranks)

def&nbsp;omaha_best(hole, board):
&nbsp; &nbsp; best =&nbsp;None
&nbsp; &nbsp;&nbsp;for&nbsp;h&nbsp;in&nbsp;combinations(hole,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;b&nbsp;in&nbsp;combinations(board,&nbsp;3):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score = eval_5(h + b)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;best&nbsp;is&nbsp;None&nbsp;or&nbsp;score > best:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; best = score
&nbsp; &nbsp;&nbsp;return&nbsp;best

def&nbsp;count_outs(hero, villains, board):
&nbsp; &nbsp; known = set(hero) | set(board)
&nbsp; &nbsp;&nbsp;for&nbsp;v&nbsp;in&nbsp;villains:
&nbsp; &nbsp; &nbsp; &nbsp; known.update(v)
&nbsp; &nbsp; outs =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;river&nbsp;in&nbsp;(c&nbsp;for&nbsp;c&nbsp;in&nbsp;ALL_CARDS&nbsp;if&nbsp;c&nbsp;not&nbsp;in&nbsp;known):
&nbsp; &nbsp; &nbsp; &nbsp; full = board + [river]
&nbsp; &nbsp; &nbsp; &nbsp; hs = omaha_best(hero, full)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;all(hs > omaha_best(v, full)&nbsp;for&nbsp;v&nbsp;in&nbsp;villains):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; outs +=&nbsp;1
&nbsp; &nbsp;&nbsp;return&nbsp;outs

def&nbsp;parse_round(text):
&nbsp; &nbsp;&nbsp;def&nbsp;grab(pat):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;parse_card_list(re.search(pat, text).group(1))
&nbsp; &nbsp;&nbsp;return&nbsp;(
&nbsp; &nbsp; &nbsp; &nbsp; grab(r"k1ne[^\n]*\[([^\]]+)\]"),
&nbsp; &nbsp; &nbsp; &nbsp; [
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; grab(r"cq[^\n]*\[([^\]]+)\]"),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; grab(r"F1iAz[^\n]*\[([^\]]+)\]"),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; grab(r"ddn[^\n]*\[([^\]]+)\]"),
&nbsp; &nbsp; &nbsp; &nbsp; ],
&nbsp; &nbsp; &nbsp; &nbsp; grab(r"Board[^\n]*\[([^\]]+)\]"),
&nbsp; &nbsp; )

def&nbsp;main():
&nbsp; &nbsp; r = remote("nc1.ctfplus.cn",&nbsp;15185, timeout=20)
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;in&nbsp;range(5):
&nbsp; &nbsp; &nbsp; &nbsp; data = r.recvuntil(b"(15s) > ", timeout=18)
&nbsp; &nbsp; &nbsp; &nbsp; hero, villains, board = parse_round(data.decode("utf-8", errors="replace"))
&nbsp; &nbsp; &nbsp; &nbsp; r.sendline(str(count_outs(hero, villains, board)).encode())
&nbsp; &nbsp; print(r.recvall(timeout=8).decode("utf-8", errors="replace"))
&nbsp; &nbsp; r.close()

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; 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&nbsp;numpy&nbsp;as&nbsp;np
from&nbsp;PIL&nbsp;import&nbsp;Image
from&nbsp;scipy&nbsp;import&nbsp;ndimage

PNG_PATH =&nbsp;r"1.png"

def&nbsp;stone_centers(mask):
&nbsp; &nbsp; labels, count = ndimage.label(mask)
&nbsp; &nbsp; centers = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(1, count +&nbsp;1):
&nbsp; &nbsp; &nbsp; &nbsp; ys, xs = np.where(labels == i)
&nbsp; &nbsp; &nbsp; &nbsp; centers.append((float(xs.mean()), float(ys.mean())))
&nbsp; &nbsp;&nbsp;return&nbsp;centers

def&nbsp;cluster(values, tolerance=8):
&nbsp; &nbsp; values = sorted(values)
&nbsp; &nbsp; groups = [[values[0]]]
&nbsp; &nbsp;&nbsp;for&nbsp;value&nbsp;in&nbsp;values[1:]:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;value - np.mean(groups[-1]) <= tolerance:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; groups[-1].append(value)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; groups.append([value])
&nbsp; &nbsp;&nbsp;return&nbsp;[float(np.mean(group))&nbsp;for&nbsp;group&nbsp;in&nbsp;groups]

def&nbsp;nearest(value, lines):
&nbsp; &nbsp;&nbsp;return&nbsp;int(np.argmin(np.abs(np.array(lines) - value)))

def&nbsp;main():
&nbsp; &nbsp; arr = np.array(Image.open(PNG_PATH))
&nbsp; &nbsp; black = stone_centers(np.all(arr <&nbsp;40, axis=2))
&nbsp; &nbsp; white = stone_centers(np.all(arr >&nbsp;230, axis=2))
&nbsp; &nbsp; xs = cluster([x&nbsp;for&nbsp;x, _&nbsp;in&nbsp;black + white])
&nbsp; &nbsp; ys = cluster([y&nbsp;for&nbsp;_, y&nbsp;in&nbsp;black + white])
&nbsp; &nbsp; grid = np.full((len(ys), len(xs)),&nbsp;-1, np.int8)
&nbsp; &nbsp;&nbsp;for&nbsp;x, y&nbsp;in&nbsp;black:
&nbsp; &nbsp; &nbsp; &nbsp; grid[nearest(y, ys), nearest(x, xs)] =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;x, y&nbsp;in&nbsp;white:
&nbsp; &nbsp; &nbsp; &nbsp; grid[nearest(y, ys), nearest(x, xs)] =&nbsp;1

&nbsp; &nbsp; bits = [int(v)&nbsp;for&nbsp;row&nbsp;in&nbsp;grid&nbsp;for&nbsp;v&nbsp;in&nbsp;row&nbsp;if&nbsp;v !=&nbsp;-1]
&nbsp; &nbsp; out = bytearray()
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(bits) -&nbsp;7,&nbsp;8):
&nbsp; &nbsp; &nbsp; &nbsp; byte =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;bit&nbsp;in&nbsp;bits[i : i +&nbsp;8]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byte = (byte <<&nbsp;1) | bit
&nbsp; &nbsp; &nbsp; &nbsp; out.append(byte)
&nbsp; &nbsp; print(out.decode("ascii"))

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; 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&nbsp;hashlib
import&nbsp;struct
import&nbsp;zipfile
import&nbsp;zlib

import&nbsp;numpy&nbsp;as&nbsp;np
from&nbsp;PIL&nbsp;import&nbsp;Image

ZIP_PATH =&nbsp;r"电脑也是脑.zip"
OUT_PATH =&nbsp;r"brain_flag.png"

ZW = {"\u200b":&nbsp;0,&nbsp;"\u200c":&nbsp;1,&nbsp;"\u200d":&nbsp;2,&nbsp;"\u2060":&nbsp;3}
ALPHABET =&nbsp;"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
IGNORE = set("\r\n\u200b\u200c\u200d\u2060")

def&nbsp;zw_to_zlib(text):
&nbsp; &nbsp; bits = []
&nbsp; &nbsp;&nbsp;for&nbsp;ch&nbsp;in&nbsp;text:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ch&nbsp;not&nbsp;in&nbsp;ZW:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp; value = ZW[ch]
&nbsp; &nbsp; &nbsp; &nbsp; bits.append((value >>&nbsp;1) &&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp; bits.append(value &&nbsp;1)
&nbsp; &nbsp; raw = bytearray()
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, len(bits) -&nbsp;7,&nbsp;8):
&nbsp; &nbsp; &nbsp; &nbsp; byte =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;bit&nbsp;in&nbsp;bits[i : i +&nbsp;8]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byte = (byte <<&nbsp;1) | bit
&nbsp; &nbsp; &nbsp; &nbsp; raw.append(byte)
&nbsp; &nbsp; start = raw.find(b"\x78\xda")
&nbsp; &nbsp;&nbsp;return&nbsp;zlib.decompress(bytes(raw[start:]))

def&nbsp;char_bit(key, group, ch):
&nbsp; &nbsp; scores = []
&nbsp; &nbsp; prefix = key + struct.pack(">I", group)
&nbsp; &nbsp;&nbsp;for&nbsp;item&nbsp;in&nbsp;ALPHABET:
&nbsp; &nbsp; &nbsp; &nbsp; digest = hashlib.sha256(prefix + item.encode("ascii")).digest()
&nbsp; &nbsp; &nbsp; &nbsp; scores.append((digest, item))
&nbsp; &nbsp; scores.sort()
&nbsp; &nbsp; order = [item&nbsp;for&nbsp;_, item&nbsp;in&nbsp;scores]
&nbsp; &nbsp;&nbsp;return&nbsp;1&nbsp;if&nbsp;ch&nbsp;in&nbsp;order[:31]&nbsp;else&nbsp;0

def&nbsp;recover_bits(text, key):
&nbsp; &nbsp; visible = [ch&nbsp;for&nbsp;ch&nbsp;in&nbsp;text&nbsp;if&nbsp;ch&nbsp;not&nbsp;in&nbsp;IGNORE]
&nbsp; &nbsp; state =&nbsp;93
&nbsp; &nbsp; bits = []
&nbsp; &nbsp;&nbsp;for&nbsp;group, start&nbsp;in&nbsp;enumerate(range(0, len(visible),&nbsp;9)):
&nbsp; &nbsp; &nbsp; &nbsp; chunk = visible[start : start +&nbsp;9]
&nbsp; &nbsp; &nbsp; &nbsp; nine = [char_bit(key, group, ch)&nbsp;for&nbsp;ch&nbsp;in&nbsp;chunk]
&nbsp; &nbsp; &nbsp; &nbsp; check_at = state %&nbsp;9
&nbsp; &nbsp; &nbsp; &nbsp; eight = nine[:check_at] + nine[check_at +&nbsp;1&nbsp;:]
&nbsp; &nbsp; &nbsp; &nbsp; value =&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;bit&nbsp;in&nbsp;eight:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value = (value <<&nbsp;1) | bit
&nbsp; &nbsp; &nbsp; &nbsp; bits.extend(eight)
&nbsp; &nbsp; &nbsp; &nbsp; state = (33&nbsp;* state + value + group) %&nbsp;256
&nbsp; &nbsp;&nbsp;return&nbsp;bits

def&nbsp;diagonal_scan(size=8):
&nbsp; &nbsp; order = []
&nbsp; &nbsp;&nbsp;for&nbsp;s&nbsp;in&nbsp;range(2&nbsp;* size -&nbsp;1):
&nbsp; &nbsp; &nbsp; &nbsp; coords = [(x, s - x)&nbsp;for&nbsp;x&nbsp;in&nbsp;range(size)&nbsp;if&nbsp;0&nbsp;<= s - x < size]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;s %&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; coords.reverse()
&nbsp; &nbsp; &nbsp; &nbsp; order.extend(coords)
&nbsp; &nbsp;&nbsp;return&nbsp;order

def&nbsp;rotate(rotation, x, y, size=8):
&nbsp; &nbsp;&nbsp;if&nbsp;rotation ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;x, y
&nbsp; &nbsp;&nbsp;if&nbsp;rotation ==&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;y, size -&nbsp;1&nbsp;- x
&nbsp; &nbsp;&nbsp;if&nbsp;rotation ==&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;size -&nbsp;1&nbsp;- x, size -&nbsp;1&nbsp;- y
&nbsp; &nbsp;&nbsp;return&nbsp;size -&nbsp;1&nbsp;- y, x

def&nbsp;pixels(bits, tiles_x=94, tiles_y=36):
&nbsp; &nbsp; tile_count = tiles_x * tiles_y
&nbsp; &nbsp; tiles = [bits[i *&nbsp;64&nbsp;: (i +&nbsp;1) *&nbsp;64]&nbsp;for&nbsp;i&nbsp;in&nbsp;range(tile_count)]
&nbsp; &nbsp; source = [None] * tile_count
&nbsp; &nbsp;&nbsp;for&nbsp;k, tile&nbsp;in&nbsp;enumerate(tiles):
&nbsp; &nbsp; &nbsp; &nbsp; t = (5&nbsp;* k +&nbsp;173) % tile_count
&nbsp; &nbsp; &nbsp; &nbsp; source[t] = tile
&nbsp; &nbsp; scan = diagonal_scan()
&nbsp; &nbsp; image = np.zeros((tiles_y *&nbsp;8, tiles_x *&nbsp;8), np.uint8)
&nbsp; &nbsp;&nbsp;for&nbsp;t, tile&nbsp;in&nbsp;enumerate(source):
&nbsp; &nbsp; &nbsp; &nbsp; seq = scan[::-1]&nbsp;if&nbsp;t %&nbsp;2&nbsp;else&nbsp;scan
&nbsp; &nbsp; &nbsp; &nbsp; rotation = (7&nbsp;* t +&nbsp;3) %&nbsp;4
&nbsp; &nbsp; &nbsp; &nbsp; canvas = np.zeros((8,&nbsp;8), np.uint8)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;bit, (x, y)&nbsp;in&nbsp;zip(tile, seq):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sx, sy = rotate(rotation, x, y)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; canvas[sy, sx] = bit
&nbsp; &nbsp; &nbsp; &nbsp; ty, tx = divmod(t, tiles_x)
&nbsp; &nbsp; &nbsp; &nbsp; image[ty *&nbsp;8&nbsp;: (ty +&nbsp;1) *&nbsp;8, tx *&nbsp;8&nbsp;: (tx +&nbsp;1) *&nbsp;8] = canvas
&nbsp; &nbsp;&nbsp;return&nbsp;image

def&nbsp;main():
&nbsp; &nbsp;&nbsp;with&nbsp;zipfile.ZipFile(ZIP_PATH)&nbsp;as&nbsp;zf:
&nbsp; &nbsp; &nbsp; &nbsp; text = zf.read("data.txt").decode("utf-8")
&nbsp; &nbsp; &nbsp; &nbsp; key = bytes.fromhex(zf.comment.decode("ascii"))
&nbsp; &nbsp; image = pixels(recover_bits(text, key))
&nbsp; &nbsp; digest = hashlib.sha256(image.reshape(-1).astype(np.uint8).tobytes()).hexdigest()
&nbsp; &nbsp;&nbsp;assert&nbsp;digest ==&nbsp;"de46c7564c271c524cf4ff6df51309e49711b23fbb78cde7a11f3e1a879940e9"
&nbsp; &nbsp; Image.fromarray((1&nbsp;- image) *&nbsp;255).save(OUT_PATH)
&nbsp; &nbsp; print("PKWCTF{F4q_m3rm3r_z3n_m3_zh3_m3_hu41}")

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

依赖:numpy、Pillow。把 ZIP_PATH 改成附件解压后的 电脑也是脑.zip。脚本校验像素哈希并保存 brain_flag.png,图中文字与打印的 flag 相同。

PKWCTF{F4q_m3rm3r_z3n_m3_zh3_m3_hu41}


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。

本文转载自:玄网安全 玄网安全 opis 玄网安全 opis《PKWCTF新生MISC-“看看就好”》

评论:0   参与:  0