欢迎大家关注

来源:juejin.im/post/6890344584078721031

文末送书5本

背景

之前扒过飞书的源码,从代码设计架构层面里里外外学习一把,飞书还是挺 “大方” 的,源码在客户端和网页端都一览无余,不过好像新版本已经看不到了。相关的文章由于在内网技术论坛发过了不便于再发出来(泄露内部资料会被查水表的),因此这次周末抽时间换一个鸟窝来掏一掏。

一不小心发现迅雷的客户端竟然也是基于 Electron 开发的,那代码就好扒拉了。(先吐槽一下这新版本的某 lei 为什么要抄钉钉的界面,这些年某 lei 都不知道自己要干什么了,每个版本都招人嫌)。

拆解篇

1、一点背景知识说明

基于前端技术栈 Electron 构建的桌面应用,本质上都是加载本地前端资源文件,而这些文件通常是用 asar 格式(类似 windows iso 镜像)的方式进行打包,然后运行时再通过挂在到内存实现前端资源文件 js/css/html/img 等文件的读取。

这么说 asar 想办法挂载就可以随意阅读源码了吗?不是的。同时 asar 会提供一套通过加密方式防止任意解压,飞书就是这么做的,直接通过 asar extract 的方式无法解包出来。但是由于 node 端和 rust 构建的二进制文件如果打包到 asar 会导致无法链接到这些二进制文件,因此需要从 asar 中独立出来,因而导致有部分 js 文件仍然裸露在外面。不过即便没有任何 js 是暴露的仍然是有办法爆破的。

啊,跑偏了,先不谈飞书,今天的主菜是迅雷。

那迅雷的前端资源文件是怎么管理的呢?

是在下想多了,不好意思,迅雷梅川酷子,都摊着在那呢,根本没用 asar 打包 / 加密。

2、开撬

既然 js 都暴露了,也没什么好绕的,直接植入代码吧。我们都知道 Electron 是有 render 进程和 Node 进程的,接下来这一步需要猜猜看哪个文件是负责 render 主进程的?

好吧不用猜,名字都非常人类可读,就 main-renderer(主窗口渲染进程)。打开找到 html 文件(js 也可以)插入如下这串。

双击启动,调试窗口出来了,可以大致看到整体页面结构了。

然后看了一下,迅雷的悬浮小圆圈和主窗口,分别用一个 BrowserWindow 来实现。有趣的是那个小圆圈窗口其实并不小,鼠标悬停出来的那个浮窗也是它的一部分,为了让小圆圈在屏幕的任何位置都可以看到悬浮窗,所以整个小圆圈的 BrowserWindow 是大约 4 倍的悬浮窗口大小。

独立窗口的检视界面 - 窗口实际是 4 倍 浮窗大小,灰色部分全都是这个 “小” 浮窗所使用的 BrowserWindow 区域。

3、一点防御措施

从代码来看,nodejs 进程只有一个文件 main.js ,是 webpack 的构建产物,看源码这里的 BrowserWindow 的 webPreference 参数是把 devTools 禁用掉的,导致直接在命令行里敲 openDevTools 是不能检视任意窗口的。

当然了,这里即便是混淆过了也没关系,毕竟还是明文,把 1 改成 0,把它打开就好(双叹号 /true/1 啥都行,开心就好)。不过由于迅雷的窗口实在是太多了,下载弹窗是独立窗口,选择文件夹是独立窗口,各种广告窗口也是,需要改的配置点很多,这里就不列了,总共有 10 个窗口,这个配置点按需打开(批量替换也行,谨慎操作就行)。

进程结构

呃…… 然后要干啥…… 好像也没什么好看的了,代码是混淆过的,也没有 map 文件。而且前端部分的代码也没什么技术含量可以说的,哪个 web 页面都那样。那看看进程分工吧。

1、进程树

在进程树里可以看出来,几乎全部的进程都是 Thunder.exe,可见 Thunder.exe 作为进程派发入口(类似 server 的网关,而并不直接是业务本身),用户启动的时候传参是 --StartType:DesktopIcon,随后它唤起了两组进程,一组是 Electron main 进程,main 进程唤起相关的 renderer;然后是下载的 SDK 服务 DownlaodSDKServer。

