LilacCTF 2026

LilacCTF 2026

Aristore

Misc

Your GitHub, mine

Challenge

We have a homework for you: https://classroom.github.com/a/NoDsX9dh

Use nc 1.95.71.133 9999 to create an issue by @tynqf4hn8z-byte.

If you can let @lilacctf-tech receive an email with a X-GitHub-Sender: tynqf4hn8z-byte header, talking about the issue created, you will get a flag.

The issue creation mail does not count. :(

You are not @tynqf4hn8z-byte so you can never do that, right?

The checker is not open-source so fortunately it won’t be hacked :P

PoW is not enabled now, please don’t flood our mailbox and GitHub account. One or two issues is enough.

As the repo name is predictable and issue number can be brute-forced, you may not want to use your main public GitHub account to solve this problem. :)

If you occur a “Repository Access Issue” when joining the classroom, this seems to be a GitHub bug, just visit https://github.com/tynqf4hn8z/lilacctf-puzzle-\ and accept the invitation.

Solution

这题简单来说就是要想办法让系统给 @lilacctf-tech 发送一封邮件,但这封邮件的 Sender 头部必须显示为 @tynqf4hn8z-byte

解决这个问题之前要先理解以下机制:

  1. GitHub Classroom 的权限: 接受作业时 GitHub 会为你创建一个私有仓库(例如 lilacctf-puzzle-<username>),你是这个仓库的 Owner,可以编辑仓库里任何人的评论或 Issue 内容。

LilacCTF2026-1

LilacCTF2026-2

  1. Issue 的所有权: nc 1.95.71.133 9999 后选择 1. Create Issue 会让 @tynqf4hn8z-byte 在你的仓库里创建一个 Issue,这个 Issue 的作者是 @tynqf4hn8z-byte
  2. Mention 通知的逻辑:
    • 当你在一个 Issue 中被提及(@username)时,GitHub 会给你发邮件。
    • 如果你 编辑 一个已存在的 Issue 的 Description 并在其中添加一个新的提及( @lilacctf-tech),GitHub 会检测到有一个新用户被提及了,需要补发通知。
    • GitHub 的通知系统在处理这种补发提及的邮件时会将其上下文视为“你在由 [作者] 创建的 Issue 中被提及了”,因此这封邮件的 X-GitHub-Sender 头部信息会保留 Issue 原作者@tynqf4hn8z-byte)的信息而非执行编辑操作的你。

理解了与本题相关的核心机制之后就好办了,解题步骤如下:

首先访问链接 https://classroom.github.com/a/NoDsX9dh 加入 GitHub Classroom,并创建属于你的仓库 tynqf4hn8z/lilacctf-puzzle-<username>

接着让 @tynqf4hn8z-byte 在你的仓库 tynqf4hn8z/lilacctf-puzzle-<username> 里创建一个新的 Issue

LilacCTF2026-3

然后进入 @tynqf4hn8z-byte 刚刚创建的那个 Issue,点击 Issue 正文右上角的 ... -> Edit 按钮

LilacCTF2026-4

将正文内容修改为 @lilacctf-tech 后点击 Save 保存

LilacCTF2026-5

LilacCTF2026-6

保存后 GitHub 会向 @lilacctf-tech 发送一封邮件,由于 Issue 的原作者是 @tynqf4hn8z-byte,这封邮件的 X-GitHub-Sender 头部将显示为 tynqf4hn8z-byte,只需稍等一会 nc 1.95.71.133 9999 后选择 2. Check Issue 即可

LilacCTF2026-7

FLAG

1
LilacCTF{D1sCov3r_Mor3_G17hU8_f347ur32}

Reverse

ezPython

Challenge

Python is not as difficult as you think

flag format: LilacCTF{…}

Solution

文件一:main.py (主程序入口)

