首页
社区
课程
招聘
[原创] wp: 第七题:戌时·暗能潜流
发表于: 1天前 24

[原创] wp: 第七题:戌时·暗能潜流

1天前
24

零、我是侧信道高手

仅需家访出题人
图片描述
不难发现
图片描述
注意到
图片描述
很容易可以获得TLU1.5的源代码,摘录如下

import tkinter as tk
from tkinter import scrolledtext, messagebox, filedialog

hex_table = list("0123456789ABCDEF")

class TLU01:
    def _get_off(self, bit_index):
        return 1 if (bit_index + 1) % 2 == 1 else 2
    def encode_char(self, ch):
        code = ord(ch)
        hx = hex(code)[2:].upper()
        res = ""
        for i, c in enumerate(hx):
            pos = hex_table.index(c)
            off = self._get_off(i)
            new_p = (pos + off) % 16
            res += hex_table[new_p]
        head = f"{len(hx):02d}"
        return head + res
    def decode_block(self, block):
        hx_len = int(block[:2])
        shift_part = block[2:2 + hx_len]
        raw_hx = ""
        for i, c in enumerate(shift_part):
            pos = hex_table.index(c)
            off = self._get_off(i)
            new_p = (pos - off + 16) % 16
            raw_hx += hex_table[new_p]
        char_code = int(raw_hx, 16)
        return chr(char_code)
    def encrypt_text(self, text):
        cipher = ""
        for c in text:
            cipher += self.encode_char(c)
        return cipher
    def decrypt_text(self, cipher_text):
        plain = []
        ptr = 0
        while ptr < len(cipher_text):
            blk_len = int(cipher_text[ptr:ptr + 2])
            block = cipher_text[ptr:ptr + 2 + blk_len]
            plain.append(self.decode_block(block))
            ptr += 2 + blk_len
        return "".join(plain)
    def _byte_encode(self, byte_val, idx):
        off = self._get_off(idx)
        return (byte_val + off) % 256
    def _byte_decode(self, byte_val, idx):
        off = self._get_off(idx)
        return (byte_val - off + 256) % 256
    def encrypt_file(self, input_path, output_path):
        try:
            with open(input_path, "rb") as f:
                raw_data = f.read()
            enc_bytes = bytearray()
            for idx, b in enumerate(raw_data):
                enc_bytes.append(self._byte_encode(b, idx))
            with open(output_path, "wb") as f:
                f.write(enc_bytes)
            return True, "文件加密成功"
        except Exception as e:
            return False, str(e)
    def decrypt_file(self, input_path, output_path):
        try:
            with open(input_path, "rb") as f:
                enc_data = f.read()
            raw_bytes = bytearray()
            for idx, b in enumerate(enc_data):
                raw_bytes.append(self._byte_decode(b, idx))
            with open(output_path, "wb") as f:
                f.write(raw_bytes)
            return True, "文件解密成功"
        except Exception as e:
            return False, str(e)

tool = TLU01()

def create_gui():
    root = tk.Tk()
    root.title("TLU-1.5 文本/文件加密工具")
    root.geometry("580x480")
    root.option_add("*Font", ("微软雅黑", 9))

    tk.Label(root, text="文本输入(支持中文)").pack(anchor="w", padx=10)
    txt_in = scrolledtext.ScrolledText(root, height=5)
    txt_in.pack(fill="x", padx=10)

    tk.Label(root, text="文本输出结果").pack(anchor="w", padx=10)
    txt_out = scrolledtext.ScrolledText(root, height=5)
    txt_out.pack(fill="x", padx=10)

    btn_frame1 = tk.Frame(root)
    btn_frame1.pack(pady=6)
    def text_enc():
        s = txt_in.get("1.0", tk.END).strip()
        res = tool.encrypt_text(s)
        txt_out.delete("1.0", tk.END)
        txt_out.insert("1.0", res)
    def text_dec():
        s = txt_in.get("1.0", tk.END).strip()
        valid = set("0123456789ABCDEF")
        if any(c not in valid for c in s):
            messagebox.showerror("错误", "密文仅允许大写0-9,A-F")
            return
        try:
            res = tool.decrypt_text(s)
            txt_out.delete("1.0", tk.END)
            txt_out.insert("1.0", res)
        except:
            messagebox.showerror("解密失败", "密文损坏")
    tk.Button(btn_frame1, text="文本加密", command=text_enc).grid(row=0, column=0, padx=6)
    tk.Button(btn_frame1, text="文本解密", command=text_dec).grid(row=0, column=1, padx=6)
    tk.Button(btn_frame1, text="清空文本", command=lambda: (txt_in.delete("1.0", tk.END), txt_out.delete("1.0", tk.END))).grid(row=0, column=2, padx=6)

    tk.Label(root, text="文件加解密区域", font=("微软雅黑", 10, "bold")).pack(pady=8)
    file_frame = tk.Frame(root)
    file_frame.pack()
    src_var = tk.StringVar()
    dst_var = tk.StringVar()
    def sel_src():
        p = filedialog.askopenfilename()
        src_var.set(p)
    def sel_dst():
        p = filedialog.asksaveasfilename()
        dst_var.set(p)

    tk.Entry(file_frame, textvariable=src_var, width=35).grid(row=0, column=0)
    tk.Button(file_frame, text="选择源文件", command=sel_src).grid(row=0, column=1, padx=4)
    tk.Entry(file_frame, textvariable=dst_var, width=35).grid(row=1, column=0, pady=4)
    tk.Button(file_frame, text="保存路径", command=sel_dst).grid(row=1, column=1, padx=4)

    def file_encrypt():
        s = src_var.get()
        d = dst_var.get()
        if not s or not d:
            messagebox.showwarning("提示", "请选择源文件和保存路径")
            return
        ok, msg = tool.encrypt_file(s, d)
        if ok:
            messagebox.showinfo("完成", msg)
        else:
            messagebox.showerror("失败", msg)
    def file_decrypt():
        s = src_var.get()
        d = dst_var.get()
        if not s or not d:
            messagebox.showwarning("提示", "请选择源文件和保存路径")
            return
        ok, msg = tool.decrypt_file(s, d)
        if ok:
            messagebox.showinfo("完成", msg)
        else:
            messagebox.showerror("失败", msg)

    btn_frame2 = tk.Frame(root)
    btn_frame2.pack(pady=6)
    tk.Button(btn_frame2, text="加密文件", command=file_encrypt).grid(row=0, column=0, padx=8)
    tk.Button(btn_frame2, text="解密文件", command=file_decrypt).grid(row=0, column=1, padx=8)

    root.mainloop()

