浏览器扩展逆向指北
本文基于笔者对浏览器插件逆向的肤浅认知,和数个浏览器插件逆向的经验,将分别从基本概念、逆向知识与工具、逆向基本流程和逆向实战四个部分来进行介绍。文章难免有错误或疏漏,欢迎批评指正与交流!
基本概念
什么是浏览器插件?
浏览器扩展(Browser extension),俗称(浏览器)插件,它实现的功能很多,例如修改网页、拦截与修改请求、控制浏览器等等。一般来说,浏览器插件都是基于HTML/JS/CSS的,也就是“前端三件套”。它们利用浏览器提供的接口,实现各种功能。
在大多数情况下,基于 Chromium 内核的插件只需要少许修改就可以在 Firefox 中运行,且 Chromium 系的浏览器插件生态更佳丰富,因此文章后面部分的介绍都基于 Chromium 系的浏览器。
插件安装包里都有啥?
部分读者可能注意到,我们安装浏览器插件,实际上浏览器下载的是一个.crx
文件。这个.crx
文件并不是二进制文件这样经过编译的文件,它实质上就是一个压缩包。如果你把它的后缀改为.zip
,解压软件就能读取里面的内容。
解压之后,我们就能看到插件的各个文件了。一个简单的的插件目录结构如下:
1 | |-- 插件目录 |
插件的.js
、.html
、.css
等文件,也可能包含在子目录中。
下面的内容假设读者对.js
、.html
、.css
等有一定的了解。简单来说,.html
是网页的骨架,.css
是网页的衣服,.js
是网页的灵魂。
我们主要关心的是下面几个(类)文件:
manifest.json
:配置文件,包含了插件的名称、版本、权限等信息,类似于安卓的AndroidManifest.xml
。background.js
:后台脚本,在后台处理插件的交互,负责处理插件的全局逻辑。content.js
:内容脚本,是插入到页面中的脚本,可以用来修改页面内容、拦截请求等。
其中manifest.json
中会指定background.js
、content.js
等文件的路径,而一些.html
文件往往描述了插件的UI界面,在这其中也可能插入一些.js
、.css
等脚本或者样式文件。
什么是逆向?
在本文中,我们讨论的“逆向”实质上是“破解”的一件漂亮外衣。如果你曾经有一些逆向经历,或者是听说过一些,可能对逆向的印象是这样的:
但是事实上,浏览器插件的逆向可比这个简单多了。它大概是这样的:
直观点来说,就是从几(十)万行被压缩、混淆的晦涩代码中,抽丝剥茧,利用搜索工具找到关键代码,并且进行(往往是很简单的)修改,实现解锁会员功能等目的。
当然,这对有一些代码功底的读者来说,可能并不难,但是实际上,想要实现破解,仍然需要一些方法和技巧。本文的主要任务就是基于笔者的经验,告诉读者一些方法与技巧,让你少走弯路。
所有插件都能破解吗?
其实不一定。如果插件的功能主要依赖于云端(例如,沉浸式翻译),且云端做了身份验证,那么本地破解的能做的很少。
但是,对于一些功能依赖于本地代码,云端只是负责进行会员身份校验的插件,这种往往就能够破解。可以进一步说,这样的插件一定是可以破解的。
但事实上,我们还应该关心的是:这个插件值得我花时间去破解会员功能吗?与付费购买/订阅相比,我的得与失是什么?
就像给账号设置传统的密码一样,理论上不存在完全无法破解的密码,设置密码其实是在方便性和安全性之间的一个平衡。从破解者的角度来看,当破解所需的成本大于收益之后,没有人会做这样出力不讨好的事情。
因此,在进行破解一个插件之前,请好好想一想这给你带来的得与失。
逆向知识与工具
必须项:
- 浏览器:你得有个浏览器吧。。。
- JavaScript基本语法
- IDE:用来实际修改代码的工具,推荐使用VSCode类编辑器
可选项:
- HTML/CSS
- 调试工具的使用:Chrome DevTools
- 正则表达式:用于匹配代码或者批量替换
- Git:用于管理破解的记录,便于回溯
- 代码可读化与反混淆工具,后面会介绍
JavaScript 基本语法
如果你想要破解一个浏览器插件,你最基本来说需要对 JavaScript 有一定的了解,知道一些常见的js语法,例如:
- 有哪些变量类型?
- 什么是函数?
- 什么是对象?
- ……
等等。当然在实战中,有一些知识也是比较重要的,例如:
- 三元表达式
- 强制类型转换
- 异步编程
IDE
推荐使用VSCode,方便代码编辑、替换。
浏览器开发者工具(F12)的使用
如果你会一些基本操作就更好了。实战中不一定用到。
正则表达式
正则表达式是一种用于匹配字符串中字符组合的模式。能够按照指定的规则,找出代码中所有符合条件的部分。
例如,我们查找一个压缩后的代码中,所有类似e.user.vip
的代码,就可以使用这样的正则表达式来匹配。
1 | [a-zA-Z]+\.user\.vip |
Git
Git 是一个分布式版本控制系统,用于管理代码的变更。
其实这个工具不用也罢,但是我在几次实战中,出现过关键代码替换掉,后来出现插件无法运行的情况,这时候大多只能从头再来。如果有Git工具的话,关键的修改前后可以保存,方便回溯。
还有一个优点是,如果插件更新了,你认为有再次破解的需要的话,就可以参照原来破解的流程,把新版本插件的代码一葫芦画瓢,破解出来。
代码可读化与反混淆
拿到插件之后,格式往往没有前面那张图这么清晰,而是被压缩、混淆的。
这种代码当然不是作者故意这么写的,而是写完之后,使用一些工具进行压缩、混淆得到的。
但是根据我的经验,插件的作者往往不会对代码进行非常深度的混淆,一般做的就是把变量、函数、类的名称替换为无意义的名称(一般是很短的大小写英文字母),或者把一些代码替换为更难读懂的形式,比如说布尔值替换为!0
、!1
,频繁使用三元表达式、布尔值 && (执行逻辑)
这样更佳晦涩的方式,等等。但是类的属性等名称往往不会改变,这将成为我们逆向分析的一个重要抓手。
下图是一个典型的代码片段,其中红色方框内是被替换的无意义的名称,绿色的是未被替换的类的属性,黄色是经过转换后的更佳晦涩的代码。
关于混淆的拓展阅读:JS 反混淆 - Jartto’s blog
一般来说,我们使用代码格式化工具对浏览器插件的代码(主要是.js
代码)进行反压缩,就可以了开始破解了。Vscode等IDE有自带的格式化方法,但是无法批量对插件的所有代码进行格式化。进行这里介绍一个笔者之前写的Python脚本,能够对整个项目中所有.js
、.css
、.html
和.json
文件进行代码格式化。
逆向基本流程
下载插件并安装
下载
下载推荐使用 crxsoso,国内即可访问,且可以直接下载.zip
格式的插件包。
安装
测试的浏览器建议使用Chrome,可以新开一个用户资料,专门用于测试。不建议用平时的主力浏览器,这样能够排除其他插件或者脚本的影响。
把安装包解压到一个合适的地方,然后浏览器进入chrome://extensions
,开启开发者模式,点击“加载已解压的扩展程序”,选择解压后的插件目录即可。
破解思路
我们最核心的思路就是,找到代码中(往往是在background.js
中)判断会员状态的代码,并且修改。
当然我们不可能靠肉眼看完几万行晦涩的代码,而是利用一些技巧,来定位关键的代码。
如果你不确定在哪个文件里面,也没关系,可以使用全局搜索功能,可以对插件目录下所有代码进行检索。
那么,如何定位关键的代码呢?
定位关键代码
我一般使用的思路是两个:
全局尝试搜索
vip
、会员
、pro
、premium
、subscription
等关键词,看是否能找到相应的类的属性,以此为跳板找到核心逻辑,依次进行修改。从字符串入手,在使用使用插件过程中找到一些和会员相关的字符串,比如说“This is a premium feature”、“您尚未开通会员”、“Free user”等,然后全局搜索这些字符串,查看这些字符串附近的代码逻辑,以此找到核心的逻辑。
一般来说,我更推荐使用第二种方法,这样能够快速定位到关键的逻辑。第一种方法是实在没有头绪的时候,进行尝试的。
需要注意的是,如果文字是中文等非英文字符,那么有可能插件代码中的字符是转义后的字符,如果你搜不到,这时候可以对文件进行转义,或者试着搜索转义前的字符串。
这里推荐一个开发者常用的工具集合Ctools,支持多端使用(包括在线使用、浏览器插件),开源免费,以后在也不用去网上搜xxx在线网站啦😄
搜索转义后的字符串:
修改代码
定位到关键代码之后,大多数修改的方法都是围绕着布尔表达式进行的。
例如,如果代码中有一段逻辑是:
1 | if (isVip) { |
我们可以把isVip
直接修改为true
或者1
,这样就可以让插件以为你是会员了。当然上面只是一个最简单的例子,实际的代码可能复杂得多,但核心思路都是一样的。
测试
修改完代码之后,需要进行测试。可以找到插件的官网(如有),点击价格/Pricing,此处一般有会员和非会员功能的对比。
实战演示
我们以某插件为例,介绍一下破解流程。插件下载地址
解压安装后,先跑一下反压缩脚本,把压缩饼干展开
发现background.js
有足足十万多行。
回到浏览器,打开插件,登录账号后,发现有一个提示,提示我们升级会员。
搜索这个字符串,发现没有结果
于是我们再尝试搜索Unicode 转义前的字符串
1 | \u5347\u7ea7\u4f1a\u5458\u4eab\u66f4\u591a\u9ad8\u7ea7\u6743\u76ca |
发现一个字符串。
我们再选取这个字符串的父元素名称upgradeToPremiumTitle
,进行搜索
在这附近似乎并没有什么有价值的信息,于是想办法找另一个试试。打算从实际功能入手。
点击新建词本,会跳转到开通会员的介绍页面。类似前面的方法,我们搜索“新建词本”,得到名称creatNewWordBook
,再次进行搜索
看到一个previlege
,这很可能是记录用户是否是会员信息的名称。(实际上后来发现并不是,这应该是判断词书是否是会员专属的属性)
继续搜索,发现有可能是,因为搜到几个有和"VIP"
进行判断的表达式。
我们现在需要把所有出现的privilege
判断逻辑都进行修改,一个简单的方法是把y.privilg
这样的表达式全部替换为"vip"
。
等等,在替换之前,先在本地初始化一下Git,方便后续回溯。
直接全部提交即可。
把搜索到的判断逻辑全部修改。
保存修改之后,在扩展程序管理页面刷新一下插件
测试发现,还是不能解锁功能,考虑再找一个突破口。
搜索这个字符串
这是一个三元表达式,我们使用Ctrl
+ 左键
点击Hr
函数,跳转到它的定义
看起来像是在验证会员是否过期,我们让它直接返回1。
同时,我们搜索expireAt
关键词,发现index.js
中也有一个相同的函数,把它也改了。
刷新发现,已经显示是会员了。
假设我们准备破解到这里为止,接下来我们检查一下各项会员功能是否能正常使用。
高级翻译引擎无法使用,肯定是添加了服务端校验,在我们的意料之中。因此使用积分肯定也不行了。
英英词典可以正常使用。
高级/专业词本虽然点击切换不会跳转开通会员的页面了,但是等了半天也没反应。
抓包发现,有服务端校验,暂时先放弃。
高级释义,没找到这个功能,猜测是AI释义。测试发现,服务器同样提示需要升级会员。
AI语法分析,没找到该功能。
无限存储单词这个功能,我在原先逆向的版本中(v3.9.0),是可以无限添加的,但是该版本竟然不行,超过50个单词之后服务端返回错误信息。
这里就要提到使用Git工具的另一个好处了:上次逆向分析的过程没有保存下来,我完全忘了怎么逆向了💦
我猜测原先版本可能没有进行云端校验,但是抓包分析却发现,二者竟然都需要经过云端验证。
仔细分析二者发现,竟然是他们发送的api接口域名不同,api.relingo.net
没有验证添加后是否超过50个免费用户的额度,而cn.relingo.net
有验证。于是我搜索了关键字,发现一个关键的名称endPoint
,它似乎代表了api的域名。
于是试着修改相关逻辑,使得请求发出的域名固定为api.relingo.net
但是,改了之后还是不行。。。
奇怪了,两个请求的域名也一样,还有什么可能呢?
没办法,只能把两个请求的cURL
命令用字符串对比工具进行对比:
发现只有几个细微的区别。不会是因为版本不同吧?我修改manifest.json
,把新版的插件版本改回3.9.0,再测试一下,竟然真的可以了。服务端这样的判断逻辑,也是让我没想到😅
于是找到发送版本的参数的代码,将其强制改为3.9.0,测试就可以无限制添加单词了。
例句未测试,我们姑且猜测也可以吧?
导出单词似乎其实是免费功能,导出到Anki需要经过服务端,暂时无法破解。
下面四个功能,创建个人单词本、开启多个词本、词义编辑、学习统计,经过测试,均可使用。
因此,总结一下能用的功能:
- 高级翻译引擎
- 高级翻译引擎积分
- 英英词典
- 高级/专业词本
- 高级释义
- AI语法分析
- 无限存储单词
- 无限存储例句
- 导入导出单词
- 导出到Anki
- 创建个人单词本
- 开启多个词本
- 词义编辑
- 学习统计
逆向与反逆向
逆向“对”吗?
在文章结束之前,我还想从破解者和开发者的角度。讨论一下我对插件逆向这个行为本身的看法。
破解插件这个行为,“对”吗?从道德的角度讲,我认为当然是不对的,这显然侵犯了作者的权益。
因此本教程的初衷是为了普及相关知识,仅供学习交流,不建议随意使用和模仿,甚至以此牟利,因此产生的纠纷与作者无关。
但是从实际情况上来讲,部分插件的定价不合理,导致用户负担不起价格,或是付费渠道的狭窄,使得许多优秀的软件,难以得到它们应有客户。这事实上也对开发者是不利的。在这方面,我认为简悦是一个很好的范例,它的买断制政策与合适的价格,使得很少人有动力去破解它。
我是插件的开发者,怎样能减少破解这种行为?
对于开发者来说,有一些建议供您参考:
使用混淆工具
使用混淆工具,降低逆向者的破解的收益-成本比重。
将产品与云端紧密结合
将产品的功能与云端紧密结合,并在后端设置校验,而不是云端简单地只负责校验。但这也要考虑到产品的属性,以及会增加一定的成本。
合理定价
考虑目标人群的支付能力,合理定价,设置促销、教育优惠等活动。
及时更新
不断更新吸引用户的新功能。因为逆向后的版本无法自动更新,若开发者能不断推出有吸引力的新功能,有需求的用户自然会选择订阅支持,而不是浪费时间找破解资源/进行破解。
考虑开源/免费
😊