浏览器扩展逆向指北

本文基于笔者对浏览器插件逆向的肤浅认知,和数个浏览器插件逆向的经验,将分别从基本概念逆向知识与工具逆向基本流程逆向实战四个部分来进行介绍。文章难免有错误或疏漏,欢迎批评指正与交流!

基本概念

什么是浏览器插件?

浏览器扩展(Browser extension),俗称(浏览器)插件,它实现的功能很多,例如修改网页、拦截与修改请求、控制浏览器等等。一般来说,浏览器插件都是基于HTML/JS/CSS的,也就是“前端三件套”。它们利用浏览器提供的接口,实现各种功能。

在大多数情况下,基于 Chromium 内核的插件只需要少许修改就可以在 Firefox 中运行,且 Chromium 系的浏览器插件生态更佳丰富,因此文章后面部分的介绍都基于 Chromium 系的浏览器。

插件安装包里都有啥?

部分读者可能注意到,我们安装浏览器插件,实际上浏览器下载的是一个.crx文件。这个.crx文件并不是二进制文件这样经过编译的文件,它实质上就是一个压缩包。如果你把它的后缀改为.zip,解压软件就能读取里面的内容。

解压之后,我们就能看到插件的各个文件了。一个简单的的插件目录结构如下:

1
2
3
4
5
6
7
|-- 插件目录
|-- manifest.json
|-- popup.html
|-- background.js
|-- content.js
|-- options.html
|-- ...

插件的.js.html.css等文件,也可能包含在子目录中。

下面的内容假设读者对.js.html.css等有一定的了解。简单来说,.html是网页的骨架,.css是网页的衣服,.js是网页的灵魂

我们主要关心的是下面几个(类)文件:

  • manifest.json:配置文件,包含了插件的名称、版本、权限等信息,类似于安卓的AndroidManifest.xml
  • background.js:后台脚本,在后台处理插件的交互,负责处理插件的全局逻辑。
  • content.js:内容脚本,是插入到页面中的脚本,可以用来修改页面内容、拦截请求等。

其中manifest.json中会指定background.jscontent.js等文件的路径,而一些.html文件往往描述了插件的UI界面,在这其中也可能插入一些.js.css等脚本或者样式文件。

什么是逆向?

在本文中,我们讨论的“逆向”实质上是“破解”的一件漂亮外衣。如果你曾经有一些逆向经历,或者是听说过一些,可能对逆向的印象是这样的:

Pasted image 20241009235501

但是事实上,浏览器插件的逆向可比这个简单多了。它大概是这样的:

PixPin_2024-10-09_23-56-42

直观点来说,就是从几(十)万行被压缩、混淆的晦涩代码中,抽丝剥茧,利用搜索工具找到关键代码,并且进行(往往是很简单的)修改,实现解锁会员功能等目的。

当然,这对有一些代码功底的读者来说,可能并不难,但是实际上,想要实现破解,仍然需要一些方法和技巧。本文的主要任务就是基于笔者的经验,告诉读者一些方法与技巧,让你少走弯路。

所有插件都能破解吗?

其实不一定。如果插件的功能主要依赖于云端(例如,沉浸式翻译),且云端做了身份验证,那么本地破解的能做的很少。

但是,对于一些功能依赖于本地代码,云端只是负责进行会员身份校验的插件,这种往往就能够破解。可以进一步说,这样的插件一定是可以破解的。

但事实上,我们还应该关心的是:这个插件值得我花时间去破解会员功能吗?与付费购买/订阅相比,我的得与失是什么?

就像给账号设置传统的密码一样,理论上不存在完全无法破解的密码,设置密码其实是在方便性和安全性之间的一个平衡。从破解者的角度来看,当破解所需的成本大于收益之后,没有人会做这样出力不讨好的事情。

因此,在进行破解一个插件之前,请好好想一想这给你带来的得与失。

逆向知识与工具

