首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
7
[原创] 第七题:戌时·暗能潜流 WP
发表于: 2026-8-21 08:59
763
[原创] 第七题:戌时·暗能潜流 WP
shuax
2
2026-8-21 08:59
763
# 本题由ChatGPT Pro耗时104分钟完成。  # 下面是AI生成的Writeup # HexMaze Writeup —— 已知明文攻击与固定半字节置换逆向 ## 0x00 题目信息 题目给出了一个名为 `HexMaze` 的确定性字符串变换算法。 已知条件: - 密钥:`121` - 输入为 ASCII 字符串 - 算法只包含: - 字节编码 - 十六进制位运算 - 模 16 循环偏移 - 固定位置重排 - 固定摘要扰动 - 无随机数 - 无随机 IV - 无外部状态 - 相同输入和密钥必然产生唯一输出 - **摘要扰动只与输入长度有关** 已知明文 / 密文: ```text TLU -> 94AA48550495 Hello -> 34BB405504B5223594B94C53 2026 -> A48844556485223322356483 abcd! -> 547B475584B5223564BB4553 ``` 目标密文: ```text 14CC4655547594BC475584C5848A43551495448C445584C5 D4C9475564C534A84B55A4B574BA4355F495A48844556485 648C495534A5548C4F5584A5B4BB405554B522332235A4B3 ``` 目标:恢复 FLAG。 --- # 0x01 解题时间线 这道题真正耗时间的部分并不是最后的逆运算,而是确定“哪些十六进制位属于数据,哪些属于扰动,以及数据到底经过了什么顺序的重排”。 本次分析记录中的总推理时间约为: ```text 104 min 03 s ``` 大致可以复盘为: | 时间 | 阶段 | 状态 | |---|---|---| | 0 ~ 10 min | 看长度、找分组 | 确认输出存在明显扩展 | | 10 ~ 25 min | 比较 4 组样本 | 发现大量固定的 `4`、`5` | | 25 ~ 45 min | 尝试直接按字节映射 | 多次失败 | | 45 ~ 60 min | 尝试 XOR / AND / OR / key=121 的各种解释 | 基本走弯路 | | 60 ~ 75 min | 对同长度样本做差分 | 开始分离长度扰动 | | 75 ~ 88 min | 转向半字节分析 | 找到 `+5 / +4 mod 16` | | 88 ~ 98 min | 反推固定位置排列与整体逆序 | 出现明显 FLAG 文本 | | 98 ~ 104 min | 处理最后一个长度扰动块并完整验证 | 得到最终 FLAG | 所以这题的突破点其实比较靠后。 --- # 0x02 第一反应:输出为什么这么长? 首先看最简单的一组: ```text TLU -> 94AA48550495 ``` `TLU` 是 3 个 ASCII 字符,也就是: ```text 3 bytes = 6 hexadecimal nibbles ``` 但是密文却有: ```text 12 hexadecimal nibbles ``` 长度恰好翻倍。 一开始很自然会猜: > 每个明文字节是不是被变成了两个密文字节? 于是我最开始尝试按照: ```text TL -> 94AA U -> ... ``` 这种方式拆。 但很快发现不对。 因为对 `Hello`、`abcd!` 做相同位置比较时,相同字符位置的输出没有形成简单的逐字节映射。 因此第一版假设: ```text 一个 ASCII 字节 -> 固定两个密文字节 ``` 失败。 --- # 0x03 第一次弯路:把 121 当成普通 XOR Key 密钥是: ```text 121 ``` 首先自然想到十进制: ```text 121 = 0x79 ``` 于是尝试过一些比较常见的构造: ```text byte ^ 0x79 byte + 0x79 byte - 0x79 high_nibble ^ 7 low_nibble ^ 9 high_nibble + 7 low_nibble + 9 ``` 甚至继续测试了: ```text AND OR XOR ADD SUB nibble swap bit rotate ``` 以及各种简单组合。 例如: ```text T = 0x54 0x54 ^ 0x79 = 0x2D 0x54 + 0x79 = 0xCD 0x54 & 0x79 = 0x50 0x54 | 0x79 = 0x7D ``` 这些结果和: ```text 94AA48550495 ``` 都没有直接对应关系。 这一阶段花了比较多时间,因为题目明确写了: > 十六进制位运算 很容易让人过度关注 `0x79`。 后来才意识到: > 真正重要的不是直接拿 ASCII 字节和 121 做一次运算,而是先把结果降到 **4 bit 半字节层面**观察。 --- # 0x04 第二个线索:密文中 `4` 和 `5` 多得异常 重新观察: ```text TLU 94AA48550495 ``` 拆成半字节: ```text 9 4 A A 4 8 5 5 0 4 9 5 ``` 能明显看到: ```text 4 4 5 5 4 5 ``` 出现得非常规律。 再观察其它样本的某些完整块,也能看到类似结构。 于是开始怀疑: > 输出的 12 个半字节并不全部是明文数据,其中一部分是固定扰动。 对第一组完整结构进一步比较后,发现可以抽出: ```text 位置(0-based): 0 2 3 5 8 10 ``` 即: ```text [c0, c2, c3, c5, c8, c10] ``` 而其余位置形成非常明显的固定骨架: ```text _ 4 _ _ 4 _ 5 5 _ 4 _ 5 ``` 也就是: ```text 位置: 1 4 6 7 9 11 值: 4 4 5 5 4 5 ``` 至此有了一个非常重要的判断: > 一个完整块中的 12 个半字节,只有 6 个半字节真正携带数据。 而: ```text 6 nibbles = 3 bytes ``` 刚好对应 3 个 ASCII 字符。 这也解释了为什么 `TLU` 恰好对应一个 12-nibble 块。 --- # 0x05 一个错误假设:所有块都能直接套相同位置 发现: ```text [0, 2, 3, 5, 8, 10] ``` 之后,我一度认为已经解决了大部分问题,于是直接对: ```text Hello 2026 abcd! ``` 的所有 12 位分组都套这个位置。 结果并没有全部恢复出可读 ASCII。 例如 `Hello` 的第二段和 `2026` 的尾段就明显不符合完整块模式。 这说明: ```text 完整数据块 ``` 和: ```text 由于长度不足产生的尾部块 ``` 必须分开分析。 这个地方其实正好对应题目的提示: > 摘要扰动过程只与输入长度有关。 也就是说,长度不是无关信息。 反而是破解尾块的关键。 --- # 0x06 利用相同长度样本消去一部分干扰 `Hello` 与 `abcd!` 都是: ```text length = 5 ``` 因此如果摘要扰动: ```text 只依赖长度 ``` 那么它们的长度相关部分应该一致。 所以我开始重点比较: ```text Hello abcd! ``` 而不是把所有四组样本混在一起。 这是第二个比较重要的转折。 通过同长度样本比较,可以发现: - 某些输出位置完全相同; - 某些位置跟明文变化; - 固定值大量集中在 `2 / 3 / 4 / 5`; - 数据部分则会出现 `8 / 9 / A / B / C ...` 由此基本确认: > 摘要扰动只是穿插在数据中的固定结构,而不是类似 hash 那样把所有明文重新混合。 因此整个算法仍然高度可逆。 --- # 0x07 真正的突破:不要看字节,直接看半字节 接下来重新计算 ASCII。 以: ```text TLU ``` 为例: ```text T = 0x54 L = 0x4C U = 0x55 ``` 拆成高低半字节: ```text 5 4 4 C 5 5 ``` 然后去寻找一个非常简单的模 16 偏移。 最终发现: ```text 高半字节 + 5 mod 16 低半字节 + 4 mod 16 ``` 可以得到: ### `T = 0x54` ```text H = 5 L = 4 H' = 5 + 5 = A L' = 4 + 4 = 8 => A8 ``` ### `L = 0x4C` ```text H = 4 L = C H' = 4 + 5 = 9 L' = C + 4 = 0 mod 16 => 90 ``` ### `U = 0x55` ```text H = 5 L = 5 H' = A L' = 9 => A9 ``` 所以: ```text TLU ``` 经过模 16 偏移之后得到: ```text A8 90 A9 ``` 即: ```text A 8 9 0 A 9 ``` 这个结果非常关键。 因为密文中真正的数据半字节恰好也是: ```text A 8 9 0 A 9 ``` 只是顺序完全不同。 也就是说: > 数值变换已经找到了,剩下的问题只是 permutation。 --- # 0x08 反推位置重排 `TLU` 偏移后的半字节流: ```text A 8 9 0 A 9 ``` 发现算法先把整体顺序反转: ```text 9 A 0 9 8 A ``` 记: ```text r = [9, A, 0, 9, 8, A] ``` 而从密文: ```text 94AA48550495 ``` 抽取真实数据位置: ```text [0, 2, 3, 5, 8, 10] ``` 得到: ```text q = [9, A, A, 8, 0, 9] ``` 于是可以推出: ```text q = [ r0, r1, r5, r4, r2, r3 ] ``` 即固定排列: ```text [0, 1, 5, 4, 2, 3] ``` 反向解密: ```text r = [ q0, q1, q4, q5, q3, q2 ] ``` 即: ```text [0, 1, 4, 5, 3, 2] ``` 到这里已经基本恢复了完整块的逆算法。 --- # 0x09 用 `TLU` 完整验证一次 加密方向: ```text TLU ``` ASCII: ```text 54 4C 55 ``` 模 16 偏移: ```text A8 90 A9 ``` 半字节: ```text A 8 9 0 A 9 ``` 整体逆序: ```text 9 A 0 9 8 A ``` 固定排列: ```text 9 A A 8 0 9 ``` 插入固定扰动: ```text 9 4 A A 4 8 5 5 0 4 9 5 ``` 得到: ```text 94AA48550495 ``` 与题目完全一致。 这一刻基本可以确认: ```text 模16偏移 + 全局倒序 + 固定重排 + 固定扰动 ``` 的方向是正确的。 --- # 0x0A 开始攻击 FLAG FLAG: ```text 14CC4655547594BC475584C5848A43551495448C445584C5 D4C9475564C534A84B55A4B574BA4355F495A48844556485 648C495534A5548C4F5584A5B4BB405554B522332235A4B3 ``` 每 12 个半字节切分: ```text 01 14CC46555475 02 94BC475584C5 03 848A43551495 04 448C445584C5 05 D4C9475564C5 06 34A84B55A4B5 07 74BA4355F495 08 A48844556485 09 648C495534A5 10 548C4F5584A5 11 B4BB405554B5 12 22332235A4B3 ``` 由于之前已经确定: ```text 整个半字节数据流被反向 ``` 所以真正恢复明文时,应先从后面的完整块开始。 暂时跳过最后的长度扰动块 `12`。 从 Block 11 开始向前解。 --- # 0x0B 批量解出完整块 完整块的解密流程: ```text cipher block ↓ 取 [0,2,3,5,8,10] ↓ 逆 permutation ↓ 逆转半字节顺序 ↓ high -= 5 mod 16 low -= 4 mod 16 ↓ ASCII ``` 得到: | Block | Cipher | Plaintext | |---:|---|---| | 11 | `B4BB405554B5` | `lag` | | 10 | `548C4F5584A5` | `{T1` | | 9 | `648C495534A5` | `u_2` | | 8 | `A48844556485` | `026` | | 7 | `74BA4355F495` | `_Kc` | | 6 | `34A84B55A4B5` | `7f_` | | 5 | `D4C9475564C5` | `Cry` | | 4 | `448C445584C5` | `pt0` | | 3 | `848A43551495` | `_M4` | | 2 | `94BC475584C5` | `ste` | | 1 | `14CC46555475` | `r!}` | 拼起来: ```text lag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` 到这里其实已经很明显: ```text flag{...} ``` 只缺最开头: ```text f ``` 但不能直接靠 FLAG 格式猜。 还需要把最后的长度扰动块逆出来。 --- # 0x0C 最后的难点:尾块 `22332235A4B3` FLAG 最后的特殊块: ```text 22332235A4B3 ``` 无法直接使用完整块: ```text _4__4_55_4_5 ``` 的结构。 这正是前面曾经导致解码失败的地方。 此时利用题目最关键的提示: > 摘要扰动过程只与输入长度有关。 我们需要找一个已知样本,它的尾部形态与 FLAG 相同。 观察: ```text 2026 ``` 长度为 4。 由于完整块每次可以承载: ```text 3 characters ``` 那么 `2026` 在整体逆序以后同样会出现: ```text 1 个剩余字符 + 1 个完整三字符块 ``` 它的密文: ```text A48844556485223322356483 ``` 拆成: ```text A48844556485 223322356483 ``` 其中: ```text A48844556485 ``` 解出来正好是: ```text 026 ``` 所以特殊尾块: ```text 223322356483 ``` 必然携带最前面的字符: ```text 2 ``` --- # 0x0D 对比两个尾块 已知 `2026`: ```text 223322356483 ``` FLAG: ```text 22332235A4B3 ``` 排在一起: ```text 22332235 6 4 8 3 22332235 A 4 B 3 ``` 可以看到: ```text 22332235 ``` 完全相同。 末尾: ```text 4 3 ``` 也保持不变。 真正变化的只有: ```text 6 -> A 8 -> B ``` 这两个位置正好可以存储一个 ASCII 字节的两个半字节。 --- # 0x0E 用 `2026` 验证尾块 `2026` 中需要恢复的首字符: ```text '2' ``` ASCII: ```text 0x32 ``` 高低半字节: ```text H = 3 L = 2 ``` 按照已经得到的模 16 变换: ```text H' = 3 + 5 = 8 L' = 2 + 4 = 6 ``` 因此: ```text H' = 8 L' = 6 ``` 尾块中恰好出现: ```text ... 6 4 8 3 ``` 即数据位置是: ```text L' = 6 H' = 8 ``` 说明尾块同样受到整体逆序影响。 --- # 0x0F 恢复 FLAG 的第一个字符 FLAG 尾块: ```text 22332235A4B3 ``` 抽取: ```text L' = A H' = B ``` 逆向模 16 偏移: ```text L = A - 4 mod 16 = 6 H = B - 5 mod 16 = 6 ``` 所以原字节: ```text 0x66 ``` ASCII: ```text 'f' ``` 于是完整明文为: ```text f + lag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` 得到: ```text flag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` --- # 0x10 最终 FLAG ```text flag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` --- # 0x11 解密脚本 最后把规律写成脚本验证。 ```python ciphertext = ( "14CC4655547594BC475584C5848A43551495448C445584C5" "D4C9475564C534A84B55A4B574BA4355F495A48844556485" "648C495534A5548C4F5584A5B4BB405554B522332235A4B3" ) def decode_shifted(nibbles): result = [] for i in range(0, len(nibbles), 2): h = (nibbles[i] - 5) & 0xF l = (nibbles[i + 1] - 4) & 0xF result.append(chr((h << 4) | l)) return "".join(result) def decode_full_block(block): c = [int(x, 16) for x in block] # 提取真正携带明文的数据位 q = [c[i] for i in (0, 2, 3, 5, 8, 10)] # 逆固定位置重排 r = [q[i] for i in (0, 1, 4, 5, 3, 2)] # 原算法存在半字节整体反转 r.reverse() return decode_shifted(r) blocks = [ ciphertext[i:i + 12] for i in range(0, len(ciphertext), 12) ] # 特殊尾块保存最前方剩余的一个字符 tail = blocks[-1] first_char = decode_shifted([ int(tail[10], 16), # H' int(tail[8], 16), # L' ]) # 剩余完整块逆序读取 body = "".join( decode_full_block(block) for block in reversed(blocks[:-1]) ) flag = first_char + body print(flag) ``` 输出: ```text flag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` --- # 0x12 最终验证 仅仅得到一个看起来像 FLAG 的字符串还不够。 为了避免因为: ```text flag{ ``` 格式产生误判,还需要把最终明文重新按推导出的规则进行正向验证。 恢复出的字符串长度: ```text len("flag{T1u_2026_Kc7f_Crypt0_M4ster!}") = 34 ``` 完整块: ```text 33 characters = 11 × 3 ``` 剩余: ```text 1 character ``` 这与密文结构: ```text 11 个完整块 + 1 个长度扰动尾块 ``` 完全对应。 同时用逆向规则测试题目提供的四组样例: ```text TLU Hello 2026 abcd! ``` 均能恢复出原始明文。 因此不是单纯通过 FLAG 格式猜测,而是算法结构和全部已知样本都能够闭环验证。 --- # 0x13 复盘:为什么花了 100 多分钟? 这题最大的坑是信息量其实非常有限: ```text 4 组 known plaintext ``` 但是可能的组合却很多。 题目又明确给出了: ```text 字节编码 十六进制位运算 模16循环偏移 固定位置重排 固定摘要扰动 ``` 因此一开始很容易枚举大量可能性,例如: ```text XOR 121 XOR 0x79 ADD 121 AND 0x79 OR 0x79 nibble swap rotate 不同 key digit 循环 不同排列 ``` 这些尝试大部分都没有价值。 真正有效的三个观察是: ### 1. 不要首先把输出当成“字节” 题目强调: ```text 十六进制位运算 模16 ``` 实际上已经暗示: ```text 最自然的分析单位是 nibble ``` 而不是 byte。 --- ### 2. `4` 和 `5` 的高频出现不是巧合 大量: ```text 4 5 ``` 实际是固定扰动。 一旦把它们从数据中分离: ```text 12 nibbles ↓ 6 data nibbles ↓ 3 ASCII bytes ``` 问题立刻简单很多。 --- ### 3. “摘要只与长度有关”是处理尾块的关键 完整块可以直接逆。 真正麻烦的是: ```text 22332235.... ``` 这种非完整结构。 通过比较相同余数长度的: ```text 2026 ``` 就可以把长度摘要和真实数据分离。 最后一个字符因此不需要猜测。 --- # 0x14 总结算法 从本题给出的样本能够恢复出的有效逆向结构可以概括成: ```text ASCII │ │ 拆分高低半字节 ▼ [ H, L ] │ │ H = H + 5 mod 16 │ L = L + 4 mod 16 ▼ shifted nibble stream │ │ 整体逆序 ▼ reversed nibble stream │ │ 固定 permutation ▼ permuted stream │ │ 插入固定/长度相关扰动 ▼ ciphertext ``` 逆向过程就是: ```text ciphertext │ │ 去掉固定扰动 ▼ data nibbles │ │ inverse permutation ▼ reversed stream │ │ reverse ▼ shifted stream │ │ H -= 5 mod 16 │ L -= 4 mod 16 ▼ ASCII plaintext ``` 最终: ```text flag{T1u_2026_Kc7f_Crypt0_M4ster!} ``` --- ## 一句话复盘 这道题前 70 多分钟基本都在回答: > “121 到底怎么参与运算?” 真正突破以后才发现,更重要的问题其实是: > “哪些半字节是真的数据?” 把固定扰动、半字节偏移和 permutation 分开之后,剩下的逆向就非常直接了。
回复或点赞可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-8-21 09:26 被shuax编辑 ,原因:
收藏
・
0
点赞
・
7
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
飘零丶
为你点赞!
2天前
東陽不列山
为你点赞!
2天前
心游尘世外
感谢你的贡献,论坛因你而更加精彩!
2天前
git_51951meggadf3df
非常支持你的观点!
5天前
mb_dvqqrcce
你的帖子非常有用,感谢分享!
5天前
mb_lthgjpwj
为你点赞!
2026-8-27 15:28
huangyalei
感谢你的积极参与,期待更多精彩内容!
2026-8-21 20:57
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
xingbing
雪 币:
158
活跃值:
(5651)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
1373
粉丝
2
关注
私信
xingbing
2
楼
学习。
2026-8-21 16:10
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
shuax
2
16
发帖
254
回帖
240
RANK
关注
私信
他的文章
[原创]KCTF2026 第八题:亥子合辰·塔影迷楼
16
[原创] 第七题:戌时·暗能潜流 WP
763
KCTF2024 签到题 逐光启航
812
[原创]KCTF2024 第二题 星际生物
6052
KCTF Reverse CORE CrackMe v2.0 是否答案有误
7478
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部