紫罗兰永恒花园_wp

admin 2026-03-03 07:09:08 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文是CTF密码学题解,针对模素数域下矩阵方程求解问题。作者分析了题目交互逻辑,利用有限域矩阵求逆原理,通过Python的sympy库编写自动化脚本,成功解出未知向量并通过5轮挑战。文章详细展示了从算法推导到脚本实现的全过程,并指出获取Shell后需寻找特定路径下的真实Flag,具有较强的实战指导意义。 综合评分: 89 文章分类: CTF,实战经验


cover_image

紫罗兰永恒花园_wp

原创

wenject wenject

船山信安

2026年2月23日 15:56 江苏

Mechanical Resonance — 薇尔莉特的打字机共鸣

“我想知道,’爱’是什么。” —— 薇尔莉特·伊芙加登

前言

这道题的包装太戳我了。紫罗兰永恒花园的世界观,薇尔莉特用机械义手校准打字机的连杆共鸣来写信——出题人是懂浪漫的。

题目本质是模素数域上的矩阵方程求解,但整个交互过程像是在帮薇尔莉特一封一封地传递情感,挺有意思。

image-20260223144001721

题目信息

  • 类型:Crypto(密码学)
  • 附件:task.py
  • 靶机:nc 连接

第一步:分析附件

拿到的 task.py 非常简短:

def mechanical_resonance(matrix, vector, modulus):
    n = len(matrix)
    if len(vector) != n:
        returnNone

    result = [0] * n
    for i in range(n):
        val = 0
        for j in range(n):
            val = (val + matrix[i][j] * vector[j]) % modulus
        result[i] = val
    return result

if __name__ == "__main__":
    pass

乍一看 __main__ 是空的,可能会觉得信息不够。但仔细看这个函数,它做的事情用数学语言描述就是:

给定一个 n×n 的矩阵 A、一个长度为 n 的向量 X、和一个模数 p,计算 B = A * X mod p

这就是线性代数里最基本的矩阵乘向量运算,只不过所有运算都在模 p 下进行。

附件告诉我们的是服务端的核心算法。那么靶机大概率会给我们 A 和 B,让我们反过来求 X。

第二步:连接靶机,观察交互

nc 连上去,映入眼帘的是紫色的标题(终端里有 ANSI 颜色码):

    Violet Evergarden
    The Mechanical Heart Resonance System

请协助我校准打字机的内部连杆共鸣,以便写出完美的信件。
----------------------------------------

[Chapter 1]
少佐... 我正在努力理解这封信的含义。
当前机械结构复杂度: 5 维
共鸣模数 (Modulus): 17
连杆传递矩阵 (Transition Matrix A):
[
  [7, 0, 14, 1, 7],
  [1, 8, 5, 8, 6],
  [13, 11, 7, 2, 11],
  [0, 5, 12, 8, 8],
  [14, 2, 2, 15, 4],
]
目标情感频谱 (Target Vector B):
[12, 13, 14, 7, 7]
请输入校准向量 X (以空格分隔):

果然如我们猜测的那样。每一轮(Chapter)给出:

  • 维度 n
  • 模数 p(都是素数)
  • n×n 矩阵 A
  • 目标向量 B

要求输入向量 X,使得 A * X ≡ B (mod p)

一共 5 轮,难度递增:

| Chapter | 维度 | 模数 | 说明 | | — | — | — | — | | 1 | 5 | 17 | 热身,手算都行 | | 2 | 20 | 251 | 开始需要脚本了 | | 3 | 50 | 997 | 矩阵变大 | | 4 | 100 | 10007 | 相当大了 | | 5 | 150 | 65537 | 150×150 的矩阵,必须自动化 |

每轮的场景文字都是薇尔莉特写信的独白,比如”这些连杆的震动,就像是某种未被诉说的心跳”、”最后一刻… 我仿佛看见了那条在风中飘扬的绿宝石胸针”。出题人真的很用心。

第三步:数学原理

问题是什么?

我们要解的方程是:

A * X ≡ B (mod p)

其中 A 是已知的 n×n 矩阵,B 是已知的长度为 n 的向量,p 是素数,X 是我们要求的未知向量。

怎么解?

在普通线性代数里,解 A * X = B 就是 X = A⁻¹ * B(A 的逆矩阵乘以 B)。

在模运算下也是一样的,只不过所有运算都要 mod p。因为 p 是素数,所以 Z/pZ 构成一个有限域(Field),矩阵求逆是可行的(只要矩阵行列式不为 0)。

具体来说:

  1. 计算矩阵 A 在模 p 下的逆矩阵 A⁻¹
  2. 计算 X = A⁻¹ * B mod p

用什么工具?

Python 的 sympy 库提供了 Matrix.inv_mod(p) 方法,可以直接求模逆矩阵。一行代码搞定:

from sympy import Matrix

A = Matrix(matrix_data)      # 构造矩阵
A_inv = A.inv_mod(modulus)   # 求模逆
X = A_inv * Matrix(target)   # 乘以目标向量
solution = [int(x) % modulus for x in X]  # 取模得到结果

第四步:编写自动化脚本

因为有 5 轮交互,而且后面几轮矩阵非常大(150×150),手动操作是不现实的。需要写一个脚本自动完成:

  1. 连接服务器
  2. 接收数据,解析出矩阵 A、向量 B、模数 p
  3. 用 sympy 求解 X
  4. 发送答案
  5. 循环直到 5 轮结束