if __name__ == "__main__":
    create_gui()

题面说明算法只包含:

  • 字节编码;
  • 十六进制位运算;
  • 模 16 循环偏移;
  • 固定位置重排;
  • 固定摘要扰动。

摘要扰动过程只与输入长度有关,不存在随机数 / 随机 IV / 外部状态。

最终恢复出的 FLAG 为:

flag{T1u_2026_Kc7f_Crypt0_M4ster!}

一、注意到 byte-pattern

先不动任何密码学知识,只把四组已知明文/密文放在一起看,就能发现明显的字节模式。

1.1 密文总是按 12 个十六进制字符对齐

明文 明文字节数 密文十六进制字符数
TLU 3 12
Hello 5 24
2026 4 24
abcd! 5 24

3 个明文字节 = 6 个 ASCII 十六进制半字节,而密文块是 12 个字符(12 个半字节)。也就是说:

3 个明文字节  -> 1 个密文块(12 hex 字符)
4/5 个明文    -> 2 个密文块(24 hex 字符)

密文块里有 6 个位置是“有效的”,剩下 6 个位置是“固定摘要”。先把密文每 12 字符分组:

Hello:
34BB405504B5 | 223594B94C53

2026:
A48844556485 | 223322356483

FLAG 共 12 组:

14CC46555475
94BC475584C5
848A43551495
448C445584C5
D4C9475564C5
34A84B55A4B5
74BA4355F495
A48844556485
648C495534A5
548C4F5584A5
B4BB405554B5
22332235A4B3

1.2 完整块里的固定位置是常量

把四个“完整块”(都是 3 个明文字节)按下标展开:

下标: 0 1 2 3 4 5 6 7 8 9 10 11
TLU:  9 4 A A 4 8 5 5 0 4  9  5
Hel:  3 4 B B 4 0 5 5 0 4  B  5
202:  A 4 8 8 4 4 5 5 6 4  8  5
abc:  5 4 7 B 4 7 5 5 8 4  B  5

立刻能看到:无论明文怎么变,下面这些下标永远相同:

下标 1  = 4
下标 4  = 4
下标 6  = 5
下标 7  = 5
下标 9  = 4
下标 11 = 5

这 6 个位置就是“固定摘要扰动”,不携带任何数据;真正携带数据的是:

0, 2, 3, 5, 8, 10

再结合“固定位置重排”,用四组样本交叉对齐,可以得到完整块的提取顺序:

[0, 2, 8, 10, 5, 3]

即对 12 字符密文块 c 依次取 c[0], c[2], c[8], c[10], c[5], c[3]。以 TLU 为例:

密文块 94AA48550495
 -> c[0]=9  c[2]=A  c[8]=0  c[10]=9  c[5]=8  c[3]=A
 -> 9A098A

1.3 尾块模板只和长度有关(正好对应提示)

最后一块不足 3 个明文字节时,模板会变化,但只取决于剩下几个字节——这正是题面那句“摘要扰动过程只与输入长度有关”。

剩 2 个明文字节Helloloabcd!d!):

