中国移动云盘链接有效期加解密分析

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

解密逻辑

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

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

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

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

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

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

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

  • 我们就来到了解密逻辑定位
//后面有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仿写一下
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到了这里就改变了,变成了加密的
{
    "getOutLinkInfoReq": {
        "account": "",
        "linkID": "035Cgf2N6FFR6",
        "passwd": "",
        "caSrt": 0,
        "coSrt": 0,
        "srtDr": 1,
        "bNum": 1,
        "pCaID": "root",
        "eNum": 200
    }
}
变为了加密后的数据

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

  • 跳转到了下图所示位置

  • 好了,这样子就知道加密了,你丢给gpt问一问就好了
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重写下,自己懒得写了
    • 就是获取类型的
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重新书写下
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);

  • 运行

  • 测试正常

  • 链接失效基本上是返回这个,当然不排除其他的,什么资源违规啥的
{
    "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
}
  • 因为移动云盘分享链接强制提取码(移动端没试过),所以应该可以使用下面状态来判断有效
{
    "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
}

示例代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未成年梦想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值