(https://ethernaut.openzeppelin.com/level/0x9451961b7Aea1Df57bc20CC68D72f662241b5493)
這一關的目標仍是要將Contract的Owner改成player。
進入到了4/10 difficuty了,越來越有難度了,提示可以去看看delegatecall這個low level function,因此我們先來看看delegatecall,再來解析程式碼跟破關。
Delegatecall is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.
從上述solidity document中所寫的文字中還是比較抽象,瞭解後,簡單說delegatecall可以讓一個contract A去執行另一個contract B的函數(邏輯),但是呢,改變的變數是改變contract A 的變數,同時msg.sender(以及msg.value)的值還是原本contract A 中msg.sender的值,感覺就好像直接把Contract B的程式碼貼過來Contract A,然後在原本Contract A的環境執行,神奇!
瞭解了delegatecall函數後,我們就有了攻破這一關的關鍵武器了,來解析一下程式碼,發現在contract Delegate中有一個函數為pwn,會去設定owner = msg.sender,若我們的執行的contract Delegation中有一個delegatecall 去呼叫 Delegate.pwn()那就成了! 再往下一讀,好幸運,確實有一個fallback函數會去執行Delegate contract中的函數,只要我們把msg.data設定成 pwn()函數就成了。
//Delegate contract中此函數可設定owner = msg.sender
function pwn() public {
owner = msg.sender;
}
//Delegation 的constructor 會幫我們取得Delegate Contract 的Address來運用
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
//此fallball函數會去呼叫Delegate contract的函數
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
綜上所述,我們取得此關的instance後,先確認一下contract.owner(),果然不是我;然後用一個sendTransaction來引發contract 的fallback,同時要輸入data: pwn(),執行後,再看一下contract.owner,此時的owner應該已經變成player了,submit,過關! 具體程式碼如下:
await contract.owner();
await contract.sendTransaction({data:web3.utils.sha3("pwn()")});
await cotnract.owner();
* 附註:
delegatecall()最經典的用途為contract 升級,因為如我們知道的,contract 一旦部署了就不能更改,所以有一種做法可以大成contract的調整,首先將數據合約與邏輯合約分開,數據合約用delegatecall去呼叫邏輯合約(數據合約的constructor 需要帶邏輯合約的address),而有合約升級的時候,只要數據合約帶入新的合約的address就可以使用升級合約的邏輯了。但Ethernaut也提醒,很多過去的駭客事件也都是利用delegatecall()這個工具,因此使用這個powerful的工具時要特別小心。