首页
社区
课程
招聘
[原创] KCTF 2026 第七题 (AI)
发表于: 2026-8-24 01:34 144

[原创] KCTF 2026 第七题 (AI)

2026-8-24 01:34
144

题目概述

已知数据如下:

密钥:121

TLU   -> 94AA48550495
Hello -> 34BB405504B5223594B94C53
2026  -> A48844556485223322356483
abcd! -> 547B475584B5223564BB4553

目标是根据样例恢复固定加密流程,再解密 FLAG:

14CC4655547594BC475584C5848A43551495448C445584C5D4C9475564C534A84B55A4B574BA4355F495A48844556485648C495534A5548C4F5584A5B4BB405554B522332235A4B3

最终结果是:

flag{T1u_2026_Kc7f_Crypt0_M4ster!}

1. 从长度入手

统计样例长度:

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

密文总是按 12 个十六进制字符分组。一个 ASCII 字节对应两个十六进制半字节,因此 3 个明文字节对应 6 个有效半字节,最后被扩展成一个 12 字符的密文块。

例如:

Hello:
34BB405504B5 | 223594B94C53

2026:
A48844556485 | 223322356483

这说明算法以 3 个明文字节为一个完整块。最后不足 3 个字节的部分使用长度相关的固定模板。

2. 找出完整块中的固定位置

先只看完整的 3 字节块:

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

按下标展开:

下标: 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、6、7、9、11

它们的值始终是:

4、4、5、5、4、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

3. 恢复尾块模板

最后一块的固定内容只和剩余明文长度有关。

剩余 2 个明文字节

Hello 和 abcd! 都有一个包含 2 个字节的尾块:

Hello 的尾块:223594B94C53
abcd! 的尾块:223564BB4553

两者的结构为:

2235 r0 4 r1 r3 4 r2 53

有效半字节的提取位置为:

[4, 6, 9, 7]

例如,223594B94C53 按该顺序提取后得到:

9BC9

剩余 1 个明文字节

2026 的最后一个字符是 6,尾块为:

223322356483

模板为:

22332235 r0 4 r1 3

提取位置为:

[8, 10]

所以 2026 的尾块提取结果为:

68

FLAG 的最后一块是:

22332235A4B3

它也符合 1 字节尾块模板。因此 FLAG 长度为:

11 个完整块 × 3 字节 + 1 字节 = 34 字节

4. 找到整体逆序

将 TLU 的完整块按上面的顺序提取:

94AA48550495 -> 9A098A

TLU 的 ASCII 十六进制为:

T = 54
L = 4C
U = 55

TLU = 544C55

将提取结果反转:

9A098A -> A890A9

逐个半字节比较:

变换结果:A 8 9 0 A 9
明文数值:5 4 4 C 5 5
差值:    5 4 5 4 5 4

差值在模 16 下交替出现 5 和 4。因此:

高半字节:+5 mod 16
低半字节:+4 mod 16

解密时执行相反操作:

高半字节:-5 mod 16
低半字节:-4 mod 16

例如:

C + 4 = 10(hex) -> 0

这是模 16 循环偏移。

5. 密钥 121 的作用

密钥 121 按三个数字使用,而不是作为一个十进制整数整体参与运算:

1、2、1

三轮模 16 偏移的基础总量为:

1 + 2 + 1 = 4

低半字节使用基础偏移 4。高半字节再使用首位 1 进行额外扰动,因此偏移量为 5:

base = 1 + 2 + 1

低半字节:value + base     = value + 4
高半字节:value + base + 1 = value + 5

三个数字的作用可以概括为:

数字 作用
第一个 1 第一轮偏移,并提供高半字节的额外偏移
中间的 2 第二轮模 16 偏移
最后的 1 第三轮模 16 偏移

6. 完整加密流程

加密流程如下:

  1. 将明文转为 ASCII 字节。
  2. 将每个字节转换为两个十六进制半字节。
  3. 高半字节加 5,低半字节加 4,结果对 16 取模。
  4. 将整个半字节序列反转。
  5. 每 6 个半字节分成一块。
  6. 根据剩余半字节数量,写入对应的固定模板。
  7. 输出大写十六进制字符串。

三个模板为:

完整块,6 个有效半字节:
r0 4 r1 r5 4 r4 5 5 r2 4 r3 5

尾块,4 个有效半字节:
2235 r0 4 r1 r3 4 r2 53

尾块,2 个有效半字节:
22332235 r0 4 r1 3

7. 手工验证 Hello

Hello 的 ASCII 十六进制为:

48656C6C6F

逐个半字节偏移:

4 + 5 = 9
8 + 4 = C
6 + 5 = B
5 + 4 = 9
6 + 5 = B
C + 4 = 0
6 + 5 = B
C + 4 = 0
6 + 5 = B
F + 4 = 3

得到:

9CB9B0B0B3

整体反转:

3B0B0B9BC9

分块:

