ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

EVM存储机制及安全性

2022-08-14 10:31:42  阅读:154  来源: 互联网

标签:存储 EVM 32 public ----------------------------------------------------- password 


欢迎加入我们的区块链社区

群里还有一些关于solidity学习的心得分享

还有一些免费小工具分享

 

 

EVM存储机制及安全性

EVM存储结构

EVM 存储数据分为两类:

  • 存储在 code 和 storage 里的数据是 non-volatile (不容易丢失的)

  • 存储在 stack,args,memory 里数据是volatile(容易丢失的)

我们主要来了解一下关于Storage中数据的存储机制。

storage

Storage 是一个可以读写修改的持久存储的空间,也是每个合约持久化存储数据的地方。Storage 是一个巨大的 map,一共 2^256 个插槽 (slot),每个插糟有 32byte,合约中的“状态变量”会根据其具体类型分别保存到这些插槽中。

# 插槽式数组存储
----------------------------------
|               0               |     # slot 0
----------------------------------
|               1               |     # slot 1
----------------------------------
|               2               |     # slot 2
----------------------------------
|             ...               |     # ...
----------------------------------
|             ...               |     # 每个插槽 32 字节
----------------------------------
|             ...               |     # ...
----------------------------------
|           2^256-1             |     # slot 2^256-1
----------------------------------

存储概述

变量类型

Solidity的数据变量类型分为两类

  • 值类型-value type

  • 引用类型-reference type

简单分析

1.对于大小在 32 字节以内的变量(常量),以其定义的顺序作为它的索引值来存储。即第一个变量的索引为 key(0),第二个变量的索引为 key(1)...

2.对于连续较小的值,可能被优化存储在同一个位置,比如:合约中前四个状态变量都是 uint64 类型的,则四个状态变量的值会被打包成一个 32 字节的值存储在 0 位置。这也被称为优化存储原则。

举个例子,先看下面的合约。

pragma solidity ^0.4.0;

contract C {
  address a;     // 0
  uint8 b;       // 0
  uint256 c;     // 1
  bytes24 d;     // 2
}

其布局如下

-----------------------------------------------------
| unused (11) | b (1) |           a (20)           | <- slot 0
-----------------------------------------------------
|                       c (32)                     | <- slot 1
-----------------------------------------------------
| unused (8) |               d (24)               | <- slot 2
-----------------------------------------------------

首先,a为地址类型,在solidity中,地址类型占160bit,即20个字节,此时插槽0中还剩12字节。而b为unint8类型,为1字节,根据优化存储原则,此时b也储存在插槽0中,此时slot0中还剩11字节的空间未使用。

c为uint256,256bit为32字节,在slot0中无法存储,但刚好能将slot1存储满。

d为24比特数据,存储在slot2中,此时slot2中还剩8字节的空间未使用。

这就是简单的数据存储机制了。

练习

web3.eth.getStorageAt(address, position [, defaultBlock] [, callback])

使用getStorageAt可以读取指定slot下的内容。

练习1-Vault

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Vault {
 bool public locked;
 bytes32 private password;

 constructor(bytes32 _password) public {
   locked = true;
   password = _password;
}

 function unlock(bytes32 _password) public {
   if (password == _password) {
     locked = false;
  }
}
}

定义为私有变量只能组织其他合约访问,但是无法阻止公开访问。

-----------------------------------------------------
| unused (31) |           locked(1)          | <- slot 0
-----------------------------------------------------
|                       password (32)                      | <- slot 1
-----------------------------------------------------

password的存储位置为slot1

尽管它是私有变量,但我们仍然可以通过web3.eth.getStorageAt(contract.address, 1)来获取password的值。

调用unlock方法实现对合约的解锁。

提交实例,本关卡成功通过。

练习2-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;

我们需要读取data[2]中的数据。

data为字节数组,我们只需要读取数组中最后一组的数据值,按照之前所讲的分布原则,我们有槽存储分布如下:

-----------------------------------------------------
| unused (31)   |         locked(1)               | <- slot 0
-----------------------------------------------------
|                       ID(32)                     | <- slot 1
-----------------------------------------------------
| unused (28) | awkwardness(2) | denomination (1) | flattening(1) | <- slot 2
-----------------------------------------------------
| data[0](32) | <- slot 3
-----------------------------------------------------
| data[1](32) | <- slot 4
-----------------------------------------------------
| data[2](32) | <- slot 5
-----------------------------------------------------

所以,data[2]存储在slot 5里。

调用await web3.eth.getStorageAt(contract.address,5)

此时bytes16与bytes32之间存在转换。要注意,以太坊有两种存储方式,大端(strings & bytes,从左开始)及小端(其他类型,从大开始)。因此,从32到16转换时,需要砍掉右边的16个字节。

'0x55a8396c56d3d9a30d6f4f5fcb51c4f9754917dce02788ddfd70769483c89716'.slice(0,34)

解锁合约:contract.unlock('0x55a8396c56d3d9a30d6f4f5fcb51c4f9')

此时合约已经完成解锁。

提交实例,本关卡成功!

安全问题

前面已经讲到EVM的存储结构及存储机制,现在我们再来探讨其安全问题。

未初始化变量

漏洞原理:

在官方手册中提到结构体,数组和映射的局部变量默认是放在 storage 中的,而 solidity 语言中函数中设置的局部变量的默认类型取决于它们本身的类型。

因此如果在函数内部设置以上 storage 类型变量却没有进行初始化,他们就相当于存储指针指向合约中的其他变量,当我们对其进行改变时改变的就是其指向的变量。

漏洞合约,目的修改 owner 为自己地址:

pragma solidity ^0.4.0;
contract testContract{
bool public unlocked = false;
address public owner = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
struct Person {
bytes32 name;
address mappedAddress;
}
function test(bytes32 _name , address _mappedAddress) public{
Person person;
person.name = _name;
person.mappedAddress = _mappedAddress;
require(unlocked);
}
}

具体操作:

调用test函数分别传入向_name 传入:0x0000000000000000000000000000000000000000000000000000000000000001(真值)

_mappedAddress 传入:0xd7471C7eaF78Dcb1bF19A33e470A039Dd72639dd(个人地址)

此时就_mappedAddress 传入的参数即指向了owner,即可修改owner为我们自己的地址。

总结

区块链上没有秘密

EVM 的存储器是和智能合约语言进行交互的,当其中一些规则发生冲突很可能就被别有用心的人用来作恶,所以规范的使用智能合约语言是避开漏洞的必要条件。

标签:存储,EVM,32,public,-----------------------------------------------------,password,
来源: https://www.cnblogs.com/SeanGyy/p/16584899.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有