社区应用 最新帖子 精华区 社区服务 会员列表 统计排行
  • 3093阅读
  • 4回复

[分享]从吾爱破解弹广告到浏览器插件网页挟持行为分析

楼层直达
z3960 
级别: 茶馆馆主
发帖
770867
飞翔币
207694
威望
215657
飞扬币
2511641
信誉值
8



一、分析背景与危害


起因为站务区出现的几篇帖子
1.咱这论坛(木马插件劫持)2.为啥每次登录都有广告弹出来(木马插件劫持)3.进入吾爱好多广告,怎么设置进去吾爱没有广告。4.pc端上52论坛子论坛跳广告,是中毒了吗
这几篇文章中均为插件劫持类型,并且分析代码发现其中结构高度相似。起被挟持后访问部分网站会出现奇奇怪怪的广告
会出现但不限于左上角和右上角的长方形广告,以及左下和右下随机出现的正方形广告
同时使用百度搜索的话,链接会被添加返利参数如【tn=xxxxxxxxx】,虽然这个明面上没有上面广告这么恶心,但是也被强制添加了推广返利参数,那也是相当的流氓。
综上所述,这类插件主要的危害有两个:
1.会导致部分网页出现诈骗、赌博等广告,即广告弹窗劫持。又因为是部分网页才会显示广告,导致用户误以为是网页的广告,使得插件隐蔽性更高。2.在搜索百度搜索等搜索时,会被添加额外的返利参数。即使是正常的搜索行为,也会被不断给开发者进行返利推广。同时也是特定的搜索引擎才会出现,也导致大部分网友认为是官方添加上去的,也具有极高隐蔽性。
备注:本样本是被人恶意篡改,并不是原本插件就是这样本样本是被人恶意篡改,并不是原本插件就是这样本样本是被人恶意篡改,并不是原本插件就是这样

二、行为分析


首先在未安装插件前,访问52破解官方首页。这时是没有出现上述广告的,然后打开Fiddler,安装插件,安装完成后就发现会发出大量的请求
此时尝试访问52破解官方首页