必须项:

  • 浏览器:你得有个浏览器吧。。。
  • 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工具的话,关键的修改前后可以保存,方便回溯

还有一个优点是,如果插件更新了,你认为有再次破解的需要的话,就可以参照原来破解的流程,把新版本插件的代码一葫芦画瓢,破解出来。

代码可读化与反混淆

拿到插件之后,格式往往没有前面那张图这么清晰,而是被压缩、混淆的。

PixPin_2024-10-10_00-18-08

这种代码当然不是作者故意这么写的,而是写完之后,使用一些工具进行压缩、混淆得到的。

但是根据我的经验,插件的作者往往不会对代码进行非常深度的混淆,一般做的就是把变量、函数、类的名称替换为无意义的名称(一般是很短的大小写英文字母),或者把一些代码替换为更难读懂的形式,比如说布尔值替换为!0!1,频繁使用三元表达式、布尔值 && (执行逻辑)这样更佳晦涩的方式,等等。但是类的属性等名称往往不会改变,这将成为我们逆向分析的一个重要抓手。

下图是一个典型的代码片段,其中红色方框内是被替换的无意义的名称,绿色的是未被替换的类的属性,黄色是经过转换后的更佳晦涩的代码。

PixPin_2024-10-10_10-23-17

关于混淆的拓展阅读:JS 反混淆 - Jartto’s blog

一般来说,我们使用代码格式化工具对浏览器插件的代码(主要是.js代码)进行反压缩,就可以了开始破解了。Vscode等IDE有自带的格式化方法,但是无法批量对插件的所有代码进行格式化。进行这里介绍一个笔者之前写的Python脚本,能够对整个项目中所有.js.css.html.json文件进行代码格式化

逆向基本流程

下载插件并安装

下载

下载推荐使用 crxsoso,国内即可访问,且可以直接下载.zip格式的插件包。

安装

测试的浏览器建议使用Chrome,可以新开一个用户资料,专门用于测试。不建议用平时的主力浏览器,这样能够排除其他插件或者脚本的影响。

PixPin_2024-10-10_14-18-32

把安装包解压到一个合适的地方,然后浏览器进入chrome://extensions,开启开发者模式,点击“加载已解压的扩展程序”,选择解压后的插件目录即可。

破解思路

我们最核心的思路就是,找到代码中(往往是在background.js中)判断会员状态的代码,并且修改。

当然我们不可能靠肉眼看完几万行晦涩的代码,而是利用一些技巧,来定位关键的代码。

如果你不确定在哪个文件里面,也没关系,可以使用全局搜索功能,可以对插件目录下所有代码进行检索。

那么,如何定位关键的代码呢?

定位关键代码

我一般使用的思路是两个:

  1. 全局尝试搜索vip会员propremiumsubscription等关键词,看是否能找到相应的类的属性,以此为跳板找到核心逻辑,依次进行修改。

  2. 字符串入手,在使用使用插件过程中找到一些和会员相关的字符串,比如说“This is a premium feature”、“您尚未开通会员”、“Free user”等,然后全局搜索这些字符串,查看这些字符串附近的代码逻辑,以此找到核心的逻辑。

一般来说,我更推荐使用第二种方法,这样能够快速定位到关键的逻辑。第一种方法是实在没有头绪的时候,进行尝试的。

需要注意的是,如果文字是中文等非英文字符,那么有可能插件代码中的字符是转义后的字符,如果你搜不到,这时候可以对文件进行转义,或者试着搜索转义前的字符串

这里推荐一个开发者常用的工具集合Ctools,支持多端使用(包括在线使用、浏览器插件),开源免费,以后在也不用去网上搜xxx在线网站啦😄

PixPin_2024-10-10_10-57-29

搜索转义后的字符串:

PixPin_2024-10-10_11-01-58

修改代码

定位到关键代码之后,大多数修改的方法都是围绕着布尔表达式进行的。

例如,如果代码中有一段逻辑是:

