• 有一个需求,检测移动云盘分享链接是否有效,然后自己创建了一个链接,后分享取消,我们来到了这个页面
  • 示例代码链接
  • 顺便吐槽一下
    • 移动云盘你也是,加密是做啥子,你关键的接口加密我没意见,比如文件列表获取,你这个分享链接有效期也加密..,有这功夫提升下服务多号,给程序员加个鸡腿也好,也没见多少人用啊……

解密逻辑

  • 示例代码链接
  • 刚开始我想着和其他一样,也是通过调用接口来获取链接状态,然后找啊找
    • 发现请求响应的除了一些乱七八糟的数据,感觉也没有什么有用的东西,之前几个网盘都有什么message为分享取消等之类的消息

  • 然后搜索关键字符串也没发现什么

  • 然后直接请求get,会发现响应的是一个无html代码的网页,也就是SFC(单文件组件)常用的做法

  • 知道了是单文件,然后我们看下怎么渲染数据上去的,因为都是数据驱动视图渲染
  • 然后搜索怎么渲染上去的
    • 技巧,==$0就指代了这个对象,可以直接在控制台通过$0操控
    • 我们要使用console.dir来查看

  • 然后找到了errormsg,也就是通过errormsg字段渲染上去的
    • errormsg肯定是通过请求数据状态获取到的,不可能是本地的,除非你是SSR服务器渲染好了存在文件中

  • 我们ctrl+shift+f全局代码搜索下

  • 不断的断点,断点,来到了这里

  • 我们就来到了解密逻辑定位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//后面有r.a.AES.decrypt用到了一个变量D,这个j函数没有
//滚动条拉到上面就找到了
var D = r.a.enc.Utf8.parse("PVGDwmcvfs1uV3d1");
function j(e) {
var t = r.a.enc.Base64.parse(e)
, n = t.clone()
, i = n.words.splice(4);
n.init(n.words),
t.init(i);
var o = r.a.enc.Base64.stringify(t)
, a = r.a.AES.decrypt(o, D, {
iv: n,
mode: r.a.mode.CBC,
padding: r.a.pad.Pkcs7
})
, s = a.toString(r.a.enc.Utf8);
return s.toString()
}
  • 测试看是不是,可以看到,数据从一个不可阅读的变到了一个可阅读的

  • 这种加密一看就是cryptojs,我们使用nodejs仿写一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var CryptoJS = require("crypto-js");

const text =
"DqZaLkcdO3f5vgWsUsS65mSBPxq7ZbLf1IdBveR2MbkGnT9kBZ57+vhTr/imVX76Y+rj/7poXCPSf4L0JPEiVFsg1K3kNsVfaqCDqAMqTsp9NUWyUaivaXf3I05LGoDHGA3WEhMUZtBeMfuhrZni2Zywm/bejF52uUVg6t/KtbMMMBRrQRCcQrrEK/JvEHmZtVMC127kSG88TZOQ90qFwYk+Dz8mHcU0MvQpggdoSC788UgaXWlVZ/9EMwseLbpxs0YjZtR1lJyEb9JjMSQ3MjFTlOzoCxpf5AYhqVPd3N1tYsSzfCMhEl1YNRz9El++ev+Xk/05wi0DOVgQYxMiaXxpOxhgarBP+N9KDGoYctwIzAPqyh1wny1CPNv6sJ56";

