首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创] wp: 第七题:戌时·暗能潜流
发表于: 2026-8-20 19:48
53
[原创] wp: 第七题:戌时·暗能潜流
the_hs
1
2026-8-20 19:48
53
## 零、我是侧信道高手 仅需家访出题人  不难发现  注意到  很容易可以获得TLU1.5的源代码,摘录如下 ```python 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 为: ```text 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 个半字节)。也就是说: ```text 3 个明文字节 -> 1 个密文块(12 hex 字符) 4/5 个明文 -> 2 个密文块(24 hex 字符) ``` 密文块里有 6 个位置是“有效的”,剩下 6 个位置是“固定摘要”。先把密文每 12 字符分组: ```text Hello: 34BB405504B5 | 223594B94C53 2026: A48844556485 | 223322356483 ``` FLAG 共 12 组: ```text 14CC46555475 94BC475584C5 848A43551495 448C445584C5 D4C9475564C5 34A84B55A4B5 74BA4355F495 A48844556485 648C495534A5 548C4F5584A5 B4BB405554B5 22332235A4B3 ``` ### 1.2 完整块里的固定位置是常量 把四个“完整块”(都是 3 个明文字节)按下标展开: ```text 下标: 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 ``` 立刻能看到:无论明文怎么变,下面这些下标永远相同: ```text 下标 1 = 4 下标 4 = 4 下标 6 = 5 下标 7 = 5 下标 9 = 4 下标 11 = 5 ``` 这 6 个位置就是“固定摘要扰动”,不携带任何数据;真正携带数据的是: ```text 0, 2, 3, 5, 8, 10 ``` 再结合“固定位置重排”,用四组样本交叉对齐,可以得到完整块的提取顺序: ```python [0, 2, 8, 10, 5, 3] ``` 即对 12 字符密文块 `c` 依次取 `c[0], c[2], c[8], c[10], c[5], c[3]`。以 `TLU` 为例: ```text 密文块 94AA48550495 -> c[0]=9 c[2]=A c[8]=0 c[10]=9 c[5]=8 c[3]=A -> 9A098A ``` ### 1.3 尾块模板只和长度有关(正好对应提示) 最后一块不足 3 个明文字节时,模板会变化,但**只取决于剩下几个字节**——这正是题面那句“摘要扰动过程只与输入长度有关”。 **剩 2 个明文字节**(`Hello` 的 `lo`、`abcd!` 的 `d!`): ```text 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 | 提取顺序: ```python [4, 6, 9, 7] ``` 例:`223594B94C53` → 取下标 4,6,9,7 → `9BC9`。 **剩 1 个明文字节**(`2026` 的 `6`): ```text 22332235 r0 4 r1 3 ``` 提取顺序: ```python [8, 10] ``` 例:`223322356483` → `68`。 FLAG 的最后一个密文块是 `22332235A4B3`,正好套用“剩 1 字节”模板,因此 FLAG 的明文长度为: ```text 11 * 3 + 1 = 34 字节 ``` --- ## 二、验证剩下的部分和家访结果同源 第一步只拿到了“骨架”:哪些位置是摘要、哪些位置是数据、提取顺序是什么。但**剩下的部分**——有效半字节到底怎么变换(偏移多少、方向如何)、模板里的常数为什么是 `4/5/55/2235/22332235/53`——还不能凭空确定。 这一步靠“家访”:找 TLU1.5 的源代码(出题人工具),摘录关键算法如下: ```python 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` 手工验证这个“同源”结论是否自洽: ```text 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]`): ```text 1C576C9B8C7C88193A488C4CDC6C793AABB87BF93AA86848683A9C588AFCBB5B0BAB ``` **第 2 步**:撤销全局逆序,整体反转: ```text BAB0B5BBCFA885C9A38684868AA39FB78BBAA397C6CDC4C884A39188C7C8B9C675C1 ``` **第 3 步**:偶数下标减 5、奇数下标减 4(模 16),得到明文十六进制: ```text 666C61677B5431755F323032365F4B6337665F4372797074305F4D3473746572217D ``` **第 4 步**:每两个十六进制字符转一个 ASCII 字节: ```text 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 ``` 得到: ```text flag{T1u_2026_Kc7f_Crypt0_M4ster!} ```
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
shuax
雪 币:
2222
活跃值:
(2736)
能力值:
( LV12,RANK:240 )
在线值:
发帖
16
回帖
254
粉丝
11
关注
私信
shuax
2
2
楼
6啊,顺着网线找到了作者
2026-8-21 16:27
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
the_hs
1
18
发帖
22
回帖
90
RANK
关注
私信
他的文章
[原创]wp:第八题:亥子合辰·塔影迷楼
26
[原创] wp: 第七题:戌时·暗能潜流
53
[原创]wp: 第二题:巳时·绿光幽语
36
[讨论]被kimi k3吓哭
347
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部