剖析中国移动的流量气泡
在使用4G上网浏览网页的时候,经常会出现下面这个气泡。即使是用电脑使用了手机的个人热点也不能例外。
有的时候,这个东西可以说很贴心,它可以实时告诉你还剩多少流量,是该猛着用还是省着用。当你不小心使用电脑狂用自己个人热点的流量的时候,这个流量气泡也会提醒你“你在烧钱ing!”,能够防止更大的损失。
不过也有时候,这个东西会非常烦人。譬如,它偶尔会弹广告!建议您办理每月400元的移动豪华套餐,or 参与大富豪抽奖……老子忙着呢,抽你妹啊!
它的实现原理是什么呢?中国移动,如何能够在任意浏览的网页里,插入自己的内容?
其实很简单,中国移动只是嵌入一段很短的js标签,这个标签所指向内容的代码就会运行,接下来10086就可以在整个页面里兴风作浪了,增加一个iframe,在iframe里实现自己的气泡、各种流量查询功能、积分、广告……
下面是插入的javascript的内容:
|
|
当然我不确定那个id是不是会泄露自己的信息,我也不知道别人的id是不是也是这个,但为了友好还是写出来了。By the way, 这个script在非移动网络是失效的。当你使用的不是中国移动蜂窝网络,是无法链接221.179.140.145的。
当然了,即使在蜂窝网络下:
|
|
在蜂窝网络下也访问不了?并不是,这最多说明中国移动把ICMP关了吧。
不管它的访问地址了,先看看js代码里有什么猫腻。打开sript标签中的js代码:
|
|
这么乱,连个换行都没有?!看个鬼啊!没关系,我们有JSNICE,一个让混淆过的JS更有亲和力的工具,自带缩进,自带类型提示,甚至会有一定的反混淆的功能,把混淆后的变量名尽可能地按照意思还原回去。棒!经过它的处理之后,代码变成了下面这个样子,是不是易读多了?
|
|
这个代码加长了之后有184行,所以还是有一些复杂的。不过大概还是分析一下架构:
主题分为这么几个部分,首先是一个外围的主函数function(){}(window);,主函数内部用一个try{}catch(e){}括起来,方便进行错误处理。
然后try内部定义了一些函数,包括
- done : 用于最终的iframe构建
- loadScript : 用于判断信息是否收集完全
- extend : 用于把JS的Key-Value对转化为应对HTTP GET请求的&;串
- init : 增加流量气泡的函数入口
为了方便理解,我做了一个调用init前的逻辑序列图:
打开网页,script运行的时候,首先是不会管这些函数的,而是直接运行line120的判断语句。如果parent==self那么就运行init()
这里专门对于nba.sina.cn做了一个判断,我估计是作为一个体育网站,经常有直播的内容,而文字直播经常是用ajax实现的,或许和10086的这个添加的iframe有某些冲突,被专门投诉了吧,所以对于这个网站,加载方式不太一样,所以使用了onload函数,在加载页面的时候运行init。
sina.cn和sina.com.cn还不是完全一样,sina.cn默认是专门为手机浏览器设计的。
如果parent != self的话,就说明当前的视口不是顶级浏览器,很可能是出于某个iframe嵌套中,所以要找到顶级窗体的内容(var doc = top.window.document)然后在顶级窗体中进行处理。
第138行,找到ID为1qa2ws的项目,这个我在前面也说了,那个旧市script的ID。如果已经有这个ID了,那就说明,顶级的窗体已经家再过相应的script了,所以有一个气泡就够了,下层的就别操心了,所以139行检测到存在这个script,就直接退出了。
如果不存在,那就说明,作为下层iframe的内容,还需要再挣扎一下,142行到158行,就是在尝试构建上文中的那个script标签的内容,然后把这个script放置到顶层的浏览器视口中。
161行,cache error,如果出错了怎么办,首先找到刚才的script,把src值赋予expr,然后把错误信息综合进入data中吧,最后再使用document建立一个新的script - node,把data发送到tlbsserver/stagelog那边去,让它们在后台写入log,让开发人员慢慢分析去了。
到这里这一层逻辑就结束了。
下面,关于init、extend、done、loadScript又是什么,它们之间的关系又是什么呢?
这里给一个init后的示意图:
下面具体研究上述的四个函数。可以知道,入口函数肯定是init的,因为其他几个在主代码中都没有用到。init函数从86行开始到119行。
首先它找到1qa2ws的ID,也就是那个Script标签,然后获取一些feature,创建了一个新的script s,118行,把s放到svg中。svg就是head。
什么鬼?这个script中,最重要的init的目的,无非是由新增一个script到head中?
是的,就是这个样子,所以很多玄机应该都在这个s中罢。
具体地,这个s的内容是:
|
|
其中的expr就是原来script的src部分,截取tlbsgui之前的部分,再加上后面一堆东西。
那么新的src应该就是
%SIZE的具体内容应该是得到的时间var size = defaultCenturyStart.getTime();defaultCenturyStart其实就是new Date(),故弄玄虚。
%MARKER的具体内容就是element.attributes了。extend函数就是把这些key-value对用&=连缀起来,作为GET的参数。
我用手机访问了一下http://221.179.140.145:9090/tlbsserver/jsreq?tid=4&cid=2&time=1449641644008,没有加MARKER,还好可以访问到,获得了一个应该是json。
|
|
好了,这应该就非常清晰了,因为这个json,把name、网址端口、css、js的地址都发送回来了,还有各种辅助的参数。拿到这些内容,就可以最终访问了。
有人会问,这个js地址,都用|来分隔了,键入这个地址肯定没法使用啊!别着急,这个并不是直接用的,而是用处理函数的,而具体的处理函数就在Done里面,对于|,见11行。
这个过程是:首先使用loadScript函数,检测一下相关的load工作有没有完成,完成了之后,就去运行Done函数。
Done函数就在读取这个json中的内容,Done函数的过程,就是建立一个iframe,把json中的参数填写进去,这个iframe的效果不是别的,就是那个流量气泡!
在Done的错误处理中,有一小段
我也没仔细看,估计是跟人民网、财经网有不兼容,或者这两个网跟中国移动说了“别再我的网站上加插件!”,所以检测到网址中有这个字符的时候,就不再挣扎着放置iframe了。
我觉得,也许不想让中国移动在自己的网页上放置气泡的方法,就是在自己的网址中放入”www.people.com.cn”字样。并没有测试,也没有完整追究这两个网站,仅仅是猜想。
我还尝试下载了上述一些js,再次强调只有移动网络内部才能够访问得到,其他网络是无法访问221.179.140.145等网址的。
其中http://221.179.140.145:9090/tlbsgui/baseline/common/js/UA.js?v=20151209141500这个js的内容很简单:
随便找另一个js:http://221.179.140.145:9090/tlbsgui/customize/L_bar/bjyd/js/simplifiedCloseHandler.js?vv=104‘
它的内容是:
这个内容就多了,而且能够找到很多资源,譬如/images/content/radio_check.png等等,这些肯定就是最终的那些控件图标了。
又随便找一个css:http://221.179.140.145:9090/tlbsgui/baseline/L_bar/css/tlbs_min.css?vv=104,这个就复杂很多,有很多CSS内容。这也就最终支持了气泡的CSS样式吧。
好了,再深挖就不知道要挖到哪里去了,时间有限就先分享这么多了。