try {
var D = CryptoJS.enc.Utf8.parse("PVGDwmcvfs1uV3d1");
const t = CryptoJS.enc.Base64.parse(text),
n = t.clone(),
i = n.words.splice(4);
n.init(n.words);
t.init(i);
var o = CryptoJS.enc.Base64.stringify(t);

var a = CryptoJS.AES.decrypt(o, D, {
iv: n,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
console.log(a.toString(CryptoJS.enc.Utf8));
} catch (e) {
console.error("解密过程出错:", e.message);
}

  • 解密成功

  • resultCode也就是国际化的东东,一个编号啥的,不用特别在意

加密逻辑

  • 断点

  • 刷新页面,断点来到了这里

  • 跳过下一个函数调用,来到了这里,很明显是一个请求拦截器,我们下断点,我们只需要getOutLinkInfoV6请求的即可,其他url的不需要看(因为貌似他们不同的url还设置了不同加密逻辑….)

  • 我们来看看闭包里面的数据,看看能不能发现什么,可以看到,目前处理的url请求是/platformInfo/advertapi/adv-filter/adv-filter/AdInfoFilter/getAdInfos我们跳过,我们只看getOutLinkInfoV6

  • 好,我们断点来到了这,可以看到里面的data数据还是原始的数据,并没有被加密

  • 查看下,发现data到了这里就改变了,变成了加密的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


{
"getOutLinkInfoReq": {
"account": "",
"linkID": "035Cgf2N6FFR6",
"passwd": "",
"caSrt": 0,
"coSrt": 0,
"srtDr": 1,
"bNum": 1,
"pCaID": "root",
"eNum": 200
}
}
变为了加密后的数据

  • 跳转查看下jt["h"]函数干了什么

  • 跳转到了下图所示位置

  • 好了,这样子就知道加密了,你丢给gpt问一问就好了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var D = r.a.enc.Utf8.parse("PVGDwmcvfs1uV3d1");
function I(e) {
var t = r.a.lib.WordArray.random(16)
, n = "";
if ("string" == typeof e) {
var o = r.a.enc.Utf8.parse(e);
n = r.a.AES.encrypt(o, D, {
iv: t,
mode: r.a.mode.CBC,
padding: r.a.pad.Pkcs7
})
} else if ("object" == Object(i["a"])(e)) {
var a = JSON.stringify(e)
, s = r.a.enc.Utf8.parse(a);
n = r.a.AES.encrypt(s, D, {
iv: t,
mode: r.a.mode.CBC,
padding: r.a.pad.Pkcs7
})
}
return r.a.enc.Base64.stringify(t.concat(n.ciphertext))
}
  • 里面还有一个i["a"],发现是下面图的内容

  • 丢给gpt重写下,自己懒得写了
    • 就是获取类型的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getType(t) {
const isSymbolSupported = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol';
if (isSymbolSupported) {
return typeof t;
} else {
return t && typeof Symbol === 'function' && t.constructor === Symbol && t !== Symbol.prototype ? 'symbol' : typeof t;
}
}

示例
console.log(getType('hello')); // 'string'
console.log(getType(123)); // 'number'
console.log(getType(true)); // 'boolean'
console.log(getType(null)); // 'object'
console.log(getType(undefined)); // 'undefined'
console.log(getType(Symbol('foo'))); // 'symbol'

  • 用nodejs重新书写下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const CryptoJS = require("crypto-js");
//加密内容
const data = {
getOutLinkInfoReq: {
account: "",
linkID: "035Cgf2N6FFR6",
passwd: "",
caSrt: 0,
coSrt: 0,
srtDr: 1,
bNum: 1,
pCaID: "root",
eNum: 200,
},
};
const r = {
a: CryptoJS,
};

const i = {
a: function getType(t) {
const isSymbolSupported =
typeof Symbol === "function" && typeof Symbol.iterator === "symbol";
if (isSymbolSupported) {
return typeof t;
} else {
return t &&
typeof Symbol === "function" &&
t.constructor === Symbol &&
t !== Symbol.prototype
? "symbol"
: typeof t;
}
},
};

var D = CryptoJS.enc.Utf8.parse("PVGDwmcvfs1uV3d1");
function I(e) {
var t = r.a.lib.WordArray.random(16),
n = "";
if ("string" == typeof e) {
var o = r.a.enc.Utf8.parse(e);
n = r.a.AES.encrypt(o, D, {
iv: t,
mode: r.a.mode.CBC,
padding: r.a.pad.Pkcs7,
});
} else if ("object" == Object(i["a"])(e)) {
var a = JSON.stringify(e),
s = r.a.enc.Utf8.parse(a);
n = r.a.AES.encrypt(s, D, {
iv: t,
mode: r.a.mode.CBC,
padding: r.a.pad.Pkcs7,
});
}
return r.a.enc.Base64.stringify(t.concat(n.ciphertext));
}
const result = I(data);
console.log("加密结果", result);

  • 运行

  • 测试正常

  • 链接失效基本上是返回这个,当然不排除其他的,什么资源违规啥的
1
2
3
4
5
6
7
{
"resultCode": "200000727",
"desc": "[501237|D181AA79D09743338F661AC067B56EE2]Failed to invoke the service.The link[035CtYLTLHFdT] does not exist or has been canceled[NDA(100556)][Flag:D181AA79D09743338F661AC067B56EE2]",
"data": null,
"code": "200000727",
"success": true
}
  • 因为移动云盘分享链接强制提取码(移动端没试过),所以应该可以使用下面状态来判断有效
1
2
3
4
5
6
7
{
"resultCode": "9188",
"desc": "[501273|4C4E7986B7E0453999D5A65634ED8DD7]Failed to invoke the service.wrong password for outlink, linkID=035CtYLTLHFdT[NDA(100576)][Flag:4C4E7986B7E0453999D5A65634ED8DD7]",
"data": null,
"code": "9188",
"success": true
}