以太坊 Input Data 解析

创宇区块链安全实验室
创宇区块链安全实验室 机构得得号

Oct 14, 2021 专注构建区块链安全生态,致力于让人类进入安全的区块链世界。

摘要: 我们经常在区块链浏览器中看到的 input Data 到底是什么?

前段时间,Poly Network 被盗事件的一个小插曲,一地址向黑客地址转账在 input data 中告知其 USDT 已被冻结,不要使用 USDT,黑客知晓后向该地址转账 13.37 ETH。 事后很多人便通过 input Data 在区块链上“聊天”向黑客“索要”虚拟货币,那么我们经常在区块链浏 览器中看到的 input Data 到底是什么?知道创宇区块链安全实验室 为您解答。

以太坊

input data
在以太坊协议中,当交易(transaction)为合约创建时,input data 是账户初始化程序的 EVM 代码;
而当交易(transaction)为消息调用时,input data 是合约函数调用数据。

正常情况下简单的消息调用如调用转账函数时需要填写你要转账的地址 _to 和你要转账的数量 _amount,这些基本信息都包含在 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 中函数标识符一致。

这里之所以要将函数签名截断到四个字节是考虑到 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、首先我们需要打开钱包高级选项的显示十六进制数据开关

2、在转账时将你要附着的信息通过十六进制编码后填入下方十六进制数据中,记得在开头加上 0x 然后进行转账

3、转账成功后在 etherscan 中就能够看到附着信息

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

作者:创宇区块链安全实验室;来自链得得内容开放平台“得得号”,本文仅代表作者观点,不代表链得得官方立场凡“得得号”文章,原创性和内容的真实性由投稿人保证,如果稿件因抄袭、作假等行为导致的法律后果,由投稿人本人负责得得号平台发布文章,如有侵权、违规及其他不当言论内容,请广大读者监督,一经证实,平台会立即下线。如遇文章内容问题,请联系微信:chaindd123

链得得仅提供相关信息展示,不构成任何投资建议
本文系作者 创宇区块链安全实验室 授权链得得发表,并经链得得编辑,转载请注明出处、作者和本文链接

更多精彩内容,关注链得得微信号(ID:ChainDD),或者下载链得得App

分享到:

相关推荐

    评论(0

    Oh! no

    您是否确认要删除该条评论吗?

    分享到微信