自定义百度网盘分享密码
Author: JieJiSS
在百度网盘里私密分享文件的时候,百度网盘会给你生成一个随机四位数字和大小写英文字母混杂的密码作为分享密码。稍微分析一下在点击分享按钮之后的网络请求,就会发现密码是本地生成好了之后再发送给服务器留存。既然是本地生成的,就给了我们可调整的余地。只要发送给服务器的密码长度为 4,服务器并不能判断出这个密码是不是随机生成的——任何一个四位密码都有可能是随机生成的,不论它看上去多么不随机(例如:1234,mdzz)。
0x00 架构分析
百度网盘作为一个大型的 Web APP,必然有着较为完整的项目模块化规范,否则极高的耦合将导致每次迭代都异常艰难。稍微在 Chrome 的开发者工具的 Source 栏里搜索一下几种浏览器端常用的模块化方法的关键词,可以发现源代码里出现了很多 define
和 require
,例如登录后主页面第 198 行:
1 | var yunData = require('disk-system:widget/data/yunData.js'); |
和第 204 行:
1 | var context = require('system-core:context/context.js').instanceForSystem; |
看上去像是 requireJS
的路子,不过是不是的都不影响,知道怎么用就行了。我们来看一下它的目录结构:
这里的文件夹名称 disk-system
、system-core
是不是看上去有点眼熟?
再往下展开试试:
找到了之前看到的 require
的函数定义,轻松愉快有木有
0x01 定位密码生成函数
首先想到的是模拟一次分享,看看有哪些函数被调用了。结果一路下断点,跟到了一个事件钩子池里(
就是说,它把所有的注册的事件和对应的 handler 都搁到了一个大数组里,对接受到的每个事件都依次判断一下这些 handler 的触发条件是否满足,如果满足就调用。往里跟了两三层,不想接着跟了……倒也不是跟不了,之前研究学校的综评系统的时候一个 click
事件跟了十几层,最后还是把注册的处理函数给揪出来了(
于是打算另辟蹊径:上百度搜搜有没有别人干过类似的事情(逃
这叫:用百度家的产品来搞百度家的产品,真是一点负罪感也没有啊 2333333
搜到了这里有一篇教程,它提供的代码是:
1
2 var Share = require(["function-widget-1:share/util/service/createLinkShare.js"]);
Share.prototype.makePrivatePassword = function () { return "1234"; };
放到百度网盘页面的 Chrome Inspector's Console 里执行,不出意外地报错了。(要是不报错,哪来的这篇博文呢)
看一眼报的错是什么:
1 | Uncaught Error: Cannot find module `function-widget-1:share/util/service/createLinkShare.js` |
很明显,网上资料里给的模块路径跟实际所在的路径已经不一样了,require
函数根据给出的路径找不到对应的模块。我瞅了一眼那篇博文的发表时间,2017 年 4 月份……两年前的了,路径变了也正常。但是至少 function-widget-1
这个文件夹还在(参见上文插图),所以先从这个文件夹入手。点开之后发现目录结构如下:
1 | + function-widget-1/pkg |
从文件名来判断的话,头两个文件分别负责异步操作和同步操作,那么看上去创建分享链接这种需要和服务器通信的就应该被归类到负责异步的 async-all
里去了。打开 async-all_<hash>.js
一搜:
于是利用 Chrome 提供的 beautify 功能格式化一下代码,稍微阅读一下它 define
的东西,就在 createPrivateShareLink
函数里定位到了最终的密码生成函数 makePrivatePassword
:
在第 2626 行下个断点,实际操作一次,在断点处就可以跟到 makePrivatePassword
的定义了。
最终的模块路径是 function-widget-1:share/util/shareFriend/createLinkShare.js
。
0x02 Hook 密码生成函数
光是知道这个 makePrivatePassword
在哪没用,我们还得想办法能控制它返回的函数值,这样才能实现自定义密码这个需求。再看一眼函数定义(已经进行了一定处理,方便阅读),
1 | makePrivatePassword: function() { |
于是自然而然的想到可以魔改 Math.random
,从而控制 makePrivatePassword
:
1 | let i = 0, indexOfChars = [0, 1, 2, 3]; |
但是这样的话,首先有一点问题就是原本的 chars
数组(改名之后)就不包括 "l"
、"o"
、"0"
(我猜测是因为 l
容易和 1
搞混、o
、0
互相容易搞混),所以魔改后还是没法用这三个字符,有点不完美;其次,这样 tricky 的操作封装起来比较麻烦,百度网盘的程序员哪天改了 makePrivatePassword
的实现的话,封装的函数还得跟着变。很差劲。
所以得另辟蹊径。翻回来看看 createPrivateShareLink
的实现:
1 | createPrivateShareLink: function(e, i) { |
发现了一个很有意思的地方:makePrivatePassword
是 t
的属性,而 t
指向了 this
!换言之,我们直接修改
1 | require(["path/to/module"]).makePrivatePassword |
无法覆盖所有的 makePrivatePassword
(因为这只是 require
出来的一个实例),但是我们可以修改:
1 | require(["path/to/module"]).prototype.makePrivatePassword |
相当于覆盖掉原型链上的函数定义,那么以后每次 require
出来的实例在调用 makePrivatePassword
时就会调用我们的函数,完美实现覆盖声明。(这个时候就可以理解为什么刚才在百度上搜到的教程要修改 prototype
了)
最终成型的、封装好的函数如下:
1 | /** |