标签 JavaScript 下的文章

为什么会有这个需求?

如果要追踪一个js中的变量变化,一般是在开发者工具的Sources选项卡中,对js下断点。
这其中有几个不爽的地方:

  1. js文件被压缩,不方便直接下断点,一般在格式化后下断点比较方便。
  2. js文件现在越来越大,本来浏览器就已经够占内存了,在Sources选项卡进入js并格式化常常需要等很久的时间,甚至直接没有响应。
  3. 在多个地方下断点不方便,以及有的地方下了断点也进不去。

因此本方案通过Gooreplacer插件重定向特定的js请求到本地js文件来解决上述问题烦心的点。
为了本地js能够返回特定的headers,选择通过重写SimpleHTTPRequestHandler来实现,同时保证浏览器顺利请求本地文件。

以获取西瓜视频中DRM解密用的key为例

自定义返回头脚本见此或文末。
获取西瓜视频DRM解密key的关键点
地址:

https://www.ixigua.com/cinema/album/7MzYdtWv46X_7MBDgA7bPWt/

  • 打开上述地址,F12后在Network过滤js文件关键词xgplayer_encrypt
  • 可以看到该js有一些特定的返回头

请输入图片描述

  • 首先编写一个如下形式的配置文件,由于我们格式化了js,这里要去掉content-encoding和content-length,命名为config.json,实际上不需要全部的头,只需要保证有access-control-allow-origin就行

请输入图片描述
精简版配置:

{
    "host": "127.0.0.1",
    "port": 22222,
    "scripts_path": "scripts",
    "vendors~xgplayer_encrypt.b05f677a.chunk.js": {
        "access-control-allow-origin": "*"
    }
}
  • 新建scripts文件夹,将vendors~xgplayer_encrypt.b05f677a.chunk.js放在scripts文件夹里面
  • 保存配置并执行cheat_server脚本,通过访问http://127.0.0.1:22222/vendors~xgplayer_encrypt.b05f677a.chunk.js可以看到返回头与设定的全部一致

请输入图片描述

  • 打开vendors~xgplayer_encrypt.b05f677a.chunk.js进行格式化,并在window.Module.UTF8ToString(p)前面加一句debugger;

请输入图片描述

  • 设定Gooreplacer插件重定向规则,并启用,注意不需要调试的时候记得关闭

请输入图片描述

  • 现在提前F12并刷新西瓜视频地址,等待自动进入debugger处

请输入图片描述

  • 现在愉快的拿到DRM解密用的key啦

cheat_server实现代码

完整cheat_server脚本见:
https://github.com/xhlove/cheat_server

#!/usr/bin/env python3.7
# coding=utf-8
'''
# 作者: weimo
# 创建日期: 2020-01-18 01:01:09
# 上次编辑时间: 2020-02-22 18:14:01
# 一个人的命运啊,当然要靠自我奋斗,但是...
'''
import os
import sys
import json
import chardet
import datetime
import email.utils
import urllib.parse
from http import HTTPStatus
from functools import partial
from http.server import HTTPServer, SimpleHTTPRequestHandler

def load_config():
    config = {}
    config_path = "config.json"
    if os.path.isfile(config_path):
        with open(config_path, "rb") as f:
            # 只读256是为了避免读取文件太大,虽然一般不会太大
            _encoding = chardet.detect(f.read(256))["encoding"]
        with open(config_path, "r", encoding=_encoding) as f:
            config = json.loads(f.read())
    return config

