前言

前段时间,Poly Network 被盗事件的一个小插曲,一地址向黑客地址转账在 input data 中告知其 USDT 已被冻结,不要使用 USDT,黑客知晓后向该地址转账 13.37 ETH。

事后很多人便通过 input Data 在区块链上“聊天”向黑客“索要”虚拟货币,那么我们经常在区块链浏览器中看到的 input Data 到底是什么?知道创宇区块链安全实验室为您解答。
知道创宇区块链安全实验室 | 以太坊 Input Data 解析

Input data

在以太坊协议中,当交易(transaction)为合约创建时,input data 是账户初始化程序的 EVM 代码;

而当交易(transaction)为消息调用时,input data 是合约函数调用数据。

正常情况下简单的消息调用如调用转账函数时需要填写你要转账的地址_to 和你要转账的数量_amount,这些基本信息都包含在 input data 里面。

我们通过一个调用合约的转账交易具体分析,来理解消息调用时 input data 的结构。
知道创宇区块链安全实验室 | 以太坊 Input Data 解析
解析形式:
知道创宇区块链安全实验室 | 以太坊 Input Data 解析
原始形式: 
知道创宇区块链安全实验室 | 以太坊 Input Data 解析
我们将原始的 input data 分为三个部分进行分析: 

  • 0xa9059cbb: 函数标识符

  • 000000000000000000000000345d8e3a1f62ee6b1d483890976fd66168e390f2: 第一个参数为 address 即你要转账的地址 , 并补位到 32 字节即 64 个 16 进制字符

  • 0000000000000000000000000000000000000000000054b7d8ed70650b290000: 第二个参数为 value 即你要转账的数量,并补位到 32 字节即 64 个 16 进制字符

通过对比分析我们可以发现 input data 的基本结构为函数标识符+参数

函数标识符

这里的函数标识符即为函数选择器,根据官方文档可知函数选择器是某个函数签名的 Keccak (SHA-3)哈希的前 4 字节(高位在左的大端序)。

我们可以通过代码 bytess4(keccake256("transfer(adddress,uint256)")) 或者在线工具获取这种函数签名。
下图可以看出加密结果的前四个字节 (a9059cbb) 跟 input data 中函数标识符一致。
知道创宇区块链安全实验室 | 以太坊 Input Data 解析
这里之所以要将函数签名截断到四个字节是考虑到 Gas 成本问题。

在一笔交易中 0 字节需要支付 4 gas, 而非 0 字节需要 68 gas 也就是 0 字节的 17 倍。

在 SHA-3 加密中生成的 32 字节随机字符串更倾向于多的非 0 字节,所以大概成本是 32x68=2176 gas,而截断成本大概为 4x68=272 gas,可见截断到四个字节能够节省约 8 倍的 gas 费。

而函数标识符的作用是指定调用哪一个函数,在同一个合约中两个不同函数的 SHA-3 签名的前 4 字节相同的概率是十分小的,所以截断到四个字节实际不会影响函数调用。

参数

在 evm 执行字节码的约定中,静态类型左补齐零至 64 长度,而动态类型则是右补齐零至 64 长度。

归纳下常见的静态类型:uint,bool,Address,bytes[0-32], 动态数组类型:bytes,string,address[],bytes32[]…..

我们通过 pyethereum 的 ABI 编码函数来研究不同数据类型的编码方式。

静态类型

先导入 encode_abi 函数

import rlp
from ethereum.abi import encode_abi

我们以函数 transfer(address,uint 256) 为例

> encode_abi(["address", "uint256"],
[345d8e3a1f62ee6b1d483890976fd66168e390f2,1]).hex()
000000000000000000000000345d8e3a1f62ee6b1d483890976fd66168e390f2
0000000000000000000000000000000000000000000000000000000000000001

对于小于 32 字节的定长数组会被自动填充到 32 字节:

