首页
社区
课程
招聘
[原创]第七题:戌时·暗能潜流
发表于: 13小时前 39

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

13小时前
39

四组样例的密文长度都是明文长度的四倍:一个明文字节最终对应四个十六进制字符。将密文每 12 个字符分组,正好对应三个明文字节。

先比较所有完整三字节块:

TLU    -> 94AA48550495
Hel    -> 34BB405504B5
202    -> A48844556485
abc    -> 547B475584B5

这些块的第 1、4、6、7、9、11 位始终不变,其结构为:

q0 4 q1 q2 4 q3 5 5 q4 4 q5 5

提取位置是:

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

再比较两个剩余两字节的尾块:

lo -> 223594B94C53
d! -> 223564BB4553

可以得到两字节尾块结构:

2 2 3 5 q0 4 q1 q2 4 q3 5 3

提取位置是:

[4, 6, 7, 9]

2026 最后的单字节尾块为:

223322356483

对应结构:

2 2 3 3 2 2 3 5 q0 4 q1 3

提取位置是:

[8, 10]

三类固定结构只是外层包装。去掉包装后,每个明文字节均剩下一对十六进制半字节。

恢复内部变换

从样例中提取出的半字节对如下:

TLU   -> 9A A8 09
Hello -> 3B B0 0B 9B 9C
2026  -> A8 84 68 68
abcd! -> 57 B7 8B 6B BB

密钥为 121。把每个半字节对看成长度为 2 的循环序列:旋转 1 位会交换两个半字节,旋转 2 位则回到原序。因此逆向时按输出位置循环执行:

1 -> 交换 hi、lo
2 -> 保持 hi、lo
1 -> 交换 hi、lo

每三个字节对还存在一次固定位置重排:

[0, 2, 1]

该排列是自身的逆排列。还原分组顺序后,再将整个字节对数组逆序。

TLU 为例,首先从包装中提取:

9A A8 09

逆密钥 121

A9 A8 90

[0, 2, 1] 重排:

A9 90 A8

整体逆序:

A8 90 A9

此时与明文 54 4C 55 的关系已经很明显:每个高半字节都增加了 5,每个低半字节都增加了 4,运算均在模 16 下进行。

A8 - (5,4) = 54 = 'T'
90 - (5,4) = 4C = 'L'
A9 - (5,4) = 55 = 'U'

Hello 继续验证。提取结果是:

3B B0 0B 9B 9C

依次逆密钥、逆分组重排、整体逆序后得到:

9C B9 B0 B0 B3

分别对高、低半字节减去 5 和 4:

48 65 6C 6C 6F -> Hello

其余两组样例也能得到原始明文,说明完整逆变换已经确定。

解密流程

完整解密顺序如下:

  1. 每 12 个十六进制字符划分一个外层块;
  2. 按完整块、两字节尾块或单字节尾块的位置提取 q
  3. 按密钥 121 循环交换对应字节对的高、低半字节;
  4. 每三个字节对执行 [0, 2, 1]
  5. 将整个字节对数组逆序;
  6. 高半字节减 5,低半字节减 4,全部按模 16 计算;
  7. 合并半字节并按 ASCII 解码。

Exp

FLAG_CIPHER = (
    "14CC4655547594BC475584C5848A43551495448C445584C5D4C9475564C534A84B55A4B574BA4355F495"
    "A48844556485648C495534A5548C4F5584A5B4BB405554B522332235A4B3"
)

FULL_POS = (0, 2, 3, 5, 8, 10)
TAIL2_POS = (4, 6, 7, 9)
TAIL1_POS = (8, 10)


def decrypt(ciphertext):
    blocks = [ciphertext[i:i + 12] for i in range(0, len(ciphertext), 12)]

    if blocks[-1].startswith("22332235") and blocks[-1].endswith("3"):
        tail = 1
    elif blocks[-1].startswith("2235") and blocks[-1].endswith("53"):
        tail = 2
    else:
        tail = 0

    q = []
    full_blocks = blocks if tail == 0 else blocks[:-1]
    for raw in full_blocks:
        q.extend(int(raw[i], 16) for i in FULL_POS)

    if tail == 2:
        q.extend(int(blocks[-1][i], 16) for i in TAIL2_POS)
    elif tail == 1:
        q.extend(int(blocks[-1][i], 16) for i in TAIL1_POS)

    ordered = []
    for i in range(0, len(q), 2):
        hi, lo = q[i], q[i + 1]
        if (1, 2, 1)[(i // 2) % 3] == 1:
            hi, lo = lo, hi
        ordered.append((hi, lo))

    reversed_pairs = []
    for i in range(0, len(ordered), 3):
        group = ordered[i:i + 3]
        if len(group) == 3:
            reversed_pairs.extend((group[0], group[2], group[1]))
        else:
            reversed_pairs.extend(group)

    plain = []
    for hi, lo in reversed(reversed_pairs):
        value = (((hi - 5) & 0xF) << 4) | ((lo - 4) & 0xF)
        plain.append(value)
    return bytes(plain)


flag = decrypt(FLAG_CIPHER)
print(flag.decode())

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

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回