2235 r0 4 r1 r3 4 r2 53
下标 0 1 2 3 4 5 6 7 8 9 10 11
内容 2 2 3 5 r0 4 r1 r3 4 r2 5 3

提取顺序:

[4, 6, 9, 7]

例:223594B94C53 → 取下标 4,6,9,7 → 9BC9

剩 1 个明文字节20266):

22332235 r0 4 r1 3

提取顺序:

[8, 10]

例:22332235648368

FLAG 的最后一个密文块是 22332235A4B3,正好套用“剩 1 字节”模板,因此 FLAG 的明文长度为:

11 * 3 + 1 = 34 字节

二、验证剩下的部分和家访结果同源

第一步只拿到了“骨架”:哪些位置是摘要、哪些位置是数据、提取顺序是什么。但剩下的部分——有效半字节到底怎么变换(偏移多少、方向如何)、模板里的常数为什么是 4/5/55/2235/22332235/53——还不能凭空确定。

这一步靠“家访”:找 TLU1.5 的源代码(出题人工具),摘录关键算法如下:

hex_table = list("0123456789ABCDEF")

class TLU01:
    def _get_off(self, bit_index):
        return 1 if (bit_index + 1) % 2 == 1 else 2

    def encode_char(self, ch):
        code = ord(ch)
        hx = hex(code)[2:].upper()
        res = ""
        for i, c in enumerate(hx):
            pos = hex_table.index(c)
            off = self._get_off(i)              # 位置相关偏移:1 / 2 交替
            new_p = (pos + off) % 16            # 模 16 循环偏移
            res += hex_table[new_p]
        head = f"{len(hx):02d}"                 # 按长度加头部(摘要)
        return head + res

    def _byte_encode(self, byte_val, idx):
        off = self._get_off(idx)
        return (byte_val + off) % 256           # 字节级模 256 旋转

把 TLU1.5 的算法设计与题面逐条对照:

题面要求的操作 TLU1.5 源码对应
字节编码 ord(ch) / _byte_encode
十六进制位运算 hex() 转十六进制后逐位处理
模 16 循环偏移 (pos + off) % 16
固定位置重排 密文块固定槽位、固定提取顺序
固定摘要扰动(只与长度有关) head = f"{len(hx):02d}" 的按长度头部

可见 HexMaze 和 TLU1.5 同源:同一套“半字节级模 16 旋转 + 按长度加摘要”的算法骨架。因此“剩下的部分”可以直接用 TLU1.5 的骨架 + 密钥 121 复原:

  • 密钥 121 拆成 1、2、1,基础偏移 1 + 2 + 1 = 4
  • 高半字节额外加首位 1(位置扰动),即高半字节 +5、低半字节 +4
  • 每 6 个有效半字节一块,整体做一次逆序(固定位置重排);
  • 按剩余长度套 TLU1.5 式长度模板(2235... / 22332235...)。

TLU 手工验证这个“同源”结论是否自洽:

TLU = 54 4C 55(ASCII hex:544C55)

高半字节 +5,低半字节 +4(模 16):
5+5=A  4+4=8
4+5=9  C+4=0
5+5=A  5+4=9
-> A890A9

整体逆序:
A890A9 -> 9A098A

套完整块模板 r0 4 r1 r5 4 r4 55 r2 4 r3 5:
r0=9 r1=A r2=0 r3=9 r4=8 r5=A
-> 94AA48550495

结果与题目给出的 TLU -> 94AA48550495 完全一致。说明从 byte-pattern 里“剩下的部分”和家访得到的 TLU1.5 是同一个来源,算法被完整锁定。

同样地,用 Hello 全流程回代(详见第五节验证),也完全一致。


三、得到答案:解密 FLAG

算法已经确定,直接对 FLAG 走解密流程。

第 1 步:按第一节的槽位,把每个密文块的有效半字节提取出来(最后一块按 [8,10]):

1C576C9B8C7C88193A488C4CDC6C793AABB87BF93AA86848683A9C588AFCBB5B0BAB

第 2 步:撤销全局逆序,整体反转:

BAB0B5BBCFA885C9A38684868AA39FB78BBAA397C6CDC4C884A39188C7C8B9C675C1

第 3 步:偶数下标减 5、奇数下标减 4(模 16),得到明文十六进制:

666C61677B5431755F323032365F4B6337665F4372797074305F4D3473746572217D

第 4 步:每两个十六进制字符转一个 ASCII 字节:

66 6C 61 67 7B 54 31 75 5F 32 30 32 36 5F 4B 63 37
66 5F 43 72 79 70 74 30 5F 4D 34 73 74 65 72 21 7D

得到:

flag{T1u_2026_Kc7f_Crypt0_M4ster!}

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 0
打赏
分享
最新回复 (1)
雪    币: 2119
活跃值: (2591)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
2
6啊,顺着网线找到了作者
12小时前
0
游客
登录 | 注册 方可回帖
返回