那么迅雷的进程关系差不多是清楚了:多个 Electron 窗口,对应一个 DownloadSDK。

2、通信方式

那么 Electron 的进程(甭管 main-process 还是 renderer-process,统称 electron 进程) 和 DownloadSDK 是如何通信的呢?

进程间通信一般都是依靠 ipc 管道的形式来实现。不过迅雷似乎没按套路来,它的 DownloadSDK 是控制台程序,意味着很有可能是通过 stdio 的方式来进行交互的(后续证明不是)。

通过观察进程打开的句柄,看到很诡异的一个现象:DownloadSDK 并没有打开任何 ipc 管道,反倒是前端进程打开了一个。

3、前端的 ipc

而 Electron 打开的这个 handler 进程名称,查了一下,竟然全是 Electron 进程使用的,而且是所有进程。

那么不妨做出一个大胆的推测:前端多窗口之间是靠自建的 ipc 通道实现的,而 ipc 是 1 server 对 N client 的方式,那么 server 很有可能就是在主窗口上的,也就是前文看到那个及其明显的 main-renderer 进程,通过控制台查看,确实如此,nodejs 的 net 方式创建了一个 server,并且将一个叫做 __xdasIPCServerInstance 的对象暴露在全局环境供前端 js 调用,也即 jsapi。

而小窗口并不存在上述 server 实例,而相对应的有一个 client 实例。

4、和 DownloadSDK 的通讯方式

这样看起来就很奇葩了,前端进程之间是通过自建的 ipc 管道通信的,但是并没有跟 DownloadSDK 有任何通信管道,难道它俩是心有灵犀无言自通?啊这…… 程序员是唯物主义的!微信搜索公众号:前端技术编程,回复:前端 领取资料 。

那怎么查它到底是怎么跟前端进程交互的呢?既然前端暴露了 server sdk instance,那意味着 DownLoadSDK 肯定是以一种 proxy 的方式暴露在这上面作为 jsapi 的。可以拿【创建一个下载任务 api】来顺藤摸瓜。看了主窗口的 server instance 一下果然有这个方法:createTask ,应该就是前端用于创建下载任务用的 api。

chrome 浏览器里查代码不方便,转战 vscode 看源码,搜索 createTask 这个函数的声明位置,看到这一段(篇幅控制,此处删减了部分代码)。