很明显是插件的挟持行为,导致了页面出现预料之外的广告。从上往下开始看看
第一个是一个【channeldelaytime】的api,里面请求参数有一个加密的字符串,但是响应里面并没有什么有用的东西,所以先抛弃不看,接着下一个。
接着是一个【getonlinecode】的api,从名称上可以猜测,很有可能是加载在线的恶意js,并且这是一个302的响应码,跳转到路径【/old/0.1.1.9/code.json】的一个文件,里面是一个加密了的文件,那么只能动态调试分析了。
打开插件文件目录,浏览器的核心文件是【manifest.json】
发现前面加了一个一些奇怪的js,全部打开这些js,然后在搜索文件中搜索【getonlinecode】
只匹配到一次,出现在【backg.img】,说明这个是一个比较关键的js,然后在单个文件中继续搜索
可以看到请求结果经过【openDoor】方法得到真实的js文件,然后直接eval运行。接着打开插件的背景页,删除搜索缓存,设置一个xhr断点后刷新
成功断下,然后在异步回调的地方下一个断点
data.data就是响应的加密内容,this.key就是【softwarecenter】,继续跟进【openDoor】方法
发现是一个aes加密,但是奇怪的是,密钥才14位,并不是16位,拿解密个锤子?
不着急,继续往里面跟进
里面调用了【i.kdf.execute】方法,返回了n对象,这个n对象的key和iv才是真实aes使用的,这里其实是一个加盐的aes。而却从加密文本的开头【U2FsdGVkX】也可以发现是加盐的aes。
根据逍遥一仙的文章【易语言】带盐AES的加解密(非调用JS),这里有易语言模块
下面给出python版本 复制代码 隐藏代码def Salted_Aes_Decrypt(data: bytes, key: bytes) -> bytes:    if data[:8] == b'Salted__':        salt, data = data[8:16], data[16:]        md5_1 = MD5.new(key + salt).digest()        md5_2 = MD5.new(md5_1 + key + salt).digest()        aes_key = md5_1 + md5_2        aes_iv = MD5.new(md5_2 + key + salt).digest()        crypto = AES.new(key=aes_key, mode=AES.MODE_CBC, iv=aes_iv)        try:            return unpad(crypto.decrypt(data), AES.block_size)        except:            raise Exception('解密失败,可能是key不正确')    else:        raise Exception('这不是一个Salted_Aes加密的结果')
解密的结果就是一段js,然后eval执行。这就达到了在线注入js了,然后注入广告的代码,很有可能也是在这段js里面。
接下来这三个请求比较可以,都是返回了aes加密后的结果,用python请求并解密一下看看
这里可以看到了一些类似策略文件之类的一些敏感内容,看起来不是一个标准的序列化方法,那么就在js里面分析反序列化方法,也是下载xhr断点来查看回调函数
这里可以看到调用堆栈都是vm,也和前面获取在线js然后eval运行对上了,这里可以看到是调用了createTxtJson方法来解析这些敏感信息
使用python稍微复现一下 复制代码 隐藏代码def createTxtJson(txt: str) -> list:    t = list()    for each in txt.split('n'):        if each:            e = each.split('||')            if e[0] == 'list':                t.append({                    'type': e[0],                    'id': int(e[1]),                    'targetid': int(e[2]),                    'shieldCity': e[3],                    'shieldChannel': [] if "null" == e[4] else e[4].split("|"),                    'targeturl': e[5],                    'sourcesproportion': int(e[6]),                    'targetproportion': int(e[7]),                    'filter': e[8],                    'refferfilter': e[9],                    'clearcookie': int(e[10]),                    'clearreffer': int(e[11]),                    'domain': [] if "" == e[12] else e[12].split("|"),                    'interval': e[13],                    'appointchannel': [] if "null" == e[14] else e[14].split("|"),                    'writeSourceLog': "true" == e[15],                    'sampling': int(e[16]),                    'intervalscope': e[17] if e[17] else "all"                })            elif e[0] == 'rule':                t.append({                    'type': e[0],                    'id': int(e[1]),                    'targetid': int(e[2]),                    'shieldCity': e[3],                    'shieldChannel': [] if "null" == e[4] else e[4].split("|"),                    'targeturl': e[5],                    'sourcesproportion': int(e[6]),                    'targetproportion': int(e[7]),                    'filter': e[8],                    'refferfilter': e[9],                    'clearcookie': int(e[10]),                    'clearreffer': int(e[11]),                    'reg': e[12],                    'interval': e[13],                    'appointchannel': [] if "null" == e[14] else e[14].split("|"),                    'writeSourceLog': "true" == e[15],                    'sampling': int(e[16]),                })            elif e[0] == 'insertjs':                t.append({                    'type': e[0],                    'id': int(e[1]),                    'targetid': int(e[2]),                    'shieldCity': e[3],                    'shieldChannel': [] if "null" == e[4] else e[4].split("|"),                    'targeturl': e[5],                    'sourcesproportion': int(e[6]),                    'targetproportion': int(e[7]),                    'filter': e[8],                    'refferfilter': e[9],                    'clearcookie': int(e[10]),                    'clearreffer': int(e[11]),                    'reg': e[12],                    'interval': e[13],                    'appointchannel': [] if "null" == e[14] else e[14].split("|"),                    'js_statistics': e[15] if e[15] else None,                    'js_id': e[16] if e[16] else None,                    'js_whitelist': [] if "null" == e[17] else e[17].split("|"),                    'js_blacklist': [] if "null" == e[18] else e[18].split("|"),                    'writeSourceLog': "true" == e[19],                    'sampling': e[20] if e[20] else "all",                    'intervalscope': e[21] if e[21] else "all",                    'when': e[22] if e[22] else "complete"                })            elif e[0] == 'special':                t.append({                    'type': e[0],                    'id': int(e[1]),                    'targetid': int(e[2]),                    'shieldCity': e[3],                    'shieldChannel': [] if "null" == e[4] else e[4].split("|"),                    'targeturl': e[5],                    'specialproportion': int(e[6]),                    'targetproportion': int(e[7]),                    'codename': e[8],                    'interval': e[9],                    'appointchannel': [] if "null" == e[10] else e[10].split("|"),                    'writeSourceLog': "true" == e[11],                    'sampling': int(e[12]),                    'intervalscope': e[13] if e[13] else "all",                    'bangdingclient': int(e[14]) if e[14] else 0                })            elif e[0] == 'biglist':                t.append({                    'type': e[0],                    'id': int(e[1]),                    'targetid': int(e[2]),                    'shieldCity': e[3],                    'shieldChannel': [] if "null" == e[4] else e[4].split("|"),                    'targeturl': e[5],                    'sourcesproportion': int(e[6]),                    'targetproportion': int(e[7]),                    'filter': e[8],                    'refferfilter': e[9],                    'clearcookie': int(e[10]),                    'clearreffer': int(e[11]),                    'domain': [] if "" == e[12] else e[12].split("|"),                    'interval': e[13],                    'appointchannel': [] if "null" == e[14] else e[14].split("|"),                    'writeSourceLog': "true" == e[15],                    'sampling': int(e[16]),                    'intervalscope': e[17] if e[17] else "all",                    'listtype': e[18] if e[18] else None,                    'version': int(e[19]) if e[19] else 0                })    return t
运行后可以得到解析好的数据