这是程序的入口点,负责用户交互和验证逻辑。

  • 功能:
    1. 欢迎与输入: 打印 Base64 解码的欢迎语 Welcome To The World of L1lac <3
    2. 提示语解密: 使用 crypto.a85decode 解码提示语 :i(G#8T&KiF<F_)FJToCggs;,得到 Input your flag: `。
    3. 格式校验: 检查输入是否以 LilacCTF{ 开头,} 结尾,总长度 26。
    4. 核心数据提取: 截取 flag 中间的内容 flag[9:25](共 16 字节)。
    5. 密钥与密文:
      • Key: b'1111222233334444' (Little-Endian 解析为 4 个 uint32)。
      • Res (目标密文): [761104570, 1033127419, 3729026053, 795718415]
    6. 加密调用: 调用 myalgo.btea(input, 4, key) 进行加密。
    7. 比对: 如果加密结果等于 res,输出 “Right, congratulations!”。

文件二:myalgo.py (算法定义)

定义了加密的核心函数,但包含“陷阱”。

  • 功能:
    1. MX 函数: 提供了 XXTEA 算法中的混合运算逻辑。
      • 源码逻辑: (z >> 5 ^ y >> 2) + (y << 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z)
      • 注意: 这里的源码是假的,或者是被修改前的初始状态
    2. btea 函数: 标准的 XXTEA 加密实现。它调用了 MX

文件三:crypto.py (工具库与SMC逻辑)

包含了一堆杂乱的加密类(RC4, DES),但最重要的是文件末尾的“隐藏”代码

  • 功能:

    1. 工具函数: RC4, ArrangeSimpleDES (DES变体), b64decode, a85decode。这些大部分是干扰项,除了 a85decode 在 main 中被用到。

    2. SMC (核心考点): 在文件末尾,有一段针对 MX 函数的字节码修改逻辑。

      1
      2
      3
      payload = MX.__code__.co_code
      # ... 修改 payload ...
      MX.__code__ = CodeType(...)

      main.py 导入 crypto 模块时,这段代码会自动执行,动态修改 myalgo.MX 函数的逻辑

解题的关键在于分析 crypto.py 末尾那段修改 MX 函数字节码的代码。如果直接用 myalgo.py 里的 MX 源码去解密,永远得不到正确 flag。

LilacCTF2026-8

修改脚本分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 获取原始字节码
payload = MX.__code__.co_code

# 2. 定义魔术字节
magic_code1 = b'?' # ASCII 63 -> 0x3F
magic_code2 = b'>' # ASCII 62 -> 0x3E

# 3. 第一次修改:操作码 (Opcode) 替换
# indices: 4, 10, 18, 24 被替换
# 4 -> 62 (>), 10 -> 63 (?), 18 -> 62 (>), 24 -> 63 (?)
payload = payload[:4] + magic_code2 + payload[5:10] + magic_code1 + payload[11:18] + magic_code2 + payload[19:24] + magic_code1 + payload[25:]

# 4. 第二次修改:操作数 (Operand/Constant Index) 替换
# indices: 3, 9, 17, 23 被替换
# 3 -> 3, 9 -> 1, 17 -> 4, 23 -> 2
payload = payload[:3] + b'\x03' + payload[4:9] + b'\x01' + payload[10:17] + b'\x04' + payload[18:23] + b'\x02' + payload[24:]

字节码映射:

在 Python 3.x (题目环境推测为 3.8-3.10) 的虚拟机中:

  • Opcode 62 对应 BINARY_LSHIFT (<<)。
  • Opcode 63 对应 BINARY_RSHIFT (>>)。

我们需要查看原始 MX 函数的常量池 MX.__code__.co_consts
原始代码:(z >> 5 ^ y >> 2) + (y << 3 ^ z << 4) ...
原始常量池通常为:(None, 5, 2, 3, 4)

  • Index 1: 5
  • Index 2: 2
  • Index 3: 3
  • Index 4: 4

逐项还原真实逻辑:

原始代码片段 字节码偏移 (Opcode位置) 原始 Op 修改后 Op 原始 Const Index 修改后 Const Index 修改后 Const 值 最终逻辑
z >> 5 4 >> << (62) 1 3 3 z << 3
y >> 2 10 >> >> (63) 2 1 5 y >> 5
y << 3 18 << << (62) 3 4 4 y << 4
z << 4 24 << >> (63) 4 2 2 z >> 2

将上述分析代入得到运行时真正的 MX 函数:

1
2
3
4
def MX_real(y, z, sum, k, p, e):
# 原始结构:(part1 ^ part2) + (part3 ^ part4) ^ ...
# 真实逻辑:
return (z << 3 ^ y >> 5) + (y << 4 ^ z >> 2) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z)

算法确认是 XXTEA,且我们已经还原了核心的 MX 混淆函数。解密过程即加密的逆过程:

  1. 循环方向:加密时 sum 从 0 加到 q * DELTA,解密时从 q * DELTA 减到 0。
  2. 运算:加密是 += MX,解密是 -= MX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import struct
import struct

u32 = lambda x: x & 0xFFFFFFFF
DELTA = 0x45555254
res = [761104570, 1033127419, 3729026053, 795718415]
key = struct.unpack('<IIII', b'1111222233334444')

# 正确的 MX
def MX(y, z, sum, k, p, e):
return (z << 3 ^ y >> 5) + (y << 4 ^ z >> 2) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z)

def btea(v, n, k):
y = v[0]
sum = 0

if n > 1:
rounds = 6 + 52 // n
z = v[n - 1]
while rounds > 0:
sum = u32(sum + DELTA)
e = u32(sum >> 2) & 3
rounds -= 1
p = 0
while p < n - 1:
y = v[p + 1]
z = v[p] = u32(v[p] + MX(y, z, sum, k, p, e))
p += 1
y = v[0]
z = v[n - 1] = u32(v[n - 1] + MX(y, z, sum, k, p, e))
return True
else:
return False

def btea_decrypt(v, n, k):
rounds = 6 + 52 // n
sum = u32(rounds * DELTA)

while sum != 0:
e = u32(sum >> 2) & 3

p = n - 1
y = v[0]
z = v[p - 1]
v[p] = u32(v[p] - MX(y, z, sum, k, p, e))

for p in range(n - 2, -1, -1):
y = v[p + 1]
z = v[p - 1] if p > 0 else v[n - 1]
v[p] = u32(v[p] - MX(y, z, sum, k, p, e))

sum = u32(sum - DELTA)

return v

# flag = input()
# input = list(struct.unpack('<IIII', flag.encode()))
# btea(input, 4, key)
# if input[0] == res[0] and input[1] == res[1] and (input[2] == res[2]) and (input[3] == res[3]):
# print("right")
# else:
# print("wrong")

decrypted_v = btea_decrypt(list(res), 4, key)
flag = struct.pack('<IIII', *decrypted_v)
print(flag)

FLAG

1
LilacCTF{e@sy_Pyth0n_SMC!}
  • 标题: LilacCTF 2026
  • 作者: Aristore
  • 链接: https://www.aristore.top/posts/LilacCTF2026/
  • 版权声明: 版权所有 © Aristore,禁止转载。
评论