驚鴻一瞥:捕獵以太坊黑暗森林中的'那隻怪獸'
By ChainNode·

作為區塊鏈安全領域從業者,我對密碼學和安全一直都有著強烈的興趣,上週看了一篇非常不錯的文章: shorturl.at/cfhg9,在和作者聯絡取得同意後,翻譯成了中文,與感興趣的朋友一起分享一下。下邊就是原文:
上週,以太坊黑暗森林裡的一隻怪獸終於向我露出了他的獠牙。在與tux交流後,我得知了這隻怪獸的存在,於是投下了“我的誘餌”,16個小時之後它如期上鉤。在etherscan上看到我消失的eth,仍舊心有餘悸。
這隻怪獸一直在注視著以太坊網路,試圖尋找以太坊交易中那個不為人注意的錯誤:簽名中的k值複用。為了找到這隻怪獸,我引誘它出現。為了理解我是怎麼做到的,一些關於橢圓加密曲線簽名演算法(ecdsa)和數字簽名的知識需要先介紹一下。
ecdsa
橢圓曲線的數字簽名演算法(ecdsa)是支撐當今最大的兩個區塊鏈,bitcoin 和 ethereum的密碼學基礎。就如同在現實中,人們用自己的簽名來證明自己的權益,基於ecdsa的數字簽名可以證明這些區塊鏈上的數字資產的所有權。數字簽名可以提供下邊兩個方面的證明:
1. 對於私鑰的所有權。每把私鑰都有與之繫結的一把公鑰,這把公鑰同時也用來生成在區塊鏈上的一個“地址”。
2. 使用私鑰對於一個訊息的簽名。在區塊鏈的上下文中,這個訊息就是交易。
ecdsa之所以有效是因為,我們可以使用私鑰非常容易的得到公鑰,但是無法(或者說很難)從公鑰去推到出私鑰。但是如果得到了數字簽名,在一些條件下是可以推匯出私鑰的。下邊讓我們聊一點技術細節。
如果要完成對某個訊息的簽名,我們需要使用私鑰d,一個隨機值k,以及這個訊息的雜湊h。同時加上橢圓曲線中的g和n, 經過下邊的公式計算,我們可以得到這個訊息的數字簽名r和s。

r和s一起就是這訊息數字簽名。
nonce是什麼?
在橢圓加密演算法中,計算數字簽名的隨機k值是非常關鍵的。k值永遠不能暴露,並且一個k值永遠只能使用一次。這就是隨機k值被稱為nonce的原因。下文中也會用nonce來指代k值。如果駭客知道了某個簽名對應的k值,那麼它就可以透過以下的公式推算出這個簽名的私鑰。

同樣,如果nonce被在兩個不同的簽名中重複使用,這個nonce(k值)同樣也可以透過以下公式被推算出來。

有了nonce就可以透過上邊的公式來推算出私鑰。
那在實際中我們如何判斷有沒有nonce重複使用呢?
我們回憶一下,在ecdsa 數字簽名中r的計算過程 r = k * g mod n. 這個公式中g和n都是確定的,唯一變數就是k,所以如果用同一個私鑰籤兩個不同訊息,在得到的簽名中r是一樣的,那麼就可以判斷出有nonce重複使用的問題。
在瞭解了這些後,我們來看看以太坊上的交易,如果一個地址下的兩筆交易的簽名有相同的r值但是s值不同,那麼可以判斷這個地址下有nonce重複使用的情況。這就是“那隻怪獸”一直的關注點。也是我下邊所用的實驗方法。
note:對於普通的使用者來說,只要選擇靠譜的錢包,你不要關心nonce洩露和重複使用的問題。但是對於區塊鏈開發者來說,這些是需要了解和注意的。
下餌,引蛇出洞
為了引蛇出洞,找到這隻怪獸,我構造了兩筆交易,這兩筆交易的簽名使用了相同的nonce,所以他們簽名會有相同的r值。我在這個地址上留了點eth,所以如果有任何人盯著這個地址,那麼它就可以透過簽名推算出私鑰來轉走這個地址上的eth。
為了製作這些誘餌,我必須構造出兩筆nonce重複使用的交易,當然你沒法使用metamask來達到這個目的。於是我使用ether.js來達到這個目的。來看下邊的示例程式碼。
var drbg = new hmacdrbg({
hash: this.hash,
entropy: bkey,
nonce: 1, // changed from "nonce"
pers: options.pers,
persenc: options.persenc || 'utf8',
});
(程式碼僅供參考示例,別在自己的錢包上試)
在這裡我把nonce設為了1!然後我重新生成了一把私鑰,並在地址上充了0.04eth,然後我使用下邊的指令碼,生成了兩筆交易。
const ethers = require("ethers")
async function main(){
const privatekey = "not-leaking-it-this-way"
let wallet = new ethers.wallet(privatekey)
console.log("using wallet:", wallet.address)
const provider = new ethers.providers.jsonrpcprovider("rpc-endpoint")
let signer = wallet.connect(provider)
const tx = await signer.sendtransaction({
to: wallet.address,
value: ethers.utils.parseether("0").tostring(),
gaslimit: 45000,
})
console.log(tx)
}
main()
由於我把nonce設為了1, 所以這兩個交易的簽名是有相同的r,和不同的s值。寫了一個簡單的指令碼驗證了一下:
[email protected] nonce-reuse-bait % node get_r_s.js
transaction 1
r: 0x340709f674d030dda4aa8794bffb578030870bdfe583e7f41aea136a7ca1ed94
s: 0x3e5b92d8c5a2a033c9e5eb53ff2946681b28ab82fa5b17d0a9c48d139e78fe2c
transaction 2
r: 0x340709f674d030dda4aa8794bffb578030870bdfe583e7f41aea136a7ca1ed94
s: 0x2c17cf960d1af7602f875afc56d01eddcc67bb03e962877444ed8d4c1287ab7a
隨著這兩筆交易的上鍊,簽名私鑰其實已經暴露了。我已經把餌下好,現在就靜靜地等待那隻怪獸的上鉤。
驚鴻一瞥,怪獸出沒
當傳送完這兩筆交易後,我就不斷的重新整理區塊瀏覽器來觀察地址上的eth是否還依舊存在。但是奇怪的事,一切如常。又過了幾個小時,依舊一切如常。我都開始懷疑是不是自己搞錯了什麼,但是天色已晚,我就先去休息了。
第二天早上一早,我就檢視我的賬戶,它出現了!我地址上的eth被取的乾乾淨淨,我故意留在地址上的0.04個eth被取走了!

