
本文講述了作者如何根據一個云儲幣(Siacoin)用戶發貼,從一個錯誤密鑰中恢復出正確密鑰,最終偷取了發貼用戶交易賬戶中的所有云儲幣。
起因
事情要從6月9號晚上說起。那是個普通的周五晚上,我一邊看著Netflix美劇一邊刷著reddit論壇。突然,云儲幣(Siacoin)論壇內( /r/siacoin)跳出了一條如下發貼:

大意是這樣的:
前些天,我購買了價值2000歐的云儲幣,當時我記得是用txt文件保存了錢包密鑰的,但不知怎地,后來竟然沒存上。不過還好,當時我手抄了一份密鑰。但問題是,交易平臺一直提示我的輸入密鑰有錯!我已經試了不下500遍了,天哪,誰能幫幫我!如果誰能幫我,我可以送他一些云儲幣作為補償……。
發貼者隨后貼出了自己手抄備份的錢包密鑰(紅框文字)。
云儲幣介紹
SIA是一個運用區塊鏈技術的去中心化的云儲存平臺。相比較傳統的云儲存方式,去中心化的Sia系統能夠使云儲存更安全、更快捷、更低成本。通過編碼技術(erasure coding),加密技術(encryption),和區塊鏈(blockchian),Sia既具備傳統的云儲存功能,同時又解決了傳統的云儲存存在的安全隱私問題。
Siacoin(云儲幣/云幣):Sia的設計使提供儲存空間的服務器能夠收到Siacoin(云儲幣)–Sia系統內置代幣,以此激勵更多閑散空間成為儲存空間提供商。用戶可以用Siacoin來出租或賣買存儲空間。(點此查看Siacoin挖礦教程和Siacoin交易平臺)
勾起興趣
該發貼用戶正在犯錯,他竟然把Sia錢包私鑰種子公布在網上!該密鑰種子就像用來開啟存儲用戶加密貨幣錢包的一把鑰匙,是用戶賬戶的重要信息。從發貼內容可以看出,該用戶聲稱其錢包內包含價值€2,000的云儲幣,并大方地把密鑰種子貼出來。讓我覺得感興趣的是該用戶竟然手抄了種子密鑰,還反復強調:
我非常肯定我抄的正確無誤,我還檢查了兩遍。
但我覺得,他肯定抄錯了。我希望他只犯了一個小錯而已,如果只是漏掉一個字母或把兩個字母順序抄反了,我認為有可能推斷出正確的密鑰而恢復云儲幣。那可是2000歐吶,想想都氧氧!不排除其他高人能破解出該密鑰而把錢攬入囊中,我要盡快行動。
手工破解
先來看看該用戶發布的這段錯誤的密鑰:
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
雖然我不熟悉Sia的密鑰種子生成機制,但Sia是完全開源的項目,所以我認為這應該不難了解。
事實果然如此。在Sia項目的wallet.go文件中,我發現了一個SeedToString函數,其中提及了名為entropy-mnemonics(熵助記符)的文件目錄,該目錄文件中竟然包含了種子密鑰字典,GOD,如下:
englishDictionary = Dictionary{
"abbey",
"abducts",
"ability",
"ablaze",
"abnormal",
"abort",
"abrasive",
"absorb",
"abyss",
"academy",
"aces",
"aching",
"acidic",
"acoustic",
"acquire",
"across",
"actress",
...
你可以發現該字典共1600個詞。我是這樣假設的:該用戶的29個詞段密鑰應該大多都是對的,有可能他在抄寫種子密鑰時,不小心寫錯了一個詞,當然該錯詞肯定不包含在這個密鑰字典中,如果我能發現這個錯詞,那么,正確密鑰自然就真相大白可以告破了!
但是,要在1600多個詞段中去對29個詞篩選一遍,確實有些眼花繚亂,SO,暴力枚舉法可以派上用場。
暴力枚舉
我們用代碼來實現自動檢測吧。現在,我需要查找密鑰字典中所有詞語的方法,這些所有詞語都可能是那段29種子密鑰中的被寫錯的那一個。
我覺得編輯距離(Levenshtein distance)可能有用,它能衡量兩個詞語之間的相似度。比如,”cat”和”car”之間因為有1個字母差異而存在1個編輯距離, “cat” 和 “scar”間有2個字母差異而存在2個編輯距離。為了在密鑰字典和用戶公布的私鑰之間發現可能的種子密鑰,我需要編寫腳本程序來找出那些相互之間包含1個編輯距離的詞語。
Levenshtein distance:又叫最小編輯距離,是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。許可的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符。一般來說,編輯距離越小,兩個串的相似度越大。
首先,把字典文件下載下來,提取出所有a到z的有效密鑰字段,保存為dictionary.txt文件:
$ wget -qO- https://raw.githubusercontent.com/NebulousLabs/entropy-mnemonics/master/english.go \
| egrep "^\s+\"(.+)\"," \
| egrep -o [a-z]+ \
> dictionary.txt
之后,安裝python-Levenshtein庫,當然,也得把Sia項目克隆安裝到系統中。接著,編寫一個能自動導出可能性種子密鑰的小腳本:
import Levenshtein
seed = raw_input('enter your wallet seed: ')for seed_word in seed.split():
for dict_word in open('dictionary.txt'):
dict_word = dict_word.strip()
distance = Levenshtein.distance(seed_word, dict_word)
if distance != 1:
continue
print '"%s" -> "%s"\n%s\n' % (seed_word, dict_word,
seed.replace(seed_word, dict_word))
該腳本從1600行字典文件中復制粘貼詞語到我的Python目標文件中,以下代碼操作可以很好地展示其功能。
開啟錢包
我擔心會有成百上千種可能性,所以我必須認真記錄每個種子的碰撞運行過程。幸運的是,最終腳本成功運行后顯示,與論壇用戶公布種子有1個編輯距離的密鑰只有12個!
$ python recover.py
enter your wallet seed: eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"wise" -> "wife"
eluded logic wife ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"tagged" -> "jagged"
eluded logic wise ascend jagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"tagged" -> "nagged"
eluded logic wise ascend nagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"aptitude" -> "altitude"
eluded logic wise ascend tagged acoustic situated stylishly younger altitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"push" -> "lush"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar lush because brunt viking gone august public tonic vulture shrugged otter adapt
"brunt" -> "grunt"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because grunt viking gone august public tonic vulture shrugged otter adapt
"tonic" -> "ionic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public ionic vulture shrugged otter adapt
"tonic" -> "sonic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public sonic vulture shrugged otter adapt
"tonic" -> "topic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public topic vulture shrugged otter adapt
"tonic" -> "toxic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public toxic vulture shrugged otter adapt
"adapt" -> "adept"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adept
"adapt" -> "adopt"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adopt
這么幾種可能性,手工輸入驗證都可以。我先來試試第一個可能種子,在論壇用戶公布的錯誤種子中,把wise用wife替代,如下:
> siac wallet init-seed
Seed:
Could not initialize wallet from seed: error when calling /wallet/init/seed: seed failed checksum verification
可沒那么好運,我們一個個都試試吧。最終,發現了用ionic替換掉tonic的正確密鑰種子:
> siac wallet init-seed
Seed:
Wallet initialized and encrypted with seed.
大功告成!
現在,該用戶錢包已經被我控制了,讓我們來開啟錢包吧:
> siac wallet unlock
Wallet password:
Wallet unlocked
> siac wallet
Wallet status:
Encrypted, Unlocked
Confirmed Balance: 594.8 SC
594.8 SC (Siacoin) !不會吧,這僅值市價10歐元左右,這與論壇用戶聲稱的€2000相距甚遠!WTF!我不是被騙了吧?還是該用戶故意夸大錢包數額以吸引人去幫助他?或是有其他牛人比我早先一步得手,故意留下10歐的賬戶嘲笑奚落我?
保護戰利品
仔細思索一番,我覺得我的速度已經夠快的了,怎么到頭來還只有這么點余額呢?我也不確定是否其他看到該帖子的人也和我用同樣的方法進行了錢包解鎖。哎,不管那么多了,先把這些云儲幣偷走再說!
于是,我把這些云儲幣全部轉移到了我的Sia錢包中去,即使其他人破解了種子密鑰,也不能得到云儲幣了。
一探究竟
現在云儲幣算是安全了,我們來看看這到底是怎么一回事。檢查一下該發貼用戶的錢包交易歷史:
> siac wallet transactions
[height] [transaction id] [net siacoins] [net siafunds] 108589 427b72c98e8ea64fba234ca2a00288f7a750003a243e6b3e967f5c6d426c2f9f 594.83 SC 0 SF
109002 32ad2729fe6b487aedc1b70d0dff0843404ff1cef69223d5f03699dcd1dbe568 0.00 SC 0 SF
109002 2304da26d61bd2cb7fcac5c7b38a553d788d8dfc386ae4eb47772e36e4a9269d -594.55 SC 0 SF
最后一條交易記錄是我上述的轉移偷幣操作,而0.00SC的記錄是Sia錢包的兩個地址進行交易后產生的噪音記錄,可以不用管。我感興趣的是第一條記錄:它顯示在108589區塊高度處轉入了594.83 SC的云儲幣。由于區塊高度本質上對應著云儲幣的時間單位,所以我在Sia block explorer中查看了該條交易記錄,發現該筆轉入操作在2017年6月7號產生,也就是該用戶在論壇發貼的前兩天。

他只有價值10歐的錢包,為什么還要聲稱是2000歐呢?……
陷入卡頓的交易記錄
其實,就在那段時期,當時最大的云儲幣交換平臺Poloniex,也出現了誤把云儲幣轉移到用戶錢包中的問題。他們并沒有弄丟用戶資金,只是在用戶把錢從交易賬戶發送到個人錢包時,交易機制發生了混亂卡頓,不過還好,Poloniex保存了數周時間的交易備份。
有可能該發貼用戶把2000歐轉移到他的個人錢包時,也發生了類似Poloniex的交易混亂,而這2000歐或許是陷入轉移滯后狀態,最終仍然會到達用戶錢包。
這是個有意思的問題。那我怎么把那些未到賬的錢實時偷取截留到我的錢包中呢?我決定寫一個腳本來實現。最終腳本功能如下:
for /l %%x in (1, 0, 100) do (
siac wallet send siacoins 2000SC fff0228f02a01cf8e037047a5ea0db5a88d614913af5f21de209ebf2e58431c68cfc9c27d0e4
)
該腳本執行一個增量為0,從1到100的for無限循環,不停地嘗試把2000SC從該用戶錢包發送到我自己的錢包中來。如果該用戶錢包中收到2000歐的入賬,那么它會每次向我錢包轉移2000SC,分次向我發送所有2000歐價值的云儲幣。因為2000歐元有125000SC,為了不發生其它節外生枝,每次轉移2000SC是因為這是一個額度相對較低且較安全的轉移量。
告知受害者
10歐元對我來說做不了什么,私人飛機、勞力士、帶泳池的豪宅….?最終,我還是決定把這幾個價值10歐的云儲幣退還給受害者。“嘿,哥們,是你發貼聲稱幫忙找回2000歐的錢包嗎?我已經解鎖了,但里面其實只有10歐元!”想想都覺得有點尷尬。

于是乎,在該用戶發貼后的兩小時,我發了一條私信給他,我解釋了如何恢復密鑰、如何把那些云儲幣轉移到自己賬戶中的原因,并希望他告訴我一個新的地址,我重新給他轉回去。但幾小時過去了,沒有任何回應。而且他還把發貼內容和張貼出來的種子密鑰給刪除了。好像就當沒這回事一樣。
最終,在一個周一早上,受害者聯系到了我。他說在發貼后他意識到用來買云儲幣的那2000歐可能正處于交易狀態(被我猜中了。,而所買的云儲幣可能就根本就沒有到達他的Sia錢包中。因為此刻他還能繼續在賬戶中操作控制這2000歐,所以,他立即把這些錢轉移到了另一個安全賬戶。而最終,這些錢又失而復得時,他就刪除了那個帖子,也沒有關注到我發給他的私信消息。
他很高興我能恢復密鑰并找到他的Sia錢包,他承認自己抄錯了密鑰,而且堅持給我那10個SC,我到覺得應該物歸原主,這10個幣也不能讓我發大財。在我的一再要求下,他最終給了我地址,我就這樣把這10個云儲幣發給他了。
后記
千萬不要把你的Sia錢包密鑰種子公布在網上,從以上事例可看出,即使是部分密鑰或錯誤密鑰也可能被人利用,破解出正確密鑰,開啟你的錢包。
這種可能不僅針對云儲幣(Siacoin)而是所有加密貨幣。雖然其它幣種與Siacoin不同,但可能也使用了某種密鑰組合,所以,請保管好你的密鑰!
|