class MyHandler(SimpleHTTPRequestHandler):

    def __init__(self, *args, config: dict = {}, **kwargs):
        self.config = config
        kwargs["directory"] = os.path.join(os.getcwd(), config["scripts_path"])
        super().__init__(*args, **kwargs)

    def send_head(self):
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            parts = urllib.parse.urlsplit(self.path)
            if not parts.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(HTTPStatus.MOVED_PERMANENTLY)
                new_parts = (parts[0], parts[1], parts[2] + '/',
                             parts[3], parts[4])
                new_url = urllib.parse.urlunsplit(new_parts)
                self.send_header("Location", new_url)
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except OSError:
            self.send_error(HTTPStatus.NOT_FOUND, "File not found")
            return None

        try:
            fs = os.fstat(f.fileno())
            # Use browser cache if possible
            if ("If-Modified-Since" in self.headers
                    and "If-None-Match" not in self.headers):
                # compare If-Modified-Since and time of last file modification
                try:
                    ims = email.utils.parsedate_to_datetime(
                        self.headers["If-Modified-Since"])
                except (TypeError, IndexError, OverflowError, ValueError):
                    # ignore ill-formed values
                    pass
                else:
                    if ims.tzinfo is None:
                        # obsolete format with no timezone, cf.
                        # https://tools.ietf.org/html/rfc7231#section-7.1.1.1
                        ims = ims.replace(tzinfo=datetime.timezone.utc)
                    if ims.tzinfo is datetime.timezone.utc:
                        # compare to UTC datetime of last modification
                        last_modif = datetime.datetime.fromtimestamp(
                            fs.st_mtime, datetime.timezone.utc)
                        # remove microseconds, like in If-Modified-Since
                        last_modif = last_modif.replace(microsecond=0)

                        if last_modif <= ims:
                            self.send_response(HTTPStatus.NOT_MODIFIED)
                            self.end_headers()
                            f.close()
                            return None

            self.send_response(HTTPStatus.OK)
            # self.send_header("Content-type", ctype)
            # self.send_header("Content-Length", str(fs[6]))
            # self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
            self.send_custom_header()
            self.end_headers()
            return f
        except:
            f.close()
            raise

    def send_custom_header(self):
        if self.path.startswith("/"):
            js_path = self.path.lstrip("/")
        else:
            js_path = self.path
        if self.config.get(js_path) is None:
            return
        headers = self.config[js_path]
        for key, value in headers.items():
            self.send_header(key, value)

    def send_response(self, code, message=None):
        self.log_request(code)
        self.send_response_only(code, message)
        # self.send_header('Server', self.version_string())
        # self.send_header('Date', self.date_time_string())

    def send_header(self, keyword, value):
        if self.request_version != 'HTTP/0.9':
            if not hasattr(self, '_headers_buffer'):
                self._headers_buffer = []
            self._headers_buffer.append(
                ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))

        if keyword.lower() == 'connection':
            if value.lower() == 'close':
                self.close_connection = True
            elif value.lower() == 'keep-alive':
                self.close_connection = False

def main():
    config = load_config()
    Handler = partial(MyHandler, config=config)
    server = HTTPServer((config["host"], config["port"]), Handler)
    print("Starting server, listen at: http://{host}:{port}".format(**config))
    server.serve_forever()

if __name__ == '__main__':
    main()

好像很久没有公开文章了...其实还是有写几篇,今天看到别人的博客,突然觉得还是得发点什么证明这个博客没有被废弃!

本文中心:爱奇艺authkey算法

  • 下了断点调试,很快就知道这个参数计算的方法了,然后下面是代码
  • 传入有两个参数,一个是13位时间戳,一个是tvid
  • 实际参与计算还有一个authkey(""),这是个固定值
  • 用法是node authkey.js tm tvid
  • 然而...它其实就是对字符串进行了md5计算
  • 所以最简单的代码就是md5("d41d8cd98f00b204e9800998ecf8427e"+tm+tvid)