那隻怪獸終於露出了獠牙,他注視著以太坊上的交易, 看是不是有交易的簽名有相同的r值。當他發現以後就去推算他們的私鑰,取空他們賬戶上的所有資產。我有點疑惑的是,為什麼過了這麼久它才出動?有一種可能性是它一直在默默等待,看這個賬戶上會不會有更多的資產轉進來,它好一次性的都轉走。
讓我們近距離的看看這隻怪獸,我不僅僅是唯一的受害者,它同樣也取幹了別人的資產!

這個地址一直有從不同地址下轉入的eth,截止目前這個賬戶上有價值$3,700的資產。為了驗證這些被盜地址是否也有nonce重複使用的問題,我寫了一個指令碼來檢查他們歷史上交易檢查是否也有相同的r值。我檢查這隻怪獸(駭客)盜取的第二地址,發現它之前傳送的交易也存在r值重複使用的問題。
[email protected] nonce-reuse-bait % node get-tx-history.js
same r found!
r: 0xf0d7b10f398357f7d140ff2be1bea9165d32238360ad0f82911235868be7c6e1
hash: 0xb5d2454d7380bfa7ac75ec76f15eecb56e60941429153081fe799fb53a7ff901
r: 0xf0d7b10f398357f7d140ff2be1bea9165d32238360ad0f82911235868be7c6e1
hash: 0x9e459be7fa9950835a3c2594d3440c684fed05fa8e12e8088cc7776c4afb364c
same r found!
r: 0x41d43fd626c24e449ac54257eeff271edb438bbabbc9bee3d60a5bd78dc39d6d
hash: 0x670f66ff71882ae35436cd399adf57805745177b465fdb44a60b31b7c32e4d16
r: 0x41d43fd626c24e449ac54257eeff271edb438bbabbc9bee3d60a5bd78dc39d6d
hash: 0x374180005946ef3b1906ee1677f85fa62eb5a834aa0241b4c9c74174bca26a07
這進一步證明了我的猜測,這隻怪獸緊緊盯著以太坊上的交易,時刻關注著是否有nonce複用的情況。一旦發現了nonce複用,它會慢慢的,悄悄地把這些地址上的資產都取光。
在我分析了這隻怪獸攻擊的其他地址後,我驚奇的發現有些地址並沒有nonce重複使用的問題,事實上在它攻擊的20個地址裡,有9個存在nonce重複使用的問題,而其他並沒有。
那麼其他11個地址是什麼因為什麼遭到了攻擊呢?老實來說我並不清楚,一種可能性是這隻怪獸同時還有其他策略去嘗試推算私鑰,例如用常見的一些單詞,數字來推算私鑰。同時還有一些其他的方法來利用nonce生成中的漏洞。但是我並沒有從這隻怪獸的行為中找到證明。
以太坊黑暗森林裡的這隻怪獸終於被我找到,但是會不會有或者誰是它的下一個受害者?這依舊是個謎題。
亞倫簡評:
私鑰生成不隨機,nonce生成不隨機,nonce重複使用這些問題都會有導致私鑰有洩漏的可能性。
對於普通使用者來說,選擇靠譜的錢包是避免這個問題的最好方法,尤其推薦使用有真隨機數生成器的硬體錢包。
對於開發者來說,重視隨機數的產生,選擇靠譜的cryptographic library。同時使用確定性ecdsa簽名演算法也是避免這個問題的另一種方式。
**文章來源:亞倫碎語
譯者:陳東**#資訊
免責聲明:
- 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
- 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
- 鏈報僅提供相關項目信息,不構成任何投資建議。
推荐阅读