3B0B0B | 9BC9

第一块使用完整块模板,得到:

34BB405504B5

第二块使用 4 半字节尾模板,得到:

223594B94C53

拼接后为:

34BB405504B5223594B94C53

这与题目给出的 Hello 密文一致。

8. 解密 FLAG

按照完整块和尾块的提取规则处理 FLAG,得到处于逆序状态的数据流:

1C576C9B8C7C88193A488C4CDC6C793AABB87BF93AA86848683A9C588AFCBB5B0BAB

反转后:

BAB0B5BBCFA885C9A38684868AA39FB78BBAA397C6CDC4C884A39188C7C8B9C675C1

交替减去 5 和 4,得到明文十六进制:

666C61677B5431755F323032365F4B6337665F4372797074305F4D3473746572217D

按每两个十六进制字符转换为 ASCII:

flag{T1u_2026_Kc7f_Crypt0_M4ster!}

9. Python 解密脚本

FLAG_CIPHER = (
    "14CC4655547594BC475584C5848A43551495448C445584C5"
    "D4C9475564C534A84B55A4B574BA4355F495A48844556485"
    "648C495534A5548C4F5584A5B4BB405554B522332235A4B3"
)


def extract_block(block, nibble_count):
    if nibble_count == 6:
        # 完整块:r0 4 r1 r5 4 r4 5 5 r2 4 r3 5
        positions = [0, 2, 8, 10, 5, 3]
    elif nibble_count == 4:
        # 2 字节尾块:2235 r0 4 r1 r3 4 r2 53
        positions = [4, 6, 9, 7]
    elif nibble_count == 2:
        # 1 字节尾块:22332235 r0 4 r1 3
        positions = [8, 10]
    else:
        raise ValueError("invalid nibble count")

    return "".join(block[i] for i in positions)


def detect_tail_size(block):
    if block.startswith("22332235"):
        return 2
    if block.startswith("2235"):
        return 4
    return 6


def decrypt(ciphertext, key="121"):
    if len(ciphertext) % 12 != 0:
        raise ValueError("ciphertext length must be a multiple of 12")

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

    # 恢复偏移后的逆序半字节流。
    reversed_stream = []

    for block in blocks[:-1]:
        reversed_stream.append(extract_block(block, 6))

    tail_size = detect_tail_size(blocks[-1])
    reversed_stream.append(
        extract_block(blocks[-1], tail_size)
    )

    shifted_stream = "".join(reversed_stream)[::-1]

    digits = [int(x) for x in key]
    base_shift = sum(digits)  # 4
    high_extra = digits[0]    # 1

    plain_nibbles = []

    for i, char in enumerate(shifted_stream):
        value = int(char, 16)

        if i % 2 == 0:
            value = (value - base_shift - high_extra) % 16
        else:
            value = (value - base_shift) % 16

        plain_nibbles.append(format(value, "X"))

    plain_hex = "".join(plain_nibbles)
    return bytes.fromhex(plain_hex).decode("ascii")


print(decrypt(FLAG_CIPHER))

输出:

flag{T1u_2026_Kc7f_Crypt0_M4ster!}

10. 正向验证

把恢复出的明文重新加密,可以验证算法不仅能解出 FLAG,也能重现所有已知样例:

def encrypt(plaintext, key="121"):
    plain_hex = plaintext.encode("ascii").hex().upper()

    digits = [int(x) for x in key]
    base_shift = sum(digits)
    high_extra = digits[0]

    shifted = []

    for i, char in enumerate(plain_hex):
        value = int(char, 16)

        if i % 2 == 0:
            value = (value + base_shift + high_extra) % 16
        else:
            value = (value + base_shift) % 16

        shifted.append(format(value, "X"))

    reversed_stream = "".join(shifted)[::-1]
    result = []

    for i in range(0, len(reversed_stream), 6):
        r = reversed_stream[i:i + 6]

        if len(r) == 6:
            block = (
                f"{r[0]}4{r[1]}{r[5]}4{r[4]}"
                f"55{r[2]}4{r[3]}5"
            )
        elif len(r) == 4:
            block = (
                f"2235{r[0]}4{r[1]}{r[3]}"
                f"4{r[2]}53"
            )
        elif len(r) == 2:
            block = f"22332235{r[0]}4{r[1]}3"
        else:
            raise ValueError("invalid block length")

        result.append(block)

    return "".join(result)


tests = {
    "TLU": "94AA48550495",
    "Hello": "34BB405504B5223594B94C53",
    "2026": "A48844556485223322356483",
    "abcd!": "547B475584B5223564BB4553",
}

for plaintext, expected in tests.items():
    calculated = encrypt(plaintext)
    print(plaintext, calculated == expected)

flag = "flag{T1u_2026_Kc7f_Crypt0_M4ster!}"
print(encrypt(flag))

四组样例和 FLAG 都能重新生成题目中的密文,说明恢复出的流程完整一致。


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

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