- UID
- 169246
- 阅读权限
- 90
- 注册时间
- 2021-11-25
- 最后登录
- 1970-1-1
- 在线时间
- 小时
- 人气
- 点
- MC币
- 个
- 贡献
- 点
|
前言EVM 是一个轻量级的虚拟机,其设计初衷就是提供一种可以忽略硬件、操作系统等兼容性的虚拟的执行环境供以太坊络运行智能合约。imtoken官网的相关知识也可以到网站具体了解一下,有专业的客服人员为您全面解读,相信会有一个好的合作!https://www.imtoken.vote
简单来说 EVM 是一个完全独立的沙盒,在 EVM 中运行的代码是法访问络、文件系统和其他进程的,以此来避免错误的代码能让智能合约毁灭或者影响外部环境。
在此基础上,知道创宇区块链安全验室 带大家一起深入理解 EVM 的存储机制和安全问题。
EVM存储结构
可以看到 EVM 存储数据分为两类:
存储在 和 里的数据是 - (不容易丢失的)存储在 ,, 里数据是(容易丢失的)各个存储位置的含义C
部署合约时储存 字段也就是合约内容的空间,即专门存储智能合约的二进制源码的空间
S
S 是一个可以读写修改的持久存储的空间,也是每个合约持久化存储数据的地方。S 是一个巨大的 ,一共 2^256 个插槽 (),每个插糟有 2,合约中的“状态变量”会根据其具体类型分别保存到这些插槽中。
S
即所谓的“运行栈,用来保存 EVM 指令的输入和输出数据。可以免费使用,没有 消耗,用来保存函数的局部变量,数量被限制在 16 个。 的最大深度为 1024 ,其中每个单元是 2 。
A
也叫 ,是一段只读的可寻址的保存函数调用参数的空间,与栈不同的地方的是,如果要使用 里面的数据,必须手动指定偏移量和读取的字节数。
M
M 一个简单的字节数组,主要是在运行期间存储数据,将参数传递给内部函数。基于 2 进行寻址和扩展。
EVM 数据存储概述前面已经说过 S 是每个合约持久化存储数据的地方其储存数据的方式是通过插槽来现的,现在就具体介绍它是怎么现的:
状态变量1对于大小在 2 字节以内的变量(常量),以其定义的顺序作为它的索引值来存储。即第一个变量的索引为 (0),第二个变量的索引为 (1)
2对于连续较小的值,可能被优化存储在同一个位置,比如:合约中前四个状态变量都是 64 类型的,则四个状态变量的值会被打包成一个 2 字节的值存储在 0 位置。
未优化:
^0411; C {? ?256 = 12;? ?256 = 12;? ?256 = 12;? ?256 = 12;? ? () (256,256,256,256){? ? ? ? (,,,);? ?}}
优化后:
^0411; C {? ?64 = 12;? ?64 = 12;? ?64 = 12;? ?64 = 12;? ? () (64,64,64,64){? ? ? ? (,,,);? ?}}
结构体对于大小在 2 字节以内的结构体同样也是顺序存储,例如结构体变量索引定义在位置 0,结构体内部有两个成员,则这两个成员的依序为 0 和 1。
^0411; C { I {? ?256 ;? ?256 ;}? ? () ? (256,256){? ? ? ?I ;? ? ? ? = 12 ;? ? ? ? = 24 ;? ? ? ?(,);? ?}}
映() 存储位置是通过 256 (2() + 2() ) 计算得到的, 表示 对应 类型变量存储的位置。
^0411; T {?(256 = 256) ;? () {? ? ?[060] = 040;?}}
数组定长数组同上,只要在 2 字节以内也是顺序存储,不过在编译时编译器会进行边界检查防止越界。
^0411; C {? ?256[] = [12,24,48] ; ? ? () (256,256,256){? ? ? ? ([0],[1],[2]);? ?} }
可变长度数组由于可变长度数组长度不定,一般在编译可变长度数组时会提前预留存储空间,所以就会使用状态变量的位置存储可变长度数组的长度。
而具体的数据地址会通过计算 256 (2()) 算得数组首地址,再加数组长度偏移量获得具体的元素。
^0411; C {? ?256[] = [12,24,48] ; ? ? () (256,256,256){? ? ? ? ([0],[1],[2]);? ?} }
字节数组和字符串如果长度小于等于1字节 :
1对于定长字节数组则是同定长数组一样;
2对于可变字节数组和字符串,会在存储值位置补0一直到2字节,并用补0的最后一个字节存储字符串的编码长度。
^044; A{? ? 0 = ; ? ?8 =06667767656;? ? ; ? ? () {? ? ? ?(0AA);? ? ? ?(0BB);? ? ? ?(0CC);? ?}? ? () (){? ? ? ? ;? ?}}
当节数组和字符串长度大于1字节时
1变量位置存储编码长度,并且编码长度公式更换为编码长度 = 字符数 * 2 + 1
2真存储值第一个位置通过公式 256(2()) 获取,剩余值在获取到的位置顺序存储,同样在最后存储位置补0到2字节。
= ;
安全问题前面已经讲到EVM的存储结构及存储机制,现在我们再来探讨其安全问题。
未初始化变量漏洞原理:
在官方手册中提到结构体,数组和映的局部变量默认是放在 中的,而 语言中函数中设置的局部变量的默认类型取决于它们本身的类型。
因此如果在函数内部设置以上 类型变量却没有进行初始化,他们就相当于存储指针指向合约中的其他变量,当我们对其进行改变时改变的就是其指向的变量。漏洞合约,目的修改 为自己地址:
^040; C{? ? = ;? ? = 0CA57915458EF540D6068F2F44E87; P {? ?2 ;? ? A;}? ? (2 _ , ?_A) {? ? ? ?P ;? ? ? ? = _;? ? ? ?A = _A;? ? ? ?();? }}
漏洞合约分析:
可以看到该合约在函数部分创建新的结构体时没有进行初始化,由此我们可以利用该函数进行对的修改。不过使用该函数我们还要通过验证,不过这也不难因为状态变量也同样在我们可控的范围内。
具体操作:
调用函数分别传入向_ 传入:00000000000000000000000000000000000000000000000000000000000000001(真值)
_A 传入:0B89C01888220ADD1468C165208821(个人地址)
传参前:
传参后:
可以看到已经成功更改了地址。
总结可以看到 EVM 的存储器就是一个 = 的健值数据库,存储的数据可以通过校验和来确保一致。但是其也是和智能合约语言进行交互的,当其中一些规则发生冲突很可能就被别有用心的人用来作恶,所以规范的使用智能合约语言是避开漏洞的必要条件。 |
|