createTask(e, t)
{return n(this, void 0, void 0, function* () {
.....          }
switch (e) {case h.DownloadKernel.TaskType.P2sp:
...case h.DownloadKernel.TaskType.Bt:
...case h.DownloadKernel.TaskType.Emule:
...case h.DownloadKernel.TaskType.Group:
...case h.DownloadKernel.TaskType.Magnet:
...default:              i = !1;}return(
... _.fireTaskEvent(h.DownloadKernel.TaskEventType.TaskCreated, [          );        });      }

没跑了,证实了我前面的猜想,这个 __xdasIPCServerInstance 就是 download sdk 封装到前端的 proxy。

继续查,这个 fireTaskEvent 是怎么处理的,阅读代码过程繁琐按下不提,就看这两段代码 (有删减整理)。

// 片段一
(e.getDownloadSdkVersion = function () {
let e = a.join(__rootDir, "../bin/SDK/DownloadSDKServer.exe");
return v.getFileVersion(e);
}),
// 片段二
y = l.default(o.join(__rootDir, "../bin/ThunderHelper.node"));
let F = "/ssdkver " + u.DownloadKernelManager.getDownloadSdkVersion();
B.push(F)
y.shellExecute(0, "open", o, B, H, "SW_SHOW");

很显然,DownloadSDK 是通过一个 ThunderHelper.node 的 nodejs addon 模块来启动、通信的。

我们知道,nodejs 可以通过 ffi 等方式实现内存共享,以达到两个进程不需要通过 pipe/sock 等管道就达到通信的目的。而通过工具观察 Thunder.exe 的唤起关系、句柄关系,两者的关系就更加一目了然了:ELectron 前端进程加载 DownloadSDK 进程,并且通过 \Sessions\5\BaseNamedObjects\xx@22123720|SendShareMemory 这种内存通道来实现的通信,句柄一一对应上了。

总结

扒拉了半天,扒完了有点空虚是怎么回事?

  • 迅雷的代码架构关系是轻 node 而重前端,把所有的 node 加载、进程管理、多窗口通信都放在前端进程的主窗口进程里。关于这个做法,我尊重而不认同。前端进程不应该做太重的底层交互,尤其是 js 这种单线程语言,天然的就运行效率低,而且主窗口使用这么频繁就不怕卡住吗

  • Electron 天然就有 ipc 通信能力,完全可以在 node 端做一个消息网关,达成每个窗口通信的能力,完全不需要自建一个 ipc server-client 体系。可能这也是一开始就把大量工作放在前端 (主窗口) 了导致后期的程序设计受限。说不定是个历史包袱

  • 用一个 node addon 的方式来跟 DownloadSDK 来通信,这点是可以点个赞的,虽然是业界标准(飞书是通过 rust,基本原理类似),但是我目前所负责的业务并没有做到这样,所以在惭愧的同时也给它点个赞

  • 迅雷使用的 Electron 版本是 9.2.1,vscode 也是这个版本,好神奇!非常好奇为何业界都用这个版本,事实上 electron 9.x 最新版本已经更新到 9.3.3 了(2020 年 10 月 28 日)这个 9.2.1 有什么魔力让业界都用它吗

  • 这里说明一下,Electron 从 6.0 开始就不支持 windows7 (非 sp1) 及以下的版本了。微信搜索公众号:前端技术编程,回复:前端 领取资料 。

  • 我在 win7 系统上用迅雷安装器安装迅雷最新版本,发现 electron 用的是 1.8.6 版本

  • Electron 的主入口是处理过了的,通过 Thunder.exe 程序干了很多除了启动前端以外的事情,这个定制还是挺棒的,因为这样就可以把各种进程模块管理起来,不会出现多个独立进程。就我所看到的不少 Electron 应用其实都没有定制过。

以上是纯粹技术挖掘,没有破坏到迅雷的核心机密,仅做学习交流使用哈~

- 完 -

赠书福利
赠送新书《Vue.js框架与Web前端开发从入门到精通》共5本,刚刚上架!由「 北京大学出版社」赞助提供 ,感兴趣的朋友推荐入手一本。

本书从Vue.js框架技术的基础概念出发,逐步深入Vue.js进阶实战,并在最后配合一个网站项目和一个后台系统开发实战案例,重点介绍了使用Vue.js+axios+ElementUI+wangEditor进行前端开发和使用组件进行Vue单页面网页复用,让读者不但可以系统地学习Vue.js前端开发框架的相关知识,而且还能对业务逻辑的分析思路、实际应用开发有更为深入的理解。

同样这次准备了2种方式抽奖,「评论点赞、朋友圈点赞」这两种方式都可以参与!感谢亲爱的读者们,你们的支持也是我持续更文最大的动力。

本次开奖时间为 2022.3.20 14:00
为了避免中奖后失联,提前加我微信号:itcodexy 。

留言点赞(2本)

本文留言需要根据文章的内容留言会更大可能被精选,留言点赞数「第一、二名」可获得一本Vue.js框架与Web前端开发从入门到精通》

PS:买点赞数等作弊无效,一切解释权归程序IT圈所有,留言点赞之前中过奖的朋友,1年内不得再参与,给新朋友一些机会 !(经常发现点赞中奖的总是那些熟悉的面孔)

朋友圈点赞抽奖(3本)

记得先添加我微信,不然参加不了这个朋友圈活动 。大奖:本文章我会在下午6点左右转发朋友圈,给第n位(具体数值看朋友圈发布时的规则)点赞朋友圈的同学送出一本 《Vue.js框架与Web前端开发从入门到精通》,共3位。

明天见(。・ω・。)ノ♡

大神尝试扒迅雷的代码,竟然被扒了个精光!相关推荐

  1. java大神请出来_求java大神,请分析以下代码,写出执行结果,并解释每行结果输出的原因。...

    求java大神,请分析以下代码,写出执行结果,并解释每行结果输出的原因.classPlate{publicPlate(){System.out.println("inPlateconstru ...

  2. 卧槽,迅雷的代码结构被扒了精光

    背景 之前扒过飞书的源码,从代码设计架构层面里里外外学习一把,飞书还是挺 "大方" 的,源码在客户端和网页端都一览无余,不过好像新版本已经看不到了.相关的文章由于在内网技术论坛发过 ...

  3. 看完微软大神写的求平均值代码,我意识到自己还是too young了

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 博雯 发自 凹非寺 量子位 | 公众号 QbitAI 取整求个无符号整数的平均值,居然 ...

  4. 看完微软大神写的求平均值代码,我意识到自己还是 too young 了

    博雯 发自 凹非寺 量子位 | 公众号 QbitAI取整求个无符号整数的平均值,居然也能整出花儿来? 这不,微软大神Raymond Chen最近的一篇长文直接引爆外网技术平台,引发无数讨论: 无数人点 ...

  5. Google大神每天写多少行代码?真相让你吃惊!

    「 读者福利!2 TB 各类技术资源免费赠送 」 Quora上有个有趣的问题:Google工程师们每天写多少行代码? Google 的 AdMob 全栈工程师 Raymond Farias 在 Quo ...

  6. java杀毒软件_震宇大神的杀毒软件 (Java代码)

    解题思路: 注意事项: 参考代码: public class 震宇大神的杀毒软件_水到不能再水 { public static void main(String[] args) { // TODO A ...

  7. 大神碉堡!99 行代码实现的神奇效果

    code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群 边策 鱼羊 发自 凹非寺量子位 报道 | 公众号 QbitAI 只用99行代码,你也可以像<冰雪奇缘>里的 ...

  8. 大神程序员几行代码增粉 100W 最后入狱,附源码。

    背景 几行代码让他增粉 100W 最后入狱,他就是著名 Samy 蠕虫病毒的作者 Samy Kamkar,通过 Samy 蠕虫成功为自己新增 100W 粉丝最后入狱.这也是一个里程碑,世界上第一只 & ...

  9. DALL·E才发布两天就被复现?官方论文还没出,大神们就在自制代码和视频了

    萧箫 发自 凹非寺 量子位 报道 | 公众号 QbitAI 没想到,OpenAI刚公布DALL·E,就已经有人在复现了. 虽然还是个半成品,不过大体框架已经搭建好了,一位第三方作者Philip Wan ...

最新文章

  1. 为什么我们需要一门新语言——Go语言
  2. 深入理解学习Git常用工作流
  3. List集合的迭代器方法
  4. 每天一道LeetCode-----杨辉三角从第一行到最后一行的最小路径和
  5. bzoj1051[kosaraju算法]求强连通分量
  6. vue-router路由、mixin混入、vue-resource、axios、计算属性watch、moment.js、vuex、vue-cli、数据双向绑定、搭建vue环境、vue实例、配置启动项
  7. Java8 时区DateTime API
  8. linux系统各种日志存储路径和详细介绍
  9. Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析
  10. 愁绪千万端,扰乱不成眠——如何修复Noise?
  11. 求一天的起始和结束(时间戳)和一个月的第一天和最后一天
  12. ABP项目后台初始化数据库
  13. 基于Matlab的语音识别
  14. js监听只读文本框_javascript 监听文本框输入
  15. 远程接入Linux、unix、Windows工具-opentext ETX
  16. Imbalance data——数据不平衡问题
  17. 甲骨文CEO独家揭秘企业转型秘诀,就一个字
  18. (转)汇编bne的问题
  19. android 壁纸制作教程,[教程]怎样制作Android手机壁纸/桌面
  20. C语言99乘法口诀函数,99乘法口诀妙用C语言输出

热门文章

  1. 第十二周练兵区——编程题——不计入总分
  2. 微信小程序连锁店如何做
  3. CSS实验案例02简单专业介绍网页
  4. 深度学习之CNN卷积神经网络
  5. 小案例:基于python的动态时钟,带十二时辰和经络养身
  6. Python 文件处理
  7. 2009 Q.E.D
  8. 使用openpyxl 对Excel的简单操作
  9. 奈氏准则 香农定理 (有公式)
  10. 词根词缀sinu/sist/soci/sol/somn等词根衍生单词