许 蕾, 刘蕊成, 陈贵美, 赵 晨, 张卫丰
1(南京大学 计算机科学与技术系,江苏 南京 210023)
2(南京邮电大学 计算机学院,江苏 南京 210003)
目前,互联网中最成熟的商业模式之一是以广告为基础的商业模式.据悉,谷歌公司 96%的收入都来自于网络广告.当用户使用搜索引擎时,除了返回与查询词相关的信息外,还会出现广告,一旦用户点击了这些广告,广告的发布者需要付费给搜索引擎,与此同时,用户有可能成为广告商品的买家.这样,在线广告把商家、用户、应用开发者有机地联系在一起,共同构成了互联网应用的生态系统.
为了便于发布、传播广告并提高用户点击率,广告提供商通常使用JavaScript代码动态生成广告页面,并收集用户交互、浏览器环境等信息.作为第三方代码,这些广告代码可能给网站以及用户带来极大的安全隐患.例如,这类广告代码可以获得用户在浏览器上的行为,从而获得用户的访问信息,增加了用户隐私信息泄露的可能性,同时也打破了网站本身的完整性.更可怕的是,一些恶意广告可能利用网页中的漏洞,偷偷将恶意软件安装到用户的电脑中.研究显示,网络上每天出现约 130万个恶意广告,它们中的大部分都会下载恶意软件并且进行一定的伪装,以绕过安全软件的检测[1-4].
现今大部分网站都使用JavaScript代码动态显示信息.事实上,几乎所有的恶意广告都是通过JavaScript代码加载的.因此,为了提高网页的安全性,可以直接禁止所有JavaScript代码的加载.但是由于包含JavaScript代码的应用十分普遍,网页中几乎所有的动态效果和交互性强的显示效果都需要使用JavaScript代码,网站应用越来越趋向于使用JavaScript代码,以吸引用户和增强表现力.因此,直接屏蔽JavaScript代码会导致网页的交互性降低,不能因噎废食.
为了识别恶意广告,需要追踪广告代码调用路径.但追踪广告代码路径具有很大的挑战,这是由于:(1) 网页中混杂了大量HTML文件、JavaScript脚本文件、CSS文件,又由于有的脚本文件根本不执行,很难识别出广告相关的JavaScript文件;(2) JavaScript脚本文件中包含大量函数,每个函数之间的调用关系错综复杂,形成了很长的函数调用链路,这些函数大都分布在不同的JavaScript文件中,也不容易识别函数的调用关系;(3) 为了保证用户的浏览体验以及代码本身的隐私和安全性,开发者往往对JavaScript代码进行了压缩,使得整体的代码可读性变差,导致JavaScript代码中函数调用链的获取工作更加困难.
为此,我们计划使用动态分析方法获取广告的调用路径.由于网页广告的调用过程实质上是网站主通过广告联盟获取广告相关的 JavaScript代码,因此我们的工作可以认为是从广告联盟的代码中获取广告调用路径.本文的主要工作如下:(1) 对广告相关的JavaScript脚本进行分析,发现动态广告的生成特点;(2) 根据生成特点,使用动态插桩工具对网页进行插桩,以获得广告的调用路径;(3) 对iframe内部调用路径和跨iframe调用路径进行不同处理,获得完整的调用路径;(4) 通过实验分析以及与静态分析方法的对比,说明本文方法的有效性:能够在有限增加访问时间(2~10X)的前提下,获取到静态方法无法获取到的广告动态加载过程信息,并生成广告代码调用路径.
本文第2节通过实例说明广告调用路径获取的困难性.第3节设定规则使用动态插桩工具对广告相关的调用路径进行追踪.第4节分别介绍对调用路径中iframe内部和跨iframe调用路径的获取.第5节是实验部分,针对 21个真实网站进行广告代码调用路径的追踪并给出具体的统计结果.另外,还与静态分析方法进行了对比,说明本文方法的有效性.第6节为相关工作,包括互联网广告研究和JavaScript程序分析.第7节总结全文并指出后续可开展的工作.
网页中动态广告的加载和生成是一个复杂的过程,其传播路径可能涉及多个JavaScript脚本文件.本节我们用一个示例描述网页中动态广告的加载过程,并说明广告调用路径获取的困难所在.
以www.gamefaqs.com为例,图1虚线以上区域展示了该网站主页加载完毕后的代码及截图,红色椭圆区域是由Google提供的动态广告;虚线以下区域显示广告加载的动态过程,包括多个JS文件的调用.在该应用中,为了个性化推送广告和躲避检测,源代码经过了一些混淆处理.为了方便阅读,图中所示代码已经进行了一些简化处理.其广告加载的详细步骤描述如下.
(1) 浏览器加载主页,加载后由服务器返回的代码如图1中虚线以下区域的左半部分第1行~第20行所示.当第3行~第14行中的脚本语言执行后,一个新script标签及其包含的JS文件会加载到页面中(ads.js).
(2) 浏览器解析新增加的script标签并执行来自pubads.g.doubleclick.net中的ads.js文件.该文件第4行又向页面中插入新的script标签并执行JS文件akyi97Q8.js.
(3) 浏览器加载来自tpc.googlesyndication.com中的akyi97Q8.js文件,该文件创建了一个iframe标签(文件中第 1行),并在第 3行中引用 tpc.googlesyndication.com的 adXpYxnS.html文件,并把该 iframe嵌套到 id为google_ads_iframe的div下(第6行、第7行).
(4) 浏览器从tpc.googlesyndication.com加载adXpYxnS.html文件.该HTML文件触发了广告内容相关函数的执行,该函数返回广告相关的内容(pagead2.googlesyndication.com).
(5) 最终,浏览器加载并显示实际的广告.页面截图和加载完毕后的源代码如图1中虚线上部所示.
从图1所示例子可以看出,网页中广告相关的JavaScript调用路径难以获得有以下几个原因.
(1) 由于网页中混杂着大量的HTML代码、JavaScript脚本文件、CSS文件,又由于有的脚本文件根本不执行,这给广告相关JavaScript文件的识别工作带来了相当大的困难.
例如,网站www.gamefaqs.com中的measure.js存在于网页中,却没有被执行,也难以判断该JS文件是否是广告相关代码.
(2) JavaScript脚本文件中包含大量函数,包括匿名函数,各函数之间的调用关系错综复杂,形成了很长的函数调用路径.这些函数大都分布在不同的JavaScript文件中,这对观察和识别函数的调用关系造成了不便.
例如,图1中存在的文件调用路径为ads.js➔akyi97Q8.js➔adXpYxnS.html,同时还有众多其他文件没有显示出来,要从中找出这些调用路径难度很大.
(3) 另外,为了保证用户的浏览体验以及代码本身的隐私和安全性,HTML页面中的JavaScript代码通常会被压缩或混淆,使得整体的代码可读性变差,导致JavaScript文件中函数调用链的获取工作更加困难重重.
例如,图 1所示 www.gamefaqs.com网站的 ads.js文件包含丰富的信息,实际找到的文件却只有1行代码,但却有上千列.这些代码压缩或混淆在一起,可读性极差,也进一步增加了调用路径获取的难度.
本节我们通过使用动态插桩工具Jalangi对包含JavaScript代码的网页进行插桩,并针对JavaScript函数进行若干特殊处理,以获取广告的调用路径.
Jalangi[5]是美国加州大学伯克利分校在 2013年开发实现的 JavaScript动态分析框架,能够对前端和后端JavaScript进行动态分析,允许监控每个JavaScript程序的操作以及编写自己的程序分析代码;通过记录-回放的机制来实现JavaScript代码的插桩,并通过影子执行的方法运行自定义的动态分析脚本.Jalangi提供了丰富的分析API,以实现各种动态程序分析.当使用Jalangi对网页进行分析时,浏览器通过使用一个代理服务器向网页所在服务器发送请求,并获得整个原始页面的JavaScript脚本文件;接着,Jalangi对获得的脚本文件进行插桩.
Jalangi在网页加载前对原始代码进行插桩,并生成插桩后代码.
插桩后代码调用了两个 Jalangi回调函数 J$.W和 J$.R,分别是变量赋值和变量读取的分析回调函数,变量名和变量值等参数会被传递给这两个函数.J$.R(5,‘b’,b)表示读取名称为‘b’的变量,其中数字 5表示回调函数的标识符;J$.W(9,‘a’,J$.R(5,‘b’,b),a)表示对变量名为‘a’的变量赋值为 J$.R(5,‘b’,b)的返回值,其中数字 9 也表示回调函数的标识符.此时在浏览器中加载网页,会执行插桩后的JavaScript代码,且不会破坏页面的原有设置.
另外,还可以通过在工具Jalangi源程序中设定一系列规则,对JavaScript代码进行动态插桩,进而得到执行轨迹、变量取值等定制化信息.因此,我们可以设定规则对网页进行插桩,以获取广告相关的JavaScript脚本.
Jalangi提供了回调函数invokeFunPre和putFieldPre,它们分别在函数执行前和在对象属性赋值前进行分析操作.其中,invokeFunPre函数包含几个重要的参数:(1) iid,当前回调函数执行时的唯一标识符,以数字类型表示;(2) f,指向当前函数对象;(3) base,如果函数f是某个对象的方法,则base是该对象的引用,否则为null;(4) args,函数 f的参数列表,以数组形式表示;(5) isConstructor,判断函数 f是否为构造函数,以布尔形式类型表示.putFieldPre函数包含几个重要的参数:(1) iid,当前回调函数执行时的唯一标识符,以数字类型表示;(2) base,赋值的属性隶属的对象;(3) offset,属性名;(4) val,在base[offset]中存储的值,即该对象属性存储的原始值.
由于时间、精力有限,我们只追踪来自百度和谷歌的广告调用路径,且对于域名与当前网页所在服务器的二级域名不相同时才进行追踪,以下定义均建立在此前提之上.另外,根据同源策略,当 iframe引用的URL与当前网页的域名不同时,DOM元素是不可以被iframe所在页面内的JavaScript代码操作的,所以document内部的函数调用是指在一个iframe内的函数调用.
定义1.document内广告路径的开始节点S若函数调用时没有调用者或者调用者是DOM事件处理函数,则认为是广告路径的开始节点.另外,我们使用unname作为匿名函数调用的标识.
例如:图1中左侧代码片段的开始节点为第3行~第15行的匿名函数.
定义2.document内广告路径的结束节点E若使用函数appendChild、insertBefore以及document.write插入〈img〉、〈script〉、〈a〉、〈iframe〉标签,或使用 innerHTML 方式插入〈iframe〉标签,则将其作为广告路径的结束节点.
例如,图 1右侧底部 ads.js文件中的结束节点为第 6行用 insertBefore方式插入代码片段,图 1右侧中部akyi97Q8.js文件中的结束节点为第7行用appendChild方式插入的〈iframe〉标签.
定义3.document内广告路径的中间节点M开始节点和结束节点之间的一系列函数调用均作为中间节点.
例如,图1虚线下方左侧广告调用路径为%source%unnamed→insertBeforescriptads.js,虚线下部右侧底部广告调用路径为%source%unnamed→insertBeforescriptakyi97Q8.js,虚线下部右侧上部广告调用路径为%source%unnamed→appendChildiframe.
虚线下部代码片段组成一条 document内部的函数调用路径,即%source%unnamed→insertBeforescriptads.js→%source%unnamed→insertBeforescriptakyi97Q8.js→%source%unnamed→appendChildiframe.
另外,我们注意到,JavaScript函数中使用setTimeout和setInterval设置了定时执行函数,函数执行时调用者会变为浏览器.因此,为了广告链路的完整性,我们需要将调用 setTimeout和 setInterval的函数作为定时执行函数的父节点.
定义5.跨document的函数调用路径AP记为P1…Pm,其中,Pi表示第i个document内部的广告路径,相邻节点Pi,Pi+1表示Pi结束节点产生了第i+1个document.
例如:在图1虚线下右侧中部中使用appendchild插入一个iframe,iframe的 src为 adXpYxnS.html,即这个document产生了另一个内容为adXpYxnS.html的document.
我们考虑使用Jalangi对网页进行动态插桩,以获得广告的调用路径,具体方案如图2所示,其主要的工作流程是:在浏览器中设置代理,当我们访问一个网页时,代理服务器会获取从Web服务器返回的HTML网页文件及其引用的JS脚本文件,并调用Jalangi对HTML和JS文件中的JavaScript脚本代码进行插桩,然后记录执行轨迹并在浏览器上显示页面内容.
每执行一行 JavaScript脚本代码,其执行轨迹均会被记录下来,通过分析将其关联起来,就可以追踪特定代码的调用路径.一个函数调用路径是一个函数调用的相关信息项的序列,每一项都包含:执行的函数;函数调用在代码中的位置,以行列数表示;函数调用所在的js文件的url.函数调用路径中相邻的两项表示前一项中的函数调用了后一项中的函数.本文获取函数调用路径的方法是基于函数的参数对象arguments实现.一个函数的参数对象可以在该函数的作用域内被访问,也可以在这个函数直接调用的函数的作用域内被访问.参数对象是一个类数组对象,存放着包括本次函数调用传入的实参arguments[0]~arguments[arguments.length-1]和对该函数的引用arguments.callee.
我们通过改写Jalangi的invokeFunPre分析回调函数,在被分析代码中的函数调用之前,将函数调用路径作为一个额外的实参传递给它,则函数执行后该函数调用路径会出现在它的参数对象里;在其调用的任何函数作用域内,都可以通过arguments.callee.caller.arguments访问到前者的参数对象存放的函数调用路径.
一个函数对应的函数调用路径即以该次函数调用为终点的一系列函数调用.Jalangi将原代码中的函数调用替换成一个包装函数的调用,该包装函数先调用 invokeFunPre进行调用前分析,再调用原代码中的函数的插桩后版本,最后调用 invokeFun进行调用后分析.原本传递给原函数的参数现在传递给包装函数,且该包装函数的调用者与原函数在插桩前原本的调用者是一致的;因为Jalangi将包装函数的参数对象传递给invokeFunPre,所以我们可以获取到包装函数的调用函数的参数对象,提取后者对应的函数调用路径.与此同时,我们也可以通过invokeFunPre在一个函数的参数对象里初始化一个函数调用路径.
为了捕捉广告调用路径的开始、过程和结束,我们设定一条广告路径由0或一串函数调用路径和一次插入某些特定 DOM 元素的操作组成.一个完整的网页广告生成流程包含不少于一个的广告路径.经过调研我们发现,广告调用路径涉及JavaScript中3类原生函数,对应于定义2,即:使用insertBefore或appendChild插入script、a、img、iframe标签;使用document.write插入script、a、img、iframe标签;使用innerHTML插入iframe标签.另外,对于setTimeout和setInterval的处理,也需要特殊对待.
综合大量文献发现,溶血现象影响生化检测项目主要表现在以下几点:①对肝功能指标影响:研究报道,溶血现象对谷草转氨酶、谷丙转氨酶、总蛋白和白蛋白等检测项目带来正干扰,而对直接胆红素和总胆红素带来负干扰[4];②对肾功能指标影响:文献报道,溶血现象对肾功能检测项目带来的影响比对肝功能带来的影响要小,因为溶血后的谷胱甘肽等红细胞物质可吸收尿酸中的H2 O2;③对血脂血糖指标的影响:目前临床上检验血糖的方法主要为葡萄糖氧化酶联合H2 O2酶法,但血液样本溶血后产生的血红蛋白会不同程度的影响血糖检测过程中的反应物,从而使得检测结果出现误差。
为了在函数调用过程中记录并传递执行轨迹(trace)信息,我们需要通过invokeFunPre中特定的函数参数记录并追踪trace.为此,我们设置了特定的trace信息结构.
如图3所示,trace中记录的document内广告调用路径P的信息结构为
%source%函数名称,位置,文件名,函数调用的位置||(函数名称,位置,文件名,函数调用的位置||)*插入元素的方式,插入的内容,插入内容的src,位置,文件名,函数调用的位置
其中,“%source%”表示广告路径P的开始节点S;“||”作为每次函数调用的分隔标识;“(函数名称,位置,文件名||)*”表示0个或多个中间节点M;“插入元素的方式”表示结束节点的开始,包括了insertBefore、appendChild、document.write这3种方式.整个广告调用路径包括1个开始节点、0个或多个中间节点、1个结束节点.
由此构成document内的广告调用路径集合CS和所有document的广告调用路径集合CSS:CS的信息结构为keyvalue键值对,key为文件名,每个value为一个二维数组,第1维表示trace,第2维表示trace的每一项的具体信息;CSS的信息结构为(url,P)*,其中,url表示当前document所在的url,P表示当前document广告调用路径.
为了得到以上信息,我们需要在冗长、复杂、可能包含混淆代码的JavaScript文件中识别出广告相关的函数,并确定调用路径的起始点、更新以及终止点,具体处理过程如下所示.
3.4.1 广告相关函数调用路径初始化
需要说明的是,在引入的第三方脚本中,如果一个函数被网站服务商提供的脚本代码调用,则这次函数调用不被认为有关广告生成,而是第三方函数库的调用行为,因为现今主流的广告联盟不要求网站开发者调用它们的接口以生成广告.
图 4是一个简单的广告调用路径代码示例:当用户访问网站www.A.com的主页时,页面主动加载了C.js文件,这段代码来自于ads.B.com,即对于主页面来说,这段代码来自于第三方JavaScript库.因此,符合我们设定的广告调用路径起始点(满足条件2).经过Jalangi插桩,可以记录下trace:onload→A.
3.4.2 广告相关的函数调用路径更新
如果一个函数有调用者且调用者的参数对象中存在函数调用路径,则说明它已被标记为潜在广告路径的一部分,所以我们可以将它的函数调用路径和当前的函数调用相关信息拼接,形成更新后的函数调用路径.
对于函数调用路径更新,有一个特殊的情况是:当一个函数调用setTimeout延时执行或调用setInterval定时延时另一个函数时,从后者的参数对象中获取的调用者是 null,即从调用栈的角度来看,两者没有直接调用的关系.但是在调研中,我们发现:广告脚本中有很多地方使用了这种定时或延时执行函数.因此,需要捕获这样的调用关系.
如图5所示,在加载执行A函数时,使用setInterval每隔5s调用B函数,在B函数中调用了C函数,C函数又做了一些操作.因此,函数的调用路径是A→setInterval→B→C.但是由于setInterval设置的延时,使得调用路径在B之后发生中断,导致无法获取到C函数的调用信息.setTimeout的情况与此类似.
因此,使用 setTimeout或 setInterval函数阻断了广告调用路径的传播,需要对这两个函数进行特殊处理.处理方法是:创建一个闭包,该闭包内存放着setTimeout函数或setInterval函数调用者参数对象的引用、要延时或定时执行的目标函数,其中,延时或定时执行的对象不再是原目标函数,而是这个闭包,这个闭包在运行时会先将setTimeout函数或setInterval函数调用者的参数对象传递给分析函数invokeFunPre,然后再执行目标函数.图6展示了创建闭包的函数关键代码,其中,J$.invokeFun函数会调用分析函数 invokeFunPre,并调用目标函数fun;args里只有setTimeout函数或setInterval函数调用者的参数对象,但它不作为目标函数真正的参数对象.
经过上述处理,在分析延时和定时的函数调用时,就可以获取到存放在设置延时和定时执行的函数的参数对象中的函数调用路径,所以能够捕获非直接的调用关系.仍然使用图4所示的例子,可以看到,A函数在调用B函数时使用setTimeout等待了5 000ms,这个操作会影响广告路径的延续,因此Jalangi在这里对setTimeout进行特殊处理,即调用invokeFun函数直接执行B,以此对路径进行延续,这样获得的trace路径为onload→A→B,没有丢失对B函数的调用.
3.4.3 广告相关的函数调用路径完成
广告调用路径可能会包含多次页面跳转(从一个JS文件链接到另一个JS文件),但最终会需要将包含广告内容的页面通过DOM操作插入到已有的HTML页面中,插入元素的类型可能是脚本元素〈script〉、内联框架元素〈iframe〉、超链接元素〈a〉以及图片元素〈img〉,这是因为网页广告的生成包括以下几种情况.
1) 广告有可能通过插入〈script〉脚本来加载外部源的JS脚本文件;
2) 插入〈a〉标签或〈img〉标签,由于很多网络广告通过插入一段链接指向某个真正广告内容的 url地址,所以需要进行标记;插入〈img〉标签则为插入图片,这时往往也是广告生成的最后一步,即用图片展示广告内容,且可以起到与〈a〉标签一样的作用,因此也需要进行标记;
3) 插入〈iframe〉标签,由于网页上的广告位通常为一个既定的区域,且保证不受页面上其他脚本的DOM操作带来的影响,常用一个iframe引入一个广告页面.
另外,一个广告脚本可能会插入其他广告脚本,也可能会插入iframe显示广告页面,而且〈a〉和〈img〉元素都可以通过设置src属性实现广告链接.
使用 JavaScript语言在页面 DOM 结构中插入 HTML元素的方法有以下几种:调用 DOM 元素对象的insertBefore和appendChild方法、调用document.write函数和赋值DOM元素对象的innerHTML属性.
1) 对insertBefore和appendChild函数的分析
对于使用insertBefore和appendChild函数插入iframe、script、a、img标签的情况,我们认为该条路径已经到达了终止点,此时需要判断其 caller的情况,如果有 caller属性,即有调用者,而且其调用者不是事件处理函数,则对该条路径进行输出;如果iframe、script、a标签没有调用者,或者调用者就是事件处理函数,则对整条广告传播路径进行输出.
在图4中,B函数创建了一个script,然后用appendChild方法添加到网页中.由于B函数的调用者A函数的参数中已经添加过trace属性,即A和B都在广告调用路径上,因此为B函数也添加调用路径的属性:onload→A→B→insertscript.appendChild.这就表示了该文件内广告调用的结束,因此在控制台对B函数所绑定的参数进行打印输出.
2) 对document.write函数的分析
考虑到document.write函数接收的参数是字符串而非DOM元素对象,满足HTML表达式规范的字符串会被解析为DOM对象,所以我们对document.write的参数进行字符串匹配,确定它是否插入了之前提到的4类元素.同样,我们也要判断写入的script标签有没有src属性.图7所示例子说明了通过document.write插入元素的过程:第7行在A.js中使用document.write嵌入一个script标签,script标签执行B.js.
3) 对innerHTML属性的分析
向一个DOM元素的innerHTML属性赋值一个合法的HTML表达式字符串,可以在其下面插入元素.使用Jalangi的putFieldPre分析回调函数,可以分析任意对象属性的写入行为.当写入一个对象的属性时,判断对象是否为DOM元素对象以及属性名是否为innerHTML,且写入了与广告相关的标签.
最后需要将函数调用路径和DOM操作关联起来,组成单条广告路径.在使用appendChild、insertBefore和document.write函数来插入广告相关元素时,如果它们有调用者,则可以获取它们的调用者参数对象里的函数调用路径,并附上当前DOM操作的相关信息作为最后一项;而对于DOM元素对象的innerHTML属性的写入,我们扩展了Jalangi的代码,使putFieldPre函数接收一个额外的参数,即该条属性赋值语句所在的函数,从这个函数的参数对象里寻找函数调用路径并拼接上该DOM操作的相关信息,作为最后一项.
动态生成的广告能够在网页广告位中显示来自于第三方广告联盟的广告资源,这些广告资源可能是图片、视频,甚至是一个URL链接.而根据JavaScript的同源策略,iframe引用的URL属于不同域名网页内的DOM是不可以被 iframe所在页面内的 JavaScript代码操作的,所以,为了不影响正常显示,这些来自于第三方的图片、视频资源等,一般都嵌套在 iframe里,再放入网页中.一个页面和它的 iframe引用的页面各自具有以 document节点为根节点的DOM树.
在一个 document内,我们规定在两条广告路径中,如果其中一条插入了一个〈script〉标签,引用了某个 JS文件,而另一条的第1项对应的语句在该JS文件中,则两者是关联的,前者是后者的前继.我们将获取到的所有路径都放在一个路径集合中,方便之后获取到新的广告路径时通过算法回溯它的所有前继.
document内的广告路径回溯算法如算法1所示.该算法接受document内所有广告路径集合CS和需要回溯的路径P,遍历CS中的路径,若找到一条插入script脚本且P对应的语句处于该脚本中,则递归回溯该条路径的前继,直到回溯完毕.
算法1.document内广告路径回溯算法.
在某些情况下,一个页面插入的 iframe引用的页面里又会出现其他广告相关元素的动态插入,我们将这些行为关联在一起.在两条广告路径中,如果其中一条路径插入了一个iframe,另一条广告路径起源于该iframe引用的网页中,则前者是后者的前继.
每条广告路径都对应一个 document,即它的每一项对应的文件都是被该 document引用的.如算法 2所示,为了关联跨documents的广告路径,遍历其他document的广告路径集合里的路径,并查看这些路径的尾部是否插入了一个iframe且src属性是否是被回溯路径对应的document的url.如果存在这样的路径,则该路径是这个待回溯路径的前继.与此同时,一个document对应的所有广告路径都与插入该document的路径相关联.
算法2.跨documents广告路径回溯算法.
在图8的示例中,A函数创建了一个iframe,用appendChild插入一个iframe标签,作为广告调用路径的终止点.此时得到的广告调用路径为A→appendChild(ifr).在iframe内部又调用了B函数,B函数又调用了C函数,C函数插入一个图片,使用appendChild插入img标签,此时,这部分的广告调用路径结束,这部分的广告调用路径为appendChild(ifr)→B→C→appendChild(img).
通过使用算法3,可以将图8所示的跨iframe的两条广告调用路径进行拼接,从而获得该广告的完整调用路径: A→appendChild(ifr)→B→C→appendChild(img).
算法3.获取广告调用路径中涉及的JS文件url.
本节我们通过实验来展示使用动态插桩方法获得广告代码调用路径,通过对比实验来验证本文方法的优越性.
为了说明本文方法的有效性,我们对动态插桩工具Jalangi进行了扩展并开展了以下实验,以期动态追踪广告代码调用路径并获取到调用路径长度、广告插入方式等特征信息,然后具体分析广告相关的JavaScript脚本文件中原生函数insertBefore、appendChild、document.wirte、innerHTML所占的比重,从而更加明确广告代码的加载、传播方式.另外,还与静态分析方式进行了对比实验,以说明我们的方法在恶意广告的检测精度上更具优势.
JavaScript的动态插桩工具Jalangi运行在Linux系统中,我们在代理服务器端使用OSX系统对网页进行插桩,在Windows 7系统上用Firefox上对网页进行浏览,并在控制台获取广告相关的调用路径,然后通过调用路径分析广告相关的JavaScript脚本文件.
我们随机选取Alexa排名网站中的21个网站(包括13个国外网站和8个国内网站),作为实验对象,以追踪网站中广告相关的JavaScript文件调用、传递的详细过程.
本文实验获取的广告相关 JavaScript函数调用路径中插入标签的方式包括:通过 JS函数 insertBefore和appendChild分别插入script、img、a、iframe标签;或通过document.write写入一些标签;或通过innerHTML插入iframe标签.之后,统计各种插入方式的操作次数.另外,我们还取到了广告路径中相关的JavaScript脚本文件,对每个文件解析其抽象语法树,然后统计文件中insertBefore、appendChild、document.wirte、innerHTML出现的次数.这种静态方法只能获取到各种操作的次数,不能获取到插入标签的种类.实验过程中,比较这两种方式所获取信息的差异,并给出具体的结论.
5.3.1 实验数据及其分析
通过对网页动态执行过程的记录和分析,我们不仅可以获取广告相关的函数调用路径,还可以获取网站 JS文件数量、广告的插入方式.对于静态分析方法,可以通过遍历广告相关的JS文件的抽象语法树获取广告插入的操作方式,但是无法获取操作标签的种类.
表1中给出了网页动态执行中所涉及的JS文件、广告相关JS文件和广告相关JS片段数量,另外,还列出了插桩前后访问时间的对比情况以及实际记录的Trace长度情况.从平均数来看,这些网站平均要载入72个JS文件,其中广告相关的JS文件有7个,这说明,在运行过程中会动态载入或生成相当多数量的JS文件或片段.如果只通过静态方法分析网页中的JS文件,则不能获得动态执行过程中载入的JS文件或片段,因而也就无法精确识别网页中的广告.另外,通过访问时间的对比,我们发现本文方法的性能开销通常是2~10X,处于用户可以接受的范围内.关于实际记录的Trace长度情况,我们发现一个页面上会包含多个广告(通常有2~10个),且广告代码调用路径的长度在 1~34之间,平均数在3~9之间,这表明,广告的调用、传播路径还是比较长的,中间经过了多次跳转,这很大程度上增加了安全风险.
Table 1 Numbers of JS files, ads-related JS files and ads-related JS snippets表1 网页动态执行中所涉及的JS文件、广告相关JS文件和广告相关JS片段数量
图9中显示了网站JS脚本动态运行过程中appendChild、insertBefore、document.write和innerHTML的比例关系,可以看出,appendChild使用得最多(超过 40%),其次为 insertBefore(37%),而 document.write和innerHTML所占比例较少(分别为14%和6%).
我们同样使用静态分析方法统计了网站中 JS文件中 appendChild、insertBefore、document.write和innerHTML的比例关系(如图10所示),可以看出,appendChild方法占比也是超过了40%,而insertBefore的比例有所减少(25%),innerHtml的数量大幅增加(25%).
通过比较图9和图10,我们发现:通过innerHtml属性来动态加载HTML网页中广告的方式比较少见(6%),而在网页源代码中经常能看到 innerHtml属性(25%),这表明,该属性虽然常见,但大部分是与广告加载无关的;与此相反,document.write在网页源代码中不算常见(2%),但还较多地用来动态加载HTML网页中的广告(14%),这表明,该属性虽然不大常见,但大部分是与广告加载相关的.
图 11中给出了网页JS脚本运行过程中动态生成 iframe、script、image和 a标签所占的比例,可以看出,在这些含广告的网站动态生成的标签中 script(52%)和 iframe(28%)标签占了大部分(80%),这说明,广告脚本在运行过程中会动态生成很多脚本和网页,这正是广告分析的难点所在.作为广告展示的image标签占17%,这说明,广告主要以图片的形式进行展示,而从广告网页的加载开始到最终显示出广告,会经历多次的动态生成脚本和网页的过程,有非常复杂的调用路径,这使得恶意广告的入侵成为可能.
我们的动态分析可以监控到广告加载的全过程,从而进行及时、有效的干预.而只进行静态分析,是无法检测到这些动态生成的恶意广告代码的.
5.3.2 实例分析
如图12所示,匿名函数1中有一个三目运算,当条件(0===TRC.trkRequestStatus)不满足时,调用函数_(函数2);函数_执行了一系列操作之后,调用函数T(函数3);函数T调用了函数C(函数4);函数C中(https:"==ea?"https://sb":"http://b")+".scorecardresearch.com/beacon.js)为一个三目运算操作和一个字符串拼接操作,并把运算后的字符串作为参数传递给a,{async:!0}传递给参数c,在函数C中,创建script标签e,然后设置e的src为a,即https://sb.scorecardresearch.com/beacon.js或者http://b.scorecardresearch.com/beacon.js,并且判断c.async的值作为e的属性设置的依据,最后用insertBefore操作将script标签写入网页中.
由此,我们使用扩展后的Jalangi进行插桩并记录执行轨迹,形成的函数调用路径如下所示:
其中,“_”“T”“C”分别表示函数名,unnamed 是我们为匿名函数取的一个标识,cache/cdn.taboola.com/937d7f3e84aa424b9efca0a72b0ec608/loader.js表示loader.js在jalangi中存放的文件路径,114:162298:114:162301表示当前函数在loader.js脚本中的位置为从第114行的162 298列~第114行的162 301列,因为脚本文件的内容是经过压缩的,所以列数多,代码难理解.上述调用路径为“unnamed→_→T→C→insertBeforescriptbeacon.js”.在beacon.js中还有一系列的函数调用.仔细观察函数_,还调用了A函数,此处又有另一条函数调用路径.这表明,实际网页中存在很多代码压缩的情况,并且函数之间的调用关系非常复杂,如果采用人工审查代码的方式,是很难识别这些广告文件中的函数调用关系的.
JavaScript在 Web应用中发挥着重要作用,具有语法灵活性和高度动态性,易于使用,但代码的可维护性不够好.比如,JavaScript在运行时被广泛用来和 DocumentObjectModel(DOM[6])元素进行异步交互[7],其动态性和松散性使JavaScript代码易错,且定位困难[8-11].
基于Jalangi动态分析框架[5],可以检查JavaScript类型的一致性[12]或提高just-in-time(JIT)性能[13].另外,工具DLint[14]是在Jalangi的基础上实现的,使用动态分析方法检查JavaScript代码质量,由一个通用框架和一组可扩展的地址和特定规则的检查器组成,能够解决被静态方法遗漏的缺陷.
文献[15]在文献[16]的基础上提出了一个自动定位技术,通过追踪和后向切片,实现对 JavaScript的动态分析,解决了包括eval、匿名函数处理的困难.此外,HTML元素和JavaScript代码之间通过浏览器相互作用,加剧了客户端JavaScript代码的维护问题.文献[17]提出了一种JavaScript动态切片技术JS-Slicer,使得理解和调试客户端JavaScript代码变得容易.JS-Slicer在动态分析框架Jalangi的基础上,结合动态和静态分析所得结果,精确地捕获运行时的依赖信息.
随着互联网的发展,在线广告越来越多地被用于非法途径,如传播恶意软件、诈骗、点击诈骗行为等.为了理解这些恶意广告活动的严重性,文献[1-3]中研究了通过广告联盟所传播广告节点的拓扑结构,以此来获得恶意广告的传播行为和特点,文献[4]分析了3个月内爬取的广告相关的网页痕迹,揭示了恶意广告的猖獗,进而从恶意广告节点及其相关的内容传递路径来识别出恶意广告的特征,并构建了一个检测系统.
出于隐私、介入性和安全性等方面的考虑,现有一些技术和工具来拦截、屏蔽互联网广告,如:AdBlocke[18]、AdblockPlus[19]、Ghostery[20].这些工具通过维护一系列基于URL的正则表达式(EasyList[21]),将其与网页上获取的URL进行匹配而过滤广告.文献[22]使用针对JavaScript源代码的静态程序分析,识别用于加载并显示广告的JavaScript代码,通过特征训练,得到广告相关脚本的分类器,从而实现广告的拦截.与其相反,为了保障广告商的合法权益,WebRanz[23]利用随机化机制来使得广告拦截器失效,通过使用 WebRanz,内容发布者可以不断改变内部HTML元素ID以及元素属性,而不会影响它们的视觉效果和功能.
现有互联网广告的检测方法主要集中在静态模式匹配、静态特征匹配等,无法对混淆过后的域名和选择器进行有效检测,并且主要通过主观判定来识别、确定广告的特征,检测精度低.相应地,本文工作的主要目的是获取广告代码运行时的调用路径,以得到广告相关的JS文件并确定广告插入的操作方式等信息,可以应用到广告代码(包括混淆代码甚至恶意广告代码等)的识别中,并有效提高检测精度,部分工作细节参加文献[24].
作为互联网最成熟的商业模式之一,在线广告一方面有助于促进互联网生态系统的健康发展,但也可能影响用户的网页浏览体验以及带来潜在的安全风险.现有的在线广告屏蔽插件通过设置黑名单的方式实现对网络广告的检测和屏蔽,但无法识别不在名单中的广告,也无法获取广告代码的调用路径.
本文的研究对象是来自于广告联盟的动态广告,这些广告是目前在线广告的主要存在形式.本文的工作是通过使用 JavaScript的动态插桩工具 Jalangi获取自动执行的广告代码第三方 JavaScript函数调用路径.为此,我们为JavaScript函数动态绑定了一个存储调用路径信息的属性,分别识别广告代码调用路径的起始、中间以及终止状态,并对于 setTimeout、setInterval等函数进行特殊处理以保证函数调用链的完整传递.另外,还对跨iframe的调用路径进行拼接处理.此外,我们统计了广告插入操作方式的类型(insertBefore、appendChild、document.wirte、innerHTML)和比例,并与遍历抽象语法树的静态方法操作数量进行了对比,以此说明本文方法的有效性.
在实验过程中,我们发现了一些用于分析用户行为和记录用户 cookie的脚本文件,如 analytics.js,bkcoretag.js等.这些文件的特征和广告代码文件的特征比较相似,尤其体现在动态生成、传播上.分析用户行为和记录用户 cookie也会在一定程度上降低用户的浏览体验感受,实际上也是一种对用户隐私的侵害,可以考虑将其一并作为广告相关 JavaScript文件进行屏蔽.因此,在后续研究中,我们会通过追踪代码调用路径的方式,进一步区分该类分析文件与广告文件的特征,并应用于恶意广告的检测和屏蔽方面.