1
2
3
4
5
if (isVip) {
// 执行会员逻辑
} else {
// 执行非会员逻辑
}

我们可以把isVip直接修改为true或者1,这样就可以让插件以为你是会员了。当然上面只是一个最简单的例子,实际的代码可能复杂得多,但核心思路都是一样的。

测试

修改完代码之后,需要进行测试。可以找到插件的官网(如有),点击价格/Pricing,此处一般有会员和非会员功能的对比。

Pasted image 20241010141322

实战演示

我们以某插件为例,介绍一下破解流程。插件下载地址

解压安装后,先跑一下反压缩脚本,把压缩饼干展开

PixPin_2024-10-10_15-07-14

发现background.js有足足十万多行。

PixPin_2024-10-10_15-09-06

回到浏览器,打开插件,登录账号后,发现有一个提示,提示我们升级会员。

PixPin_2024-10-10_15-03-03

搜索这个字符串,发现没有结果

PixPin_2024-10-10_15-11-04

于是我们再尝试搜索Unicode 转义前的字符串

1
\u5347\u7ea7\u4f1a\u5458\u4eab\u66f4\u591a\u9ad8\u7ea7\u6743\u76ca

发现一个字符串。

PixPin_2024-10-10_15-13-35

我们再选取这个字符串的父元素名称upgradeToPremiumTitle,进行搜索

PixPin_2024-10-10_15-17-35

在这附近似乎并没有什么有价值的信息,于是想办法找另一个试试。打算从实际功能入手。

PixPin_2024-10-10_15-19-58

点击新建词本,会跳转到开通会员的介绍页面。类似前面的方法,我们搜索“新建词本”,得到名称creatNewWordBook,再次进行搜索

PixPin_2024-10-10_15-22-29

看到一个previlege,这很可能是记录用户是否是会员信息的名称。(实际上后来发现并不是,这应该是判断词书是否是会员专属的属性)

继续搜索,发现有可能是,因为搜到几个有和"VIP"进行判断的表达式。

PixPin_2024-10-10_15-24-27

我们现在需要把所有出现的privilege判断逻辑都进行修改,一个简单的方法是把y.privilg这样的表达式全部替换为"vip"

等等,在替换之前,先在本地初始化一下Git,方便后续回溯。

file_1728545278865_460
PixPin_2024-10-10_15-27-43

直接全部提交即可。

把搜索到的判断逻辑全部修改。

PixPin_2024-10-10_15-35-18

保存修改之后,在扩展程序管理页面刷新一下插件
PixPin_2024-10-10_15-37-15 1

测试发现,还是不能解锁功能,考虑再找一个突破口。

搜索这个字符串

PixPin_2024-10-10_15-50-58

PixPin_2024-10-10_15-53-01

这是一个三元表达式,我们使用Ctrl + 左键点击Hr函数,跳转到它的定义

PixPin_2024-10-10_15-55-19

看起来像是在验证会员是否过期,我们让它直接返回1。

PixPin_2024-10-10_15-57-12

同时,我们搜索expireAt关键词,发现index.js中也有一个相同的函数,把它也改了。

刷新发现,已经显示是会员了。

假设我们准备破解到这里为止,接下来我们检查一下各项会员功能是否能正常使用。

PixPin_2024-10-10_15-56-30

高级翻译引擎无法使用,肯定是添加了服务端校验,在我们的意料之中。因此使用积分肯定也不行了。

PixPin_2024-10-10_16-01-06

英英词典可以正常使用。

PixPin_2024-10-10_16-57-39 1

高级/专业词本虽然点击切换不会跳转开通会员的页面了,但是等了半天也没反应。

PixPin_2024-10-10_16-58-24

抓包发现,有服务端校验,暂时先放弃。

PixPin_2024-10-10_17-01-20

高级释义,没找到这个功能,猜测是AI释义。测试发现,服务器同样提示需要升级会员。

AI语法分析,没找到该功能。