解析数据的关键正则:

  • 模数:Modulus\):\s*(\d+)
  • 矩阵行:\[([0-9,\s]+)\] 匹配所有方括号内的数字列表
  • 目标向量:Vector B\):\s*\[([0-9,\s]+)\]

第五步:拿到 Shell 后找 flag

5 轮全部通过后,服务端输出:

所有的信件都已完成。我想... 我已经稍微懂得了【爱】。
Access Granted. Interactive Shell Activated.

image-20260223143756338

薇尔莉特终于理解了爱,而我们拿到了一个真正的 shell(服务端 server.py 里用 os.execl("/bin/sh", "sh") 启动的)。

真正的 flag

继续翻文件系统,发现 /tmp/flag.txt

$ cat /tmp/flag.txt
flag{c3326fd7-7a19-48f6-aad9-a5caed4a4b52}

完整 EXP

依赖:pip install sympy

import socket
import re
import time
from sympy import Matrix

HOST = ''# 靶机地址
PORT = 0   # 靶机端口

def solve_mod_linear(matrix, target, mod):
    """求解 A * X ≡ B (mod p)"""
    n = len(matrix)
    A = Matrix(matrix)
    B = Matrix(n, 1, target)
    A_inv = A.inv_mod(mod)  # 求 A 的模逆矩阵
    X = A_inv * B
    return [int(x) % mod for x in X]

def recv_until_prompt(s, timeout=15):
    """接收数据直到看到输入提示"""
    s.settimeout(timeout)
    data = b''
    whileTrue:
        try:
            chunk = s.recv(4096)
            ifnot chunk:
                break
            data += chunk
            text = data.decode('utf-8', errors='replace')
            if'请输入校准向量'in text or'请输入'in text:
                break
            if'flag{'in text.lower():
                break
            if'Shell Activated'in text or'Access Granted'in text:
                break
        except socket.timeout:
            break
        except:
            break
    return data.decode('utf-8', errors='replace')

def recv_all(s, timeout=5):
    """接收所有可用数据"""
    s.settimeout(timeout)
    data = b''
    whileTrue:
        try:
            chunk = s.recv(4096)
            ifnot chunk:
                break
            data += chunk
        except:
            break
    return data.decode('utf-8', errors='replace')

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
    print("[*] Connected!")

    round_num = 0
    whileTrue:
        round_num += 1
        data = recv_until_prompt(s)
        print(f"[Round {round_num}]")

        # 检查是否直接收到 flag
        flag_match = re.search(r'flag\{[^}]+\}', data, re.IGNORECASE)
        if flag_match:
            print(f"FLAG: {flag_match.group()}")
            break

        # 检查是否拿到 shell
        if'Shell Activated'in data or'Access Granted'in data:
            print("[*] Got shell! Reading /tmp/flag.txt...")
            time.sleep(0.5)
            # 真正的 flag 在 /tmp/flag.txt,不是环境变量里的假 flag
            s.send(b'cat /tmp/flag.txt\n')
            time.sleep(1)
            resp = recv_all(s, timeout=3)
            print(resp)
            fm = re.search(r'flag\{[^}]+\}', resp, re.IGNORECASE)
            if fm:
                print(f"FLAG: {fm.group()}")
            break

        # 解析模数
        mod_match = re.search(r'[Mm]odulus\)?:\s*(\d+)', data)
        ifnot mod_match:
            break
        modulus = int(mod_match.group(1))

        # 解析目标向量 B
        target_match = re.search(r'Vector B\):\s*\[([0-9,\s]+)\]', data)
        ifnot target_match:
            target_match = re.search(r'频谱.*?\[([0-9,\s]+)\]', data)
        target = [int(x.strip()) for x in target_match.group(1).split(',')]
        n = len(target)

        # 解析矩阵 A(提取所有长度为 n 的方括号数组,取前 n 个作为矩阵行)
        all_groups = re.findall(r'\[([0-9,\s]+)\]', data)
        matrix = []
        for g in all_groups:
            nums = [int(x.strip()) for x in g.split(',')]
            if len(nums) == n:
                matrix.append(nums)
                if len(matrix) == n:
                    break

        # 求解并发送
        solution = solve_mod_linear(matrix, target, modulus)
        answer = ' '.join(map(str, solution))
        s.send((answer + '\n').encode())
        print(f"  dim={n}, mod={modulus}, sent answer")

    s.close()

if __name__ == '__main__':
    main()

总结

这道题的核心知识点就一个:模素数域上的线性方程组求解。附件 task.py 给出了正向计算(矩阵乘向量取模),靶机要求逆向求解。用 sympy 的 inv_mod 可以很方便地完成。

唯一的坑是拿到 shell 后环境变量里的 flag{san} 是假的(出题人的小恶作剧,”san” = 少佐),真正的 flag 在 /tmp/flag.txt

最后用薇尔莉特的话结尾吧:

“所有的信件都已完成。我想… 我已经稍微懂得了【爱】。”

-END-


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:船山信安 wenject wenject《紫罗兰永恒花园_wp》

紫罗兰永恒花园_wp 网络安全文章

紫罗兰永恒花园_wp

文章总结: 本文是CTF密码学题解,针对模素数域下矩阵方程求解问题。作者分析了题目交互逻辑,利用有限域矩阵求逆原理,通过Python的sympy库编写自动化脚本
评论:0   参与:  0