白帽故事 · 2024年12月20日 0

浏览器扩展逆向指北

文章原文:https://shiquda.link/guide-on-browser-extension-inverse-engineering/

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

基本概念

什么是浏览器插件?

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

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

插件安装包里都有啥?

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

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

|– 插件目录
|– 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.js、content.js等文件的路径,而一些.html文件往往描述了插件的UI界面,在这其中也可能插入一些.js、.css等脚本或者样式文件。

什么是逆向?

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

file

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

file

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

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

所有插件都能破解吗?

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

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

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

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

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

逆向知识与工具

必须项:

  • 浏览器:你得有个浏览器吧。。。
  • JavaScript基本语法
  • IDE:用来实际修改代码的工具,推荐使用VSCode类编辑器

可选项:

  • HTML/CSS
  • 调试工具的使用:Chrome DevTools
  • 正则表达式:用于匹配代码或者批量替换
  • Git:用于管理破解的记录,便于回溯
  • 代码可读化与反混淆工具,后面会介绍
  • JavaScript 基本语法

如果你想要破解一个浏览器插件,你最基本来说需要对 JavaScript 有一定的了解,知道一些常见的js语法,例如:

  • 有哪些变量类型?
  • 什么是函数?
  • 什么是对象?
  • ……

等等。当然在实战中,有一些知识也是比较重要的,例如:

  • 三元表达式
  • 强制类型转换
  • 异步编程

    IDE

推荐使用VSCode,方便代码编辑、替换。

浏览器开发者工具(F12)的使用

如果你会一些基本操作就更好了。实战中不一定用到。

正则表达式

正则表达式是一种用于匹配字符串中字符组合的模式。能够按照指定的规则,找出代码中所有符合条件的部分。

例如,我们查找一个压缩后的代码中,所有类似e.user.vip的代码,就可以使用这样的正则表达式来匹配。

[a-zA-Z]+\.user\.vip

Git

Git 是一个分布式版本控制系统,用于管理代码的变更。

其实这个工具不用也罢,但是我在几次实战中,出现过关键代码替换掉,后来出现插件无法运行的情况,这时候大多只能从头再来。如果有Git工具的话,关键的修改前后可以保存,方便回溯。

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

代码可读化与反混淆

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

file

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

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

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

file

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

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

逆向基本流程

下载插件并安装

下载

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

安装

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

file

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

破解思路

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

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

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

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

定位关键代码

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

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

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

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

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

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

file

搜索转义后的字符串:

file

修改代码

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

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

if (isVip) {
    // 执行会员逻辑
} else {
    // 执行非会员逻辑
}

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

测试

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

file

实战演示

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

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

file

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

file

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

file

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

file

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

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

发现一个字符串。

file

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

file

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

file

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

file

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

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

file

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

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

file

file

直接全部提交即可。

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

file

保存修改之后,在扩展程序管理页面刷新一下插件

file

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

搜索这个字符串

file

file

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

file

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

file

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

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

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

file

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

file

英英词典可以正常使用。

file

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

file

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

file

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

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

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

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

file

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

file

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

file

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

file

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

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

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

file

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

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

file

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

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

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

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

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

逆向与反逆向

逆向“对”吗?

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

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

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

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

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

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

使用混淆工具

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

将产品与云端紧密结合

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

合理定价

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

及时更新

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

考虑开源/免费

😊

参考资料

浏览器扩展
[Chrome扩展程序](Chrome 扩展程序 | Chrome Extensions | Chrome for Developers "Chrome扩展程序")
JS反混淆