math类型ctf-04
- 题目:https://capturetheether.com/challenges/math/mapping/
- 部署的合约地址:0x87765b64c0aB2F071B73e52156fFe30445C82a7E
看到题目,因为没有改变 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。
师傅我看你那个storage结构是slot(0)里边是 isComplete在高位,但一般不是以低位对齐的方式存储的吗?