为什么会有这个需求?
如果要追踪一个js中的变量变化,一般是在开发者工具的Sources选项卡中,对js下断点。
这其中有几个不爽的地方:
- js文件被压缩,不方便直接下断点,一般在格式化后下断点比较方便。
- js文件现在越来越大,本来浏览器就已经够占内存了,在Sources选项卡进入js并格式化常常需要等很久的时间,甚至直接没有响应。
- 在多个地方下断点不方便,以及有的地方下了断点也进不去。
因此本方案通过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()
刷新后进入Debugger模式,然后视频界面一直处于加载状态,脚本运行提示
Starting server, listen at: http://127.0.0.1:22222
127.0.0.1 - - [18/May/2020 17:04:57] "GET /vendors~xgplayer_encrypt.9764c929bf.chunk.js HTTP/1.1" 200 -
127.0.0.1 - - [18/May/2020 17:04:57] code 404, message File not found
127.0.0.1 - - [18/May/2020 17:04:57] "GET /sourcemap/static/js/vendors~xgplayer_encrypt.9764c929bf.chunk.js.map HTTP/1.1" 404 -
旧的js已经不适用了,建议按前一条的评论下那样直接下断点,亦或是使用新的js文件,修改并重定向。
博主你好,请问这个vendors~xgplayer_encrypt.b05f677a.chunk.js是否已失效?我尝试着下这个电影的时候得到的js文件为vendors~xgplayer_encrypt.9764c929bf.chunk.js,然后里面的window.Module.UTF8ToString(p)已经变成this.Module.UTF8ToString(p),所以无法得到这个key了,请问有其他办法吗?
你直接在这里下断点,运行到这里不能转换得到key吗?你可以试试重定向这个js到文章中这个版本。
我试过了还是不行,我把这个巨额交易的https://s3.pstatp.com/toutiao/xigua_video_web_pc/static/js/vendors~xgplayer_encrypt.9764c929bf.chunk.js重定向到文章里的http://127.0.0.1:22222/vendors~xgplayer_encrypt.b05f677a.chunk.js,用F5刷新后地址并没有改变还是网络上的那个js而不是我本地的,如果直接把9764c929bf的重定向到9764c929bf里的话,把这个js格式化之后在this.Module.UTF8ToString(p)前面加上debugger;的话断点之后是不会显示key的,大佬如果你方便的话可以试下这个方法现在还能用可以吗?我还看了nilaoda大神的这个文章https://github.com/nilaoda/Blog/issues/17,下面也有人提到window换成this了,说不好直接调用了,小白接下来就很迷茫了
http://puui.qpic.cn/vshpic/0/d9ODCvU9KvybZN7Oq72f4Bv5ZY2lLUzsExI8o407gMOs8DjC_0/0
看起来可能是随机选择一个js加载的,我测试到加载9764c929bf这个js的时候也是能拿到key的。
看起来没有问题,在这里下断点会看到key。
不过我这里加载的js是https://s3.pstatp.com/toutiao/xigua_video_web_pc/static/js/vendors~xgplayer_encrypt.8adba0381a.chunk.js
http://pan.iqiyi.com/ext/paopao/?token=eJxjYGBgmBQtsZUBBE78WgwAFw0EIA.jpg
下了断点记得刷新页面(Network处选上Disable Cache)。
至于你上面重定向没有成功,可能是匹配规则的问题,另外不知道你cheat_server是不是正常运行了。
好的,感谢感谢~~~
这个问题我不清楚。
谢谢博主!!!我成功了,得到key了和你一样的,太开心啦~然后得到key之后是用https://github.com/nilaoda/Blog/issues/17中的下载分片那一步吗?我过滤mpd里面空的,找不到链接,请问再要怎么操作呢?
你好,如何新建scripts呢,看不懂这一步,请求大佬详细点,实在感谢
整个过程是这样的,python开一个http服务器,返回修改后的js文件,浏览器插件重定向特定的js地址到python列目录的js地址。这样浏览器就能加载修改后的js,类似于fiddler的修改响应体。
在chrome的source界面进入你要改变的js,然后格式化,全选复制内容,在本地script文件夹内新建js文件,粘贴内容。然后配置config.json文件,运行程序,配置浏览器插件。刷新网页。
感谢热心解答
好可爱干净的web