EVM虚拟机在解析合约的字节码时,依赖的是ABI的定义,从而去识别各个字段位于字节码的什么地方。关于ABI,能够阅读这个文档:前端
通常ERC-20 TOKEN标准的代币都会实现transfer方法,这个方法在ERC-20标签中的定义为:function transfer(address to, uint tokens) public returns (bool success);github
第一参数是发送代币的目的地址,第二个参数是发送token的数量。函数
当咱们调用transfer函数向某个地址发送N个ERC-20代币的时候,交易的input数据分为3个部分:工具
4 字节,是方法名的哈希:a9059cbbui
32字节,放以太坊地址,目前以太坊地址是20个字节,高危补0 000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca.net
32字节,是须要传输的代币数量,这里是1*10^18 GNT 0000000000000000000000000000000000000000000000000de0b6b3a76400003d
全部这些加在一块儿就是交易数据:cdn
a9059cbb000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca0000000000000000000000000000000000000000000000000de0b6b3a7640000blog
当调用transfer方法提币时,若是容许用户输入了一个短地址,这里一般是交易所这里没有作处理,好比没有校验用户输入的地址长度是否合法。
若是一个以太坊地址以下,注意到结尾为0:
0x1234567890123456789012345678901234567800
当咱们将后面的00省略时,EVM会从下一个参数的高位拿到00来补充,这就会致使一些问题了。
这时,token数量参数其实就会少了1个字节,即token数量左移了一个字节,使得合约多发送不少代币出来。咱们看个例子:
这里调用sendCoin方法时,传入的参数以下:
0x90b98a11 00000000000000000000000062bec9abe373123b9b635b75608f94eb8644163e 0000000000000000000000000000000000000000000000000000000000000002
这里的0x90b98a11是method的hash值,第二个是地址,第三个是amount参数。
若是咱们调用sendCoin方法的时候,传入地址0x62bec9abe373123b9b635b75608f94eb8644163e,把这个地址的“3e”丢掉,即扔掉末尾的一个字节,参数就变成了:
0x90b98a11 00000000000000000000000062bec9abe373123b9b635b75608f94eb86441600 00000000000000000000000000000000000000000000000000000000000002 ^^ 缺失1个字节
这里EVM把amount的高位的一个字节的0填充到了address部分,这样使得amount向左移位了1个字节,即向左移位8。
这样,amount就成了2 << 8 = 512。
(1)首先生成一个ETH的靓号,这个帐号末尾为2个0
使用一些跑号工具就能够作到,好比MyLinkToken工具,能够很轻易跑出末尾两个0的。
(2)找一个交易所钱包,该钱包里token数量为256000
(3)往这个钱包发送1000个币
(4)而后再从这个钱包中提出1000个币,固然这时候写地址的时候把最后两个0去掉
若是交易所并无校验用户填入的以太坊地址,则EVM会把全部函数的参数一块儿打包,会把amount参数的高位1个字节吃掉。
(5)这三个参数会被传入到msg.data中,而后调用合约的transfer方法,此时,amount因为高位的1个字节被吃掉了,所以amount = amount << 8,即扩大了256倍,这样就把25600个币所有提出来了。
针对这个漏洞,说实话以太坊有不可推卸的责任,由于EVM并无严格校验地址的位数,而且还擅自自动补充消失的位数。此外,交易所在提币的时候,须要严格校验用户输入的地址,这样能够尽早在前端就禁止掉恶意的短地址。
Reference
内容来源:知道创宇
做者:隐形人真忙