本文讲述了作者如何根据一个云储币(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_worddict_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不同,但可能也使用了某种密钥组合,所以,请保管好你的密钥!
本文作者:clouds
来源:51CTO