function a(e, t) {
    e[t >> 5] |= 128 << t % 32,
    e[14 + (t + 64 >>> 9 << 4)] = t;
    for (var i = 1732584193, a = -271733879, n = -1732584194, u = 271733878, c = 0; c < e.length; c += 16) {
        var f = i
          , p = a
          , h = n
          , _ = u;
        i = r(i, a, n, u, e[c + 0], 7, -680876936),
        u = r(u, i, a, n, e[c + 1], 12, -389564586),
        n = r(n, u, i, a, e[c + 2], 17, 606105819),
        a = r(a, n, u, i, e[c + 3], 22, -1044525330),
        i = r(i, a, n, u, e[c + 4], 7, -176418897),
        u = r(u, i, a, n, e[c + 5], 12, 1200080426),
        n = r(n, u, i, a, e[c + 6], 17, -1473231341),
        a = r(a, n, u, i, e[c + 7], 22, -45705983),
        i = r(i, a, n, u, e[c + 8], 7, 1770035416),
        u = r(u, i, a, n, e[c + 9], 12, -1958414417),
        n = r(n, u, i, a, e[c + 10], 17, -42063),
        a = r(a, n, u, i, e[c + 11], 22, -1990404162),
        i = r(i, a, n, u, e[c + 12], 7, 1804603682),
        u = r(u, i, a, n, e[c + 13], 12, -40341101),
        n = r(n, u, i, a, e[c + 14], 17, -1502002290),
        a = r(a, n, u, i, e[c + 15], 22, 1236535329),
        i = o(i, a, n, u, e[c + 1], 5, -165796510),
        u = o(u, i, a, n, e[c + 6], 9, -1069501632),
        n = o(n, u, i, a, e[c + 11], 14, 643717713),
        a = o(a, n, u, i, e[c + 0], 20, -373897302),
        i = o(i, a, n, u, e[c + 5], 5, -701558691),
        u = o(u, i, a, n, e[c + 10], 9, 38016083),
        n = o(n, u, i, a, e[c + 15], 14, -660478335),
        a = o(a, n, u, i, e[c + 4], 20, -405537848),
        i = o(i, a, n, u, e[c + 9], 5, 568446438),
        u = o(u, i, a, n, e[c + 14], 9, -1019803690),
        n = o(n, u, i, a, e[c + 3], 14, -187363961),
        a = o(a, n, u, i, e[c + 8], 20, 1163531501),
        i = o(i, a, n, u, e[c + 13], 5, -1444681467),
        u = o(u, i, a, n, e[c + 2], 9, -51403784),
        n = o(n, u, i, a, e[c + 7], 14, 1735328473),
        a = o(a, n, u, i, e[c + 12], 20, -1926607734),
        i = s(i, a, n, u, e[c + 5], 4, -378558),
        u = s(u, i, a, n, e[c + 8], 11, -2022574463),
        n = s(n, u, i, a, e[c + 11], 16, 1839030562),
        a = s(a, n, u, i, e[c + 14], 23, -35309556),
        i = s(i, a, n, u, e[c + 1], 4, -1530992060),
        u = s(u, i, a, n, e[c + 4], 11, 1272893353),
        n = s(n, u, i, a, e[c + 7], 16, -155497632),
        a = s(a, n, u, i, e[c + 10], 23, -1094730640),
        i = s(i, a, n, u, e[c + 13], 4, 681279174),
        u = s(u, i, a, n, e[c + 0], 11, -358537222),
        n = s(n, u, i, a, e[c + 3], 16, -722521979),
        a = s(a, n, u, i, e[c + 6], 23, 76029189),
        i = s(i, a, n, u, e[c + 9], 4, -640364487),
        u = s(u, i, a, n, e[c + 12], 11, -421815835),
        n = s(n, u, i, a, e[c + 15], 16, 530742520),
        a = s(a, n, u, i, e[c + 2], 23, -995338651),
        i = d(i, a, n, u, e[c + 0], 6, -198630844),
        u = d(u, i, a, n, e[c + 7], 10, 1126891415),
        n = d(n, u, i, a, e[c + 14], 15, -1416354905),
        a = d(a, n, u, i, e[c + 5], 21, -57434055),
        i = d(i, a, n, u, e[c + 12], 6, 1700485571),
        u = d(u, i, a, n, e[c + 3], 10, -1894986606),
        n = d(n, u, i, a, e[c + 10], 15, -1051523),
        a = d(a, n, u, i, e[c + 1], 21, -2054922799),
        i = d(i, a, n, u, e[c + 8], 6, 1873313359),
        u = d(u, i, a, n, e[c + 15], 10, -30611744),
        n = d(n, u, i, a, e[c + 6], 15, -1560198380),
        a = d(a, n, u, i, e[c + 13], 21, 1309151649),
        i = d(i, a, n, u, e[c + 4], 6, -145523070),
        u = d(u, i, a, n, e[c + 11], 10, -1120210379),
        n = d(n, u, i, a, e[c + 2], 15, 718787259),
        a = d(a, n, u, i, e[c + 9], 21, -343485551),
        i = l(i, f),
        a = l(a, p),
        n = l(n, h),
        u = l(u, _)
    }
    return Array(i, a, n, u)
}
function n(e, t, i, a, n, r) {
    return l(u(l(l(t, e), l(a, r)), n), i)
}
function r(e, t, i, a, r, o, s) {
    return n(t & i | ~t & a, e, t, r, o, s)
}
function o(e, t, i, a, r, o, s) {
    return n(t & a | i & ~a, e, t, r, o, s)
}
function s(e, t, i, a, r, o, s) {
    return n(t ^ i ^ a, e, t, r, o, s)
}
function d(e, t, i, a, r, o, s) {
    return n(i ^ (t | ~a), e, t, r, o, s)
}
function l(e, t) {
    var i = (65535 & e) + (65535 & t);
    return (e >> 16) + (t >> 16) + (i >> 16) << 16 | 65535 & i
}
function u(e, t) {
    return e << t | e >>> 32 - t
}
function c(e) {
    for (var t = Array(), i = (1 << h) - 1, a = 0; a < e.length * h; a += h)
        t[a >> 5] |= (e.charCodeAt(a / h) & i) << a % 32;
    return t
}
function f(e) {
    for (var t = p ? "0123456789ABCDEF" : "0123456789abcdef", i = "", a = 0; a < 4 * e.length; a++)
        i += t.charAt(e[a >> 2] >> a % 4 * 8 + 4 & 15) + t.charAt(e[a >> 2] >> a % 4 * 8 & 15);
    return i
}
var p = 0
  , h = 8;
function authkey(e) {
    return f(a(c(e), e.length * h))
}
// console.log(authkey(authkey("")+"1558939218094"+"2494954900"))
var args = process.argv.slice(2);
tm = args[0]
tvid = args[1]
console.log(authkey("d41d8cd98f00b204e9800998ecf8427e" + tm + tvid));