【Ethernaut x Foundry】Level 12-Privacy 答案解說
你以為你擁有隱私權,真的只是你以為
看完這篇你會學到:
- Private Variable 特性
- Storage Layout 排序規則
題目
此次目標是解鎖合約,把 locked
狀態變成 False
即可通關。
合約程式碼如下:
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(block.timestamp);
bytes32[3] private data;
constructor(bytes32[3] memory _data) {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
解說
這題和 Level-08 Vault 的考點非常類似,同樣涉及如何讀取 private variable 和 storage layout 的儲存變數規則。
此次目標是解鎖合約,要透過呼叫 unlock()
去把狀態改成解鎖,不過這個函式需要正確的鑰匙 bytes16 _key
且符合 data[2]
數據才能解鎖,因此首要任務是想辦法找出這把鑰匙。
還記得在第八關時,提到了 private variable 和 storage slot 的觀念,智能合約上的變數設定為 private,僅表示它只供內部合約存取,無法從外部或繼承合約存取,但還是可以查看到 private 變數的數值!而在 Foundry 查找變數的 cheatcode 是 vm.load()
,要給兩個參數,contract address 和 slot 位置。contract address 我們已知,接下來就是要釐清 slot 位置,但要怎麼知道 data[2]
它所在的 slot 在哪?
可以自己動手數!由於合約的 storage variable 已知,剩下只要按照順序計算每個變數大小,按規則寫入 slot 答案便出來了。以下先複習常見變數的 bytes 大小:
- bool:1 byte
- address:20 bytes
- uint256 / int256:32 bytes
- uint8 / int8:1 byte (後綴數字除以 8 依此類推)
- 固定大小的 bytes1 / bytes2 / … / bytes32:大小等同後綴數字
- 固定陣列 bytes32[3]:每個元素皆 bytes32 (取決於陣列的資料型態)
因為這題沒有使用到動態陣列或 string,所以可以先 pass 他們。知道合約每個變數大小之後,再來是照順序安排進 storage slot,如同之前文章所說:slot 是從 0 開始,每個 slot 可以存 32 bytes 長度,同一個 slot 裡的變數大小加總超過 32 bytes 則會往下一個 slot 儲存。照著這個規則整理,可以得到如下圖的 storage layout:
而我們要找的 data[2]
儲存在 slot 5!最後,記得用拿到的 key 當作參數去 call unlock(bytes16)
就大功告成啦!
BTW,固定長度的 value type variable 在排 layout 的時候很直覺,slot 能塞就塞,不能塞就往下一個塞,但如果遇到 dynamically-sized variable (dynamic-array / string) 或 mapping 的排法會稍微不同,可以參考這篇文章,解釋得很讚!
完整 solution script 如下,這題也同樣不需要攻擊合約即可通關~
// script/solutions/12-Privacy.s.sol
contract PrivacySolution is Script, EthernautHelper {
...
function run() public {
...
// YOUR SOLUTION HERE
/**
* Understanding Solidity’s Storage Layout And How To Access State Variables In Storage Slots.
*/
bytes32 key = vm.load(challengeInstance, bytes32(uint256(5)));
challengeInstance.call(abi.encodeWithSignature("unlock(bytes16)", bytes16(key)));
...
}
}