区块链安全

math类型ctf-04


看到题目,因为没有改变 isComplete 变量的函数,很容易联想到通过 storage 覆盖的方式,改变 isComplete 变量的值。这个变量的缺省值为0,只要往里面写入了数据,就会大于 0 变成 true。

什么是 Storage?
合约中的状态变量,也就是传统理解中的非函数局部变量的全局变量,这些变量是会上链的。EVM 将合约中的状态变量存放在一个叫 Storage 的键值对虚拟空间。storage也可以理解成连续的数组,称为 slot[],每个位置可以存放32字节的数据。

因为这个合约中只有两个状态变量:
bool public isComplete;
uint256[] map; //动态数组

参考我之前的博文 Ethereum Storage 可以知道 Storage 中这些变量是如下排列的:

很自然的想到如果能通过 slot[1] 这里,map的长度这个变量来上溢,则可以使 isComplete 为 true。

具体操作就是通过 set(一个大数,任意数)。但是实际测试发现,parm1 最多可以传入 2**256-2。但是只有传入 2**256-1,然后通过 map.length == parm + 1 == 2**256,才能造成溢出,但是上溢之后也是等于0的。所以这里暂时不行。

下面是传入了 2**256-2 之后的 storage 布局,可以验证我上面画的图。因为 slot0 里面存放的是0,有时候 remix 调试时候会不显示等于0的slot,但是位置是占位成功的,因而数组的长度只能存在 slot1 中。

所以只能改变思路,去通过键值对的值那个 slot 来上溢。

因为存放值的那个 slot 离 slot0 非常远,是没有办法通过其存放的值上溢到 slot0 的。但是有一种非常巧妙的办法。刚刚上面提到了如果两个数加起来是 2**256,那么对于 uint256 ,也就是32个字节的存储范围会上溢为0。那么我们可以对 slot[x] 的 x 尝试构造上溢,如果 x 等于 2**256,那么实际上也就会是等于0。所以实际上会把值存入 slot[0]。

下面来进行具体尝试。动态数组的第一个元素的位置计算公式为:keccak256(bytes32(position)),这个 position 是指动态数组长度存放的 slot 位置这里为1,所以这个元素的位置为 slot[keccak256(bytes32(1))]。

通过合约计算一下结果为:0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6

pragma solidity ^0.4.21;

contract calc { 
    bytes32 a;
    function calcPosition() public returns(bytes32){
            a = keccak256(bytes32(1));
            return a;
    }
    
}

而任意一个动态数组内变量所在的存储位的计算公式为,index 为数组下标(正如之前的博文里面介绍的,元素存储位置是依次递增的):

hash + index

所以要使得 hash+index == 2**256,那么
index = 2**256 – 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 = 35707666377435648211887908874984608119992236509074197713628505308453184860938
下图是 python2 计算:

所以分析至此,应该很容易知道应该怎么做了:set(key,value):

  • key为刚刚计算的 index == 35707666377435648211887908874984608119992236509074197713628505308453184860938
  • value 为随便一个正数,比如233

交易见:https://ropsten.etherscan.io/tx/0x14bdd4321b9073d9fff5bc00325941b553cca1905e659dffa7a9813fef4092df

然后发现 slot1 的值被覆盖成功:

通关成功:

总结:

通过 push 对动态数组添加元素跟通过 index 去指定元素的值是一样的。
计算 value 存放的 slot 的公式为:hash + index == keccak256(bytes32(position)) + index。
注:position 为存放动态数组长度的 slot。