首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创]KCTF 2026 第七题《戌时·暗能潜流》题解
发表于: 2026-8-20 22:28
25
[原创]KCTF 2026 第七题《戌时·暗能潜流》题解
wx_吴_369
2026-8-20 22:28
25
# [原创] KCTF 2026 第七题《戌时·暗能潜流》题解(HexMaze 黑盒逆推) > 题目类型:Crypto 出题战队:FUA_KCTF > 最终 flag:`flag{T1u_2026_Kc7f_Crypt0_M4ster!}` ## 0. 题目 附件给出算法说明与 4 组已知明文/密文(密钥 121),要求恢复 FLAG: ``` TLU -> 94AA48550495 Hello -> 34BB405504B5 223594B94C53 2026 -> A48844556485 223322356483 abcd! -> 547B475584B5 223564BB4553 FLAG: 14CC4655547594BC475584C5848A43551495448C445584C5D4C9475564C534A84B55A4B5 74BA4355F495A48844556485648C495534A5548C4F5584A5B4BB405554B722332235A4B3 ``` 算法组件(题面声明):字节编码、十六进制位运算、模16循环偏移、固定位置重排、固定摘要扰动(只与输入长度有关)。 ## 1. 输出长度规律 → 3 字节窗口模型 | 明文长度 | 输出 nibble 数 | 规律 | |---|---|---| | 3B (TLU) | 12 | 1 窗 | | 4B (2026) | 24 | 2 窗(3B+1B) | | 5B (Hello/abcd!) | 24 | 2 窗(3B+2B) | | FLAG 144 nib | — | 12 窗 = 11×3B + 1×1B = **34 字节** | **输出 = 12·⌈n/3⌉ 个 nibble,每 ≤3 明文字节产生一个 12-nib 窗口。** 尾窗按实际字节数分型(3B/2B/1B)。 ## 2. digest 位与明文承载位(M 位)的分离 对两个同长度样本(Hello vs abcd!)做 nibble 级差分:明文 10 个 nib 全不同,输出只有 7 个位置变化 —— 其余位置与明文无关,是"摘要扰动"(digest): - **3B 窗 digest**:位 [1,4,6,7,9,11] 恒为 `[4,4,5,5,4,5]`(跨 len3/4/5 全部成立) - **2B 窗 digest**:位 [0..3] = `2235` 前缀;**1B 窗 digest**:位 [0..7] = `22332235` 前缀 - 剩余位置(M 位)承载明文:3B 窗 6 个、2B 窗 4 个、1B 窗 2 个 —— digest 位数 6/8/10,长度账目精确吻合 flag 的 11 个 3B 窗 digest 特征全部吻合(W10.p11=7 为末前窗特例),W11 的 `22332235` 前缀确认它是 1B 窗。 ## 3. 铁映射:TLU 大写样本的关键作用 差分可确定多数 M 位的源。其中最稳的两条(跨全部长度恒定): ``` 3B窗.p3 <- 本窗第2字节.hi + 5 3B窗.p10 <- 本窗第1字节.hi + 5 ``` 小写字母的 hi 都是 6/7,Hello/abcd!/2026 三个样本无法区分 p3/p10 的源;**TLU(T=0x54, L=0x4C, U=0x55,三个 hi 各不相同)一锤定音**:p10 = L.hi + 5 = 9、p3 = U.hi + 5 = A,全部吻合。 由此 flag 的 22 个高 nibble 直接解出,且**全部落在可打印 ASCII 区间 [2..7]** —— 模型正确性的第一次统计验证。 ## 4. 弯路:三次失败的提交(模板歧义) K 表中部分位置(p0/p5/p8)的偏移量随输入长度漂移(如 K0: len3→4, len4→7, len5→15),而 flag 是 34 字节 —— **4 组样本全是 1~2 窗,flag 是 12 窗,样本覆盖不了 flag 的上下文**。我先后构造了三套"闭环自洽"的模板(正向重加密都能完美还原 FLAGCT): ``` q"ydsq=OP4p|rsEVg;kO_202?U~4[}alcy ✗ y/taru0KQ<rrupE[d9oIQ202>]w=R}cony ✗ t/yerq1KP2r|upEYd;aI_2027]~=R}nocy ✗ ``` 全部被平台拒绝。教训:**闭环自洽 ≠ 正确** —— 当黑盒歧义无法收敛时,每套假设都能完美对拍密文。 期间还发现一个高度可疑的"锚点":flag 第 8 窗输出 `A48844556485` 与 2026 样本首窗输出**12 个 nibble 完全相同**(概率 16⁻¹²)。事实证明这只是"2026"这段明文在两处加密结果的自然重合,不能当作模板锚点使用。 ## 5. 破题:完美双射 + "hi→5 / lo→4"强制规则 回到数据本身重新归纳所有已确认映射的 K 值: ``` p3 <- b2hi + 5 p10 <- b1hi + 5 (hi + 5) p4(2B窗) <- 上窗b1lo + 4 p9(2B窗) <- 上窗b0lo + 4 (lo + 4) p6(2B窗) <- 本窗b0hi + 5 ``` **规律浮出水面:源是高 nibble 就 +5,源是低 nibble 就 +4。** 再算信息账:flag 的 M 位总数 = 11×6 + 2 = 68 = 明文 nibble 总数 —— **完美双射,每个明文 nibble 恰好被承载一次**。 于是放弃"锚点",改为枚举 p0/p2/p5/p8 四个位置的源分配(本窗/上窗/下窗 × {b0hi, b0lo, b1lo, b2lo}),K 按源类型强制(hi→5, lo→4),要求完美匹配 + 全可打印。全空间只剩 4 个候选。 ## 6. 最后一跃:字符串是"反"的 最优候选(score 最高,含 `2026` 片段): ``` }!rets4M_0tpyrC_f7cK_6202_u1T{galy ``` 倒着读 —— `Crypt0_M4ster!}`、`2026`、`flag{` 全部浮现:**我的窗口拼装顺序与真实 flag 相反**(整串字节反转)。同时末字节按"hi+5/lo+4"修正:W11 的 p8/p10 反解出 `0x66` = `f` 而非 `y`。 ``` 反转 + 末字节修正 => flag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` leet 风格自洽:`T1u` = 样本 "TLU" 的致敬(L→1)、`Kc7f` = KCTF(7→T)、`Crypt0`(o→0)、`M4ster`(a→4)。34 字节长度严丝合缝。提交,平台确认成功。 ## 7. 完整解题脚本(solve_final.py,从密文一键复现 flag) ```python # -*- coding: utf-8 -*- # KCTF 2026 第七题《戌时·暗能潜流》最终求解脚本 # 输入: FLAG 密文 -> 输出: flag{T1u_2026_Kc7f_Crypt0_M4ster!} # 方法: 3B窗口模型 + digest/M位分离 + hi->5/lo->4强制规则 + 完美双射枚举 + 整串反转 import sys sys.stdout.reconfigure(encoding='utf-8') FLAGCT = ("14CC4655547594BC475584C5848A43551495448C445584C5D4C9475564C534A84B55A4B5" "74BA4355F495A48844556485648C495534A5548C4F5584A5B4BB405554B722332235A4B3") # 归纳自4组样本的窗口结构(全部交叉验证): # 3B窗: [p0,p2,p3,p5,p8,p10]=明文承载位, [p1,p4,p6,p7,p9,p11]=digest恒[4,4,5,5,4,5] # 1B窗: [p8,p10]=承载位, [p0..p7]=digest恒[2,2,3,3,2,2,3,5] # 铁映射: p3<-本窗b2hi+5, p10<-本窗b1hi+5 (TLU大写样本定源) # 类型规则: 源为高nibble则+5, 源为低nibble则+4 C = [int(c, 16) for c in FLAGCT] W = [C[12*i:12*i+12] for i in range(12)] # 12个窗口 (11x3B + 1x1B) p0 = [W[i][0] for i in range(12)]; p2 = [W[i][2] for i in range(12)] p3 = [W[i][3] for i in range(12)]; p5 = [W[i][5] for i in range(11)] p8 = [W[i][8] for i in range(11)]; p10 = [W[i][10] for i in range(11)] # 结构自检: digest 位特征 (W10 为末前窗, p11=7 特例) for i in range(11): dig = [W[i][j] for j in (1,4,6,7,9,11)] assert dig == ([4,4,5,5,4,7] if i == 10 else [4,4,5,5,4,5]), f"W{i} digest异常" assert W[11][:8] == [2,2,3,3,2,2,3,5], "W11 非1B窗" print("[+] 窗口结构自检通过: 11x3B + 1x1B, flag = 34 字节") # 第一步: 铁映射解出 22 个高nibble (每窗 b1hi/b2hi) b1hi = [(p10[i]-5) % 16 for i in range(11)] b2hi = [(p3[i] -5) % 16 for i in range(11)] assert all(2 <= v <= 7 for v in b1hi+b2hi), "hi超出可打印域" print("[+] 22个高nibble全部落可打印域 -> 铁映射成立") # 第二步: 完美双射 + hi->5/lo->4 强制规则, 枚举 p0/p2/p5/p8 的源 SRCS = [('L',0),('L',1),('L',3),('L',5),('D',0),('D',1),('D',3),('D',5), ('U',0),('U',1),('U',3),('U',5)] # {本/上/下窗}x{b0hi,b0lo,b1lo,b2lo} KOF = {0:5, 1:4, 3:4, 5:4} # hi->5, lo->4 results = [] for s0 in SRCS: for s2 in SRCS: for s5 in SRCS: for s8 in SRCS: if len({(0 if s[0]=='L' else 1 if s[0]=='U' else -1, s[1]) for s in (s0,s2,s5,s8)}) < 4: continue # 源重复->信息丢失, 排除 nib = [[None]*6 for _ in range(11)] for i in range(11): nib[i][2] = b1hi[i]; nib[i][4] = b2hi[i] ok = True for _ in range(6): # 迭代传播+冲突检测 changed = False for i in range(11): for val, s in ((p0[i],s0),(p2[i],s2),(p5[i],s5),(p8[i],s8)): t = i + (1 if s[0]=='D' else -1 if s[0]=='U' else 0) if not 0 <= t < 11: continue v = (val - KOF[s[1]]) % 16 if nib[t][s[1]] is None: nib[t][s[1]] = v; changed = True # W11(1B窗): K=(4,5) [实测: p8<-b0hi+4, p10<-b0lo+5] w11hi = (W[11][8] - 4) % 16; w11lo = (W[11][10] - 5) % 16 if not changed: break if any(nib[i][j] is None for i in range(11) for j in (0,1,3,5)): continue # 未完全求解 -> 排除 for i in range(11): if not all(2 <= nib[i][j] <= 7 for j in (0,2,4)): ok = False if not ok: continue tail = w11hi*16 + w11lo pt = bytearray() for i in range(11): bs = [nib[i][0]*16+nib[i][1], nib[i][2]*16+nib[i][3], nib[i][4]*16+nib[i][5]] if not all(0x20 <= b <= 0x7e for b in bs): ok = False; break pt += bytes(bs) if not ok or not 0x20 <= tail <= 0x7e: continue cand = bytes(pt) + bytes([tail]) score = sum(1 for b in cand if chr(b).isalpha()) if b'2026' in cand or b'6202' in cand: score += 40 # 年份先验 results.append((score, (s0,s2,s5,s8), cand)) results.sort(key=lambda x: -x[0]) print(f"[+] 可行候选: {len(results)} 个") # 第三步: 最优候选整串反转 = flag (窗口拼装顺序与真实相反) best_score, best_src, best = results[0] flag = best[::-1] print(f"[+] 最优候选: {best!r}") print(f"\n[*] 反转后 FLAG = {flag.decode()}") # 第四步: 正向重加密闭环验证 def encrypt(pt, srcs): s0,s2,s5,s8 = srcs N = [n for b in pt for n in ((b>>4)&0xF, b&0xF)] out = [] for i in range(12): if i < 11: g = 6*i def get(s): t = g + (6 if s[0]=='D' else -6 if s[0]=='U' else 0) return N[t + s[1]] if 0 <= t + s[1] < 68 else 0 win = [ (get(s0)+5 if s0[1]==0 else get(s0)+4), 4, (get(s2)+5 if s2[1]==0 else get(s2)+4), (b2hi[i]+5), 4, (get(s5)+5 if s5[1]==0 else get(s5)+4), 5, 5, (get(s8)+5 if s8[1]==0 else get(s8)+4), 4, (b1hi[i]+5), (7 if i == 10 else 5) ] # W10末前窗p11=7特例 else: # 1B 末窗 K=(4,5) win = [2,2,3,3,2,2,3,5, (pt[33]>>4)+4, 4, (pt[33]&0xF)+5, 3] out += [v % 16 for v in win] return ''.join(f'{v:X}' for v in out) ct_check = encrypt(best, best_src) print(f"[+] 闭环验证: 候选正向加密 == FLAGCT ? {''.join(ct_check) == FLAGCT}") print(f"\n=== FLAG: {flag.decode()} ===") ``` **运行输出:** ``` [+] 窗口结构自检通过: 11x3B + 1x1B, flag = 34 字节 [+] 22个高nibble全部落可打印域 -> 铁映射成立 [+] 可行候选: 4 个 [+] 最优候选: b'}!rets4M_0tpyrC_f7cK_6202_u1T{galf' [*] 反转后 FLAG = flag{T1u_2026_Kc7f_Crypt0_M4ster!} [+] 闭环验证: 候选正向加密 == FLAGCT ? True === FLAG: flag{T1u_2026_Kc7f_Crypt0_M4ster!} === ``` --- 感谢出题战队 FUA_KCTF。题目用 4 组小样本藏了一个可完全归纳的 nibble 级变换,"摘要扰动只与输入长度有关"这句话既是提示也是陷阱 —— 它让我长期试图拟合 digest(len) 公式(4,7,15 三点根本拟合不出),而正解只需把"每窗共享的 K 规则"归纳出来,把长度差异当作黑盒修正位绕过去。
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
wx_吴_369
2
发帖
0
回帖
0
RANK
关注
私信
他的文章
[原创] KCTF 2026 第八题《亥子合辰·塔影迷楼》题解
38
[原创]KCTF 2026 第七题《戌时·暗能潜流》题解
25
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部