统计一下每个类型有多少数据 复制代码 隐藏代码"rule": 44,"special": 12,"insertjs": 2,"biglist": 4
首先就从【insertjs】开始,这个实际也是广告出现的地方,搜索【"insertjs"】
发现是在getStrategyInstalljsCode函数里面,然后调用了getInsertJs来加载js代码。为了方便调试,使用mitmproxy来注入js,在两个js的开头处加入debugger,相关内容可以查看帖子某数和某5秒-反混淆动态注入调试的一种方案
然后访问【https://www.52pojie.cn/】,发现主要在【abtdnjxxa.js】中断下,【fsa1.min.js】经过浏览发现其实就是一个计算浏览器指纹的标准代码,所以出现广告主要是【abtdnjxxa.js】影响,继续分析这个js
主要逻辑就是在浏览器加载完成后,执行一次start函数,然后还有一个定时器,一直在执行start函数
首先是判断了isIp函数,跳过了纯ip的地址
然后是判断了passHei函数,跳过了一些固定不显示广告的网站,接着跳过包含【'localhost','web','.gov.cn','.org.cn','.edu.cn','.com.cn'】等的地址
还有一个heikey函数,跳过一些标题含有特殊文字的地址

其中的文字包括但不限于【后台,系统,登录,注册,管理,联通,电信,移动,广电,ERP,平台】等,接着往下看

接着是getPass函数,里面的关键词比较敏感,就不发了,主要是匹配document.title和document.body.innerText中是否出现多次关键词,先接着往下看,等下再回来看里面的内容,
接着Intime函数大概就是判断不要太短时间重复触发,defaultAd函数就是生成广告的主体了,下面还有两个是只有1%概率发生的广告,是直接插入一个页面,再看看defaultAd函数

因为篇幅原因,就省略一些内容,这里主要是从网络上【https://vb.*******.com/uios.php】获取多个不同的域名,然后随机获取一个调用insAd函数
这里很明显就是在绘制广告的页面,然后插入到body里面,就实现了下方的正方形广告。
接着看看上方长方形的广告怎么来的,核心来自于加载的【pc_w/all_couplet.js】这个js
这里加载js后有一个自执行函数,里面是通过网络【p.*****.com/s.json?s=】获取基本的配置文件,例如广告所需要的图片等等
然后通过配置文件生成广告页面,插入到body,那么广告的生成就完成了。

三、规则匹配行为


因为一段太长了,这里分一段继续分析,接着轮到【"biglist"】,其实在Fiddler看到后面还有一大段连号的请求,就是从这里发出来的
【"biglist"】实际是把version作为最大值,然后循环生成链接来请求网络数据
例如某一个domain为【*****cq/test3/all】,version为46,那么就生成
/cq/test3/all1.json/cq/test3/all2.json.................................../cq/test3/all45.json/cq/test3/all46.json
请求结果都是16长度的字符串,这些实际都是预设的域名的md5值,用于给"rule"规则匹配使用的
那么接下来"rule"和"special"就是用来挟持链接,增加推广参数
看到这里有的人可能不知道有什么用,那么先发一下受害者视角
Microsoft Edge浏览器主页劫持被锁定,更改无效
记一次联想电脑管家的“小偷小摸”
如何看待深信息机房(部分)电脑篡改百度搜索地址,添加返利参数?
谷歌浏览器地址栏输入百度链接自动跳转到百度02003390_30_hao_pg
好了,那么接来下开始分析行为
这种是比较常规的添加了请求前的时间监听,可以通过自定义的逻辑,将原本的请求链接变成其他的链接。这里的【t.listorrule(e) || t.special(e)】是两个重要的判断逻辑,如果符合其中一个,就会修改链接,这里先看listorrule的逻辑,经过测试,发现京东的链接是符合逻辑的,所以这里直接用作示例
所以请求一个商品主页【https://item.jd.com/10057674219694.html