> encode_abi(["int8[3]"],[[1, 2, 3]).hex()
// 自动填充 0
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003

动态类型

动态类型编码要稍微复杂一些,需要先计算偏移量进行占位处理,我们通过一个简单的例子来具体说明。

> encode_abi(
 ["uint256[]", "uint256[]", "uint256[]"],
 [[0xa1, 0xa2, 0xa3], [0xb1, 0xb2, 0xb3], [0xc1, 0xc2, 0xc3]]
).hex()
// 参数 1 的偏移量 :32*3=96 十六进制 0x60
0000000000000000000000000000000000000000000000000000000000000060
// 参数 2 的偏移量 = 参数 1 偏移量+参数 1 数据部分长度 =96+32*4=224 十六进制 0xE0
00000000000000000000000000000000000000000000000000000000000000e0
// 参数 3 的偏移量 = 参数 2 偏移量+参数 2 数据部分长度 =224+32*4=352 十六进制 0x160
0000000000000000000000000000000000000000000000000000000000000160
// 偏移量 0x60 位置开始传入参数 1 的数据
0000000000000000000000000000000000000000000000000000000000000003// 元素个数
00000000000000000000000000000000000000000000000000000000000000a1// 第一个数组元素
00000000000000000000000000000000000000000000000000000000000000a2// 第二个数组元素
00000000000000000000000000000000000000000000000000000000000000a3// 第三个数组元素
// 0xe0 位置。参数 2 的数据
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000b1
00000000000000000000000000000000000000000000000000000000000000b2
00000000000000000000000000000000000000000000000000000000000000b3
//0x160 位置。参数 3 的数据
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000c1
00000000000000000000000000000000000000000000000000000000000000c2
00000000000000000000000000000000000000000000000000000000000000c3

短地址攻击

经过前面的分析当静态类型如 address 长度不足 32 字节时 EVM 会根据规则将长度补齐到 32 字节。

如果当转账的地址以 00 结尾,如 0x641988625108585185752230bde001b3ebd0fc00,转账时将地址后面的两个零去掉,EVM 依然会认为 address_to 是 32 位的,所以它会从 _value 的高位取 0 来补充,amount 的位数会多两位也就是会乘以 256。

攻击过程如下:

将恶意转账地址最后一个字节的 0 去掉
函数标识符:a9059cbb
转账地址:
000000000000000000000000641988625108585185752230bde001b3ebd0fc

转账金额:
00000000000000000000000000000000000000000000000000000000000000001

由于 EVM 的补位规则,解析结果为:
0xa9059cbb000000000000000000000000641988625108585185752230bde001b3ebd0fc0000000000000000000000000000000000000000000000000000000000000000100

我们分解后发现,转账金额已经多了两位也就是多了一个字节,即为原来转账的 256 倍
函数标识符:a9059cbb

转账地址:
000000000000000000000000641988625108585185752230bde001b3ebd0fc00

转账金额:
00000000000000000000000000000000000000000000000000000000000000100

如何在 input data 附着信息

在以太坊中直接进行转账交易的 input data 字段默认是没有内容的,但是我们可以通过设置钱包实现文章开头的“聊天功能”。我们以 MetaMask 钱包为例展示如何通过转账在 input data 字段附着一些额外的信息。

1、首先我们需要打开钱包高级选项的显示十六进制数据开关
知道创宇区块链安全实验室 | 以太坊 Input Data 解析
2、在转账时将你要附着的信息通过十六进制编码后填入下方十六进制数据中,记得在开头加上 0x 然后进行转账
知道创宇区块链安全实验室 | 以太坊 Input Data 解析
3、转账成功后在 etherscan 中就能够看到附着信息
知道创宇区块链安全实验室 | 以太坊 Input Data 解析

总结

我们能够通过交易中的 input data 将一些信息永久存储在区块链中,可以通过此项技术在食品药品监管部门的产品防伪溯源、财税部门的电子票据打假验真、学术成果存证等方面实现应用落地。







实验室官网:www.knownseclab.com
知道创宇唯一指定存证平台www.attest.im
联系我们:blockchain@knownsec.com

知道创宇区块链安全实验室导航微信公众号
@ 创宇区块链安全实验室

知道创宇区块链安全实验室 | 区块链安全解决方案:让人类进入安全的区块链世界

官方网站
@ 知道创宇区块链安全实验室
知道创宇区块链安全实验室 | 区块链安全解决方案:让人类进入安全的区块链世界

微博
@ 知道创宇区块链实验室
https://weibo.com/BlockchainLab
知乎
@ 知道创宇区块链安全实验室
https://www.zhihu.com/org/zhi-dao-chuang-yu-qu-kuai-lian-an-quan-shi-yan-shi
Twitter
@KS_Blockchain
https://twitter.com/KSBlockchain
知道创宇区块链安全实验室|重要提醒:没有审计过 Chias(XCS) 项目