背景

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

一不小心发现迅雷的客户端竟然也是基于 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 应用其实都没有定制过。

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




最近熬夜给大家准备了非常全的一套Java一线大厂面试题。全面覆盖BATJ等一线互联网公司的面试题及解答,由BAT一线互联网公司大牛带你深度剖析面试题背后的原理,不仅授你以鱼,更授你以渔,为你面试扫除一切障碍。

 ps:资料已整理成文档,需要获取的小伙伴可以直接转发+关注后私信(学习)即可获取哦!

卧槽,迅雷的代码结构被扒了精光相关推荐

  1. 卧槽!迅雷的代码结构竟然被扒了精光!

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

  2. 我擦!迅雷的代码结构竟然被扒了精光~

    作者:jiawen 链接:juejin.im/post/6890344584078721031 # 背景 之前扒过飞书的源码,从代码设计架构层面里里外外学习一把,飞书还是挺"大方" ...

  3. YYDS!迅雷的代码结构,竟然被大佬“扒了精光”!

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜留言必回,有问必答! 每天 08:15 更新文章,每天进步一点点... ...

  4. 大神尝试扒迅雷的代码,竟然被扒了个精光!

    欢迎大家关注 来源:juejin.im/post/6890344584078721031 文末送书5本 背景 之前扒过飞书的源码,从代码设计架构层面里里外外学习一把,飞书还是挺 "大方&qu ...

  5. UE4 Chaos代码结构剖析

    一.代码结构 Chaos的核心代码存储在Source/Runtime/Experimental中 而一些之外的代码都是以插件的形式存在于引擎之中.InteractiveToolsFramework并不 ...

  6. 最全Pycharm教程(43)——Pycharm扩展功能之UML类图使用 代码结构

    版权声明:本文为博主原创文章,转载时麻烦注明源文章链接,谢谢合作 https://blog.csdn.net/u013088062/article/details/50353202 1.什么是UML ...

  7. 微服务实战之春云与刀客(三)—— 面向接口调用代码结构实例

    2019独角兽企业重金招聘Python工程师标准>>> 概述 在上一篇中提到了spring cloud 面向接口调用的开发风格,这一篇会举一个简单的但完整的例子来说明整个代码结构. ...

  8. Python外壳:代码结构!(IF WHILE FOR RANGE...)

    #:octothorpe 书中说它是八脚怪物!哈哈! 注释就要在前面加上一个#符号! 在print语句里,恢复正常的符号功能! \连接符号: 这个样子就不会一行输入很多的东西,读起来不舒服!功能也是一 ...

  9. [转]VSTO Office二次开发RibbonX代码结构

    前段时间,碰到对于PPT中控制一些命令的问题,也是很是查找了不少的资料,最后使用的是RibbonX的形式解决的,发现RibbonX也是如此的高效. 文章来自:<细品RibbonX(9):层次分明 ...

最新文章

  1. Redis笔记(一)Redis简介
  2. 微软官方的.NET Framework API 参考网址
  3. 新疆计算机一级考试试题视作题,2014新疆计算机一级考试试题汇总
  4. 对话图灵奖得主、CNN之父Yann LeCun:我在中国看到了AI研究热潮
  5. SAXParseException An invalid XML character 问题的解决
  6. 格雷希尔Gripseal燃油管快速接头如何做密封性测试
  7. libtorrent编译
  8. 在php中使用高德api,javascript - 高德地图定位如何调用api?
  9. OCCT v11.0.16 x64 电脑硬件检测烤鸡软件中文
  10. 如何将LaTeX公式拷贝到Word中
  11. 解决 Hyper-V R2 虚拟网卡影响网速变慢问题
  12. 信息熵、相对熵和交叉熵
  13. 微信公众号+获取文章内容【只是记录自己的学习过程】
  14. Java解析HL7消息进阶(解析自定义HL7消息)
  15. linux c++11高性能协程库netco
  16. 转自kuangbin的AC自动机(赛前最后一博)
  17. MFC属性页CPropertySheet的使用
  18. 关于 Intel Realsense 深度图像处理.1(C++)
  19. 安卓开发资料大集合,很多都是51CTO中的推荐材料,值得学习
  20. 开屏广告=让用户等?小红书如何兼顾用户体验和广告投放效果

热门文章

  1. 024_spacemacs支持org-pomodoro的声音提示
  2. 如何科学评估疫情对业务的影响?
  3. 《机器学习 公式推导与代码实现》随书PPT示例
  4. scheduled一分钟执行一次_Spring中使用@Scheduled创建定时任务
  5. php m403n安装错误,HP LaserJet M403n驱动
  6. python 发票信息提取_Python提取发票内容保存到Excel.md
  7. larval框架的获取并存储(cache的使用)
  8. Vue进阶(幺贰叁):v-for 实现一行展示 n 个元素
  9. c语言中函数名可不可以由用户命名,C语言中变量名及函数名的命名规则与驼峰命名法...
  10. 手机设备唯一标识相关概念