前言
前段時間,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 字節,所以大概成本是 32×68=2176 gas,而截斷成本大概爲 4×68=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 將一些信息永久存儲在區塊鏈中,可以通過此項技術在食品藥品監管部門的產品防僞溯源、財稅部門的電子票據打假驗真、學術成果存證等方面實現應用落地。
實驗室官網:www.knownseclab.com
知道創宇唯一指定存證平臺:www.attest.im
聯繫我們:[email protected]
知道創宇區塊鏈安全實驗室導航微信公衆號
@ 創宇區塊鏈安全實驗室
官方網站
@ 知道創宇區塊鏈安全實驗室
微博
@ 知道創宇區塊鏈實驗室
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