2018年11月6日，DVP上线了一场“地球OL真实盗币游戏”，其中第二题是一道智能合约题目，题目中涉及到的了一个很有趣的问题，这里拿出来详细说说看。

### Writeup

pragma solidity ^0.4.21;
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}

function div(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a / b;
return c;
}

function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}

function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
}
contract ERC20 is ERC20Basic {

function approve(address spender, uint256 value) public returns (bool);
event Approval(
uint256 value
);
}

library SafeERC20 {
function safeTransfer(ERC20Basic token, address to, uint256 value) internal {
require(token.transfer(to, value));
}

function safeTransferFrom(
ERC20 token,
uint256 value
)
internal
{
require(token.transferFrom(from, to, value));
}

function safeApprove(ERC20 token, address spender, uint256 value) internal {
require(token.approve(spender, value));
}
}

contract DVPgame {
ERC20 public token;
uint256[] map;
using SafeERC20 for ERC20;
using SafeMath for uint256;

}

function (){
if(map.length>=uint256(msg.sender)){
require(map[uint256(msg.sender)]!=1);
}

if(token.balanceOf(this)==0){
//airdrop is over
selfdestruct(msg.sender);
}else{
token.safeTransfer(msg.sender,100);

if (map.length <= uint256(msg.sender)) {
map.length = uint256(msg.sender) + 1;
}
map[uint256(msg.sender)] = 1;

}
}

//Guess the value(param:x) of the keccak256 value modulo 10000 of the future block (param:blockNum)
function guess(uint256 x,uint256 blockNum) public payable {
require(msg.value == 0.001 ether || token.allowance(msg.sender,address(this))>=1*(10**18));
require(blockNum>block.number);
}
if (map.length <= uint256(msg.sender)+x) {
map.length = uint256(msg.sender)+x + 1;
}

map[uint256(msg.sender)+x] = blockNum;
}

//Run a lottery
function lottery(uint256 x) public {
require(map[uint256(msg.sender)+x]!=0);
require(block.number > map[uint256(msg.sender)+x]);
require(block.blockhash(map[uint256(msg.sender)+x])!=0);

selfdestruct(msg.sender);
}
}
}


fallback函数

function (){
if(map.length>=uint256(msg.sender)){
require(map[uint256(msg.sender)]!=1);
}

if(token.balanceOf(this)==0){
//airdrop is over
selfdestruct(msg.sender); //如果token花完了，就会自动销毁自己发送余额
}else{
token.safeTransfer(msg.sender,100); // 否则就给你转100token

if (map.length <= uint256(msg.sender)) {
map.length = uint256(msg.sender) + 1;  // 通过做map变量偏移操作来使空投只发1次
}
map[uint256(msg.sender)] = 1;
}
}


guess函数

 //Guess the value(param:x) of the keccak256 value modulo 10000 of the future block (param:blockNum)
function guess(uint256 x,uint256 blockNum) public payable {
require(msg.value == 0.001 ether || token.allowance(msg.sender,address(this))>=1*(10**18)); // guess要花费0.001 ether
require(blockNum>block.number); // blockNum要大于当前block.number
}
if (map.length <= uint256(msg.sender)+x) {
map.length = uint256(msg.sender)+x + 1;
}

map[uint256(msg.sender)+x] = blockNum;  // 可以想向任意地址写入blockNum
}


lottery函数

function lottery(uint256 x) public {
require(map[uint256(msg.sender)+x]!=0); // 目标地址必须有值
require(block.number > map[uint256(msg.sender)+x]); // 这点是和前面guess函数对应，必须在之后开奖
require(block.blockhash(map[uint256(msg.sender)+x])!=0); // 不能使中间的参数为当前块为0

// 计算hash的后4位

if (x == answer) { // 如果相等，则转账并销毁
selfdestruct(msg.sender);
}
}


constructor(address addr) payable{
}


pragma solidity ^0.4.21;
contract DVPgame {
ERC20 public token;
uint256[] map;
using SafeERC20 for ERC20;
using SafeMath for uint256;
...


memory : 内存，生命周期仅为整个方法执行期间，函数调用后回收，因为仅保存临时变量，故GAS开销很小 storage : 永久储存在区块链中，由于会永久保存合约状态变量，故GAS开销也最大 stack : 存放部分局部值类型变量，几乎免费使用的内存，但有数量限制

ERC20 public token;
uint256[] map;
using SafeERC20 for ERC20;
using SafeMath for uint256;


map[uint256(msg.sender)+x] = blockNum;


address(map_data) = sha(array_slot)+offset


pragma solidity ^0.4.21;

contract dvp_attack {

}

i = 0;
}

function attack(uint256 x,uint256 blockNum){
// modity owner
// fallback
}
}


### 在题目之后

#### array

uint256[] map;


map就是一个uint类型的数组，在storage中，map变量的储存地址计算公式如下

address(map_data) = sha3(slot)+offset


map[2] = 22333
==>
==>
storage[sha3(1)+2] = 22333


#### mapping

mapping (address => uint) balances;


address(balances_data) = sha3(key, slot)


balances[0xaaa] = 22333 //0xaaa为address
==>
==>
storage[sha3(0xaaa,0)] = 22333


#### mapping + struct

struct Person {
uint funds;
}



address(people_data) = sha3(key,slot)+offset


people[0xaaa].addr[1] = 0xbbb
==>
==>
storage[sha3(sha3(0xaaa,0)+0)+1] = 0xbbb


### 漏洞影响范围

“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。目前Haotian平台智能合约审计功能已经集成了该规则。