无限存储单词这个功能,我在原先逆向的版本中(v3.9.0),是可以无限添加的,但是该版本竟然不行,超过50个单词之后服务端返回错误信息。

这里就要提到使用Git工具的另一个好处了:上次逆向分析的过程没有保存下来,我完全忘了怎么逆向了💦

PixPin_2024-10-10_18-11-04

我猜测原先版本可能没有进行云端校验,但是抓包分析却发现,二者竟然都需要经过云端验证。

PixPin_2024-10-10_17-44-17

仔细分析二者发现,竟然是他们发送的api接口域名不同,api.relingo.net没有验证添加后是否超过50个免费用户的额度,而cn.relingo.net有验证。于是我搜索了关键字,发现一个关键的名称endPoint,它似乎代表了api的域名。

PixPin_2024-10-10_17-40-35

于是试着修改相关逻辑,使得请求发出的域名固定为api.relingo.net

PixPin_2024-10-10_17-43-07 1

但是,改了之后还是不行。。。

奇怪了,两个请求的域名也一样,还有什么可能呢?

没办法,只能把两个请求的cURL命令用字符串对比工具进行对比:

PixPin_2024-10-10_18-24-42

发现只有几个细微的区别。不会是因为版本不同吧?我修改manifest.json,把新版的插件版本改回3.9.0,再测试一下,竟然真的可以了。服务端这样的判断逻辑,也是让我没想到😅

于是找到发送版本的参数的代码,将其强制改为3.9.0,测试就可以无限制添加单词了。

PixPin_2024-10-10_18-07-48

例句未测试,我们姑且猜测也可以吧?

导出单词似乎其实是免费功能,导出到Anki需要经过服务端,暂时无法破解。

下面四个功能,创建个人单词本、开启多个词本、词义编辑、学习统计,经过测试,均可使用。

因此,总结一下能用的功能:

  • 高级翻译引擎
  • 高级翻译引擎积分
  • 英英词典
  • 高级/专业词本
  • 高级释义
  • AI语法分析
  • 无限存储单词
  • 无限存储例句
  • 导入导出单词
  • 导出到Anki
  • 创建个人单词本
  • 开启多个词本
  • 词义编辑
  • 学习统计

逆向与反逆向

逆向“对”吗?

在文章结束之前,我还想从破解者和开发者的角度。讨论一下我对插件逆向这个行为本身的看法。

破解插件这个行为,“”吗?从道德的角度讲,我认为当然是不对的,这显然侵犯了作者的权益。

因此本教程的初衷是为了普及相关知识,仅供学习交流,不建议随意使用和模仿,甚至以此牟利,因此产生的纠纷与作者无关

但是从实际情况上来讲,部分插件的定价不合理,导致用户负担不起价格,或是付费渠道的狭窄,使得许多优秀的软件,难以得到它们应有客户。这事实上也对开发者是不利的。在这方面,我认为简悦是一个很好的范例,它的买断制政策与合适的价格,使得很少人有动力去破解它。

我是插件的开发者,怎样能减少破解这种行为?

对于开发者来说,有一些建议供您参考:

使用混淆工具

使用混淆工具,降低逆向者的破解的收益-成本比重。

将产品与云端紧密结合

将产品的功能与云端紧密结合,并在后端设置校验,而不是云端简单地只负责校验。但这也要考虑到产品的属性,以及会增加一定的成本

合理定价

考虑目标人群的支付能力,合理定价,设置促销、教育优惠等活动。

及时更新

不断更新吸引用户的新功能。因为逆向后的版本无法自动更新,若开发者能不断推出有吸引力的新功能,有需求的用户自然会选择订阅支持,而不是浪费时间找破解资源/进行破解。

考虑开源/免费

😊


参考资料

  1. 浏览器扩展 - Mozilla | MDN
  2. Chrome 扩展程序  |  Chrome Extensions  |  Chrome for Developers
  3. JS 反混淆 - Jartto’s blog