在链接被修改前断下。看看链接会被修改成什么
可以看到,链接由原来的【[url=https://item.jd.com/10057674219694.html%E3%80%91%E8%A2%AB%E4%BF%AE%E6%94%B9%E4%B8%BA%E3%80%90http://a.anhg33.com/t2.php?https://item.jd.com/10057674219694.html%A1%BF%A3%AC%B8%FC%BD%F8listorrule%BA%AF%CA%FD]https://item.jd.com/10057674219694.html】被修改为【http://a.anhg33.com/t2.php?https://item.jd.com/10057674219694.html】,更进listorrule函数[/url]
首先是获取了链接的域名
然后根据前面说的,请求到的很多16长度的字符串,把域名md5后与这些值进行匹配,如果匹配到了,就会修改链接。
至于会修改成什么链接,就会按照前面请求的配置文件中的targeturl来确定。接着看看special,这里选择的案例档案就是百度搜索了。然后随便搜索一个关键词,这里我搜索的是【模板】,搜索什么都是一样的
可以看到链接被修改后添加了,进入special函数查看
起主要逻辑是匹配百度搜索链接,然后加上自己的返利参数,其中匹配逻辑如下
被添加的返利参数存放在前面的配置文件special类型的targeturl字段

到此,整体逻辑已经分析的差不多了

四、解决方法与预防


当出现这类弹窗广告和推广参数时,大概率是你的某一个或者多个插件出现了问题。而这个插件很有可能不是你自己安装的,而是由于你是用了某些下载器、私服等等东西。为了确定是不是你的插件出现了问题,可以尝试先把所有插件停用,然后再访问之前会出现广告的页面。
如果停用插件后不再广告,那么可以确定是你的插件出现了问题,那么可以继续往下看。不然就可能是被其他东西挟持原因出现的广告,需要往其他方面排查了。下面看看怎么彻底解决问题
方案一
打开扩展程序界面,浏览器访问【chrome://extensions/】,查看所有插件中,是否存在不是你自己安装的插件。如果有,那么很有可能这个就是罪魁祸首。那么此时只要直接把插件移除,就可以解决上述的问题
然而也有可能所有的插件都是你自己安装的,那么就需要检查一下插件中是否包含一些标识文件
方案二
那么接下来就是逐个插件检查了,打开扩展程序界面,浏览器访问【chrome://extensions/】

360极速浏览器中可以看到有一些是直接有【加载来源】,点击后面的地址就可以直接打开插件的安装位置
谷歌浏览器需要点击详情,拉到最下面
可以在【来源】找到安装位置
对于没有的来源链接的,就需要自己打开到默认的安装位置
谷歌浏览器:C:Users{账户名}AppDataLocalGoogleChromeUser DataDefaultExtensions360极速浏览器:{360安装目录}360ChromeChromeUser DataDefaultExtensions
找到安装目录后,查看目录下是否存在【image】文件,并且在这个目录下存在【backg】名字的文件
那么这个插件就是有问题的,直接移除对应的插件,就可以解决问题
这里注意一点,有可能有问题的插件不止一个,如果删除后依然出现广告,建议把所有插件都检查一遍。
现在不单止流氓软件多,流氓软件也可能会在你不知不觉中安装的浏览器插件1.首先第一就是不要随意安装来源不明的插件,对于插件是否有恶意行为不好界定。也有可能是因为浏览器、网站等原因。2.某些软件下载器、私服等,不单止会下载流氓软件,也有可能会安装流氓插件。安装软件建议在官方网站下载,不要在一些三方网站下载不明危险的安装包。
附件为保存的请求等资源证据,解压密码:52pojie
关键词: 浏览器 破解
我不喜欢说话却每天说最多的话,我不喜欢笑却总笑个不停,身边的每个人都说我的生活好快乐,于是我也就认为自己真的快乐。可是为什么我会在一大群朋友中突然地就沉默,为什么在人群中看到个相似的背影就难过,看见秋天树木疯狂地掉叶子我就忘记了说话,看见天色渐晚路上暖黄色的灯火就忘记了自己原来的方向。
srwam 
级别: 超级版主
发帖
633637
飞翔币
8
威望
25247
飞扬币
2856044
信誉值
0

只看该作者 1 发表于: 2022-11-05
来看看
srwam 
级别: 超级版主
发帖
633637
飞翔币
8
威望
25247
飞扬币
2856044
信誉值
0

只看该作者 2 发表于: 2022-11-05
也很正常
级别: 超级版主
发帖
833617
飞翔币
226622
威望
224648
飞扬币
2442865
信誉值
0

只看该作者 3 发表于: 2022-11-06
来看一下
级别: 超级版主
发帖
833617
飞翔币
226622
威望
224648
飞扬币
2442865
信誉值
0

只看该作者 4 发表于: 2022-11-06
不错,了解了