赞
踩
网址:https://live.leisu.com/wanchang
可以看到这个比分是使用canvas绘制上去的。
首先了解下canvas
是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素.例如,它可以用于绘制图表、制作图片构图或者制作简单的(以及不那么简单的)动画.
主要了解下 canvas绘制文本
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_text
看了上面的简单的教程,姑且粗略地认为绘制文本前需要提供文本。
例如绘制hello world
则需要提供hello world
这个字符串。
在下方链接体验canvas文本绘制
通过上面的了解,我们只需要找到数据在哪里就可以了。
F12查看network,浏览一遍下来没有发现明显的数据交互请求。全是js、css、png
等静态文件。
查看html源代码,也没有明显的数据。
html中的canvas的标签,js需要定位到canvas标签才继续操作,所以我们可以试着全局搜索canvas
关键字。
F12打开审查工具,Ctrl+Shift+F
全局搜索
一一查看搜索结果,查看到最后一个js
文件(match_layout_wc_xxx.js
),明显这个js
代码被混淆了。一般可以理解为,开发者只会对关键代码进行混淆。
说明这个文件的代码不想给其他人看。
继续往下看,可以看到这明显是Vue框架的写的。这个文件是一个Vue写的子组件。
当这个组件被挂载时,将执行this[_x116622[80]]()
,即调用this[_x11622[80]]
方法。
对这个位置打上断点,刷新网页,在此断点后查看_x11622[80]
所以,这个组件被挂载时,即执行this.drwaBase()
这个this.drwaBase
方法在哪里呢,Vue的方法统一定义在methods
内,或者在这个js文件里搜搜看。
我们把断点打在drwaBase
方法里的开头和结尾,让其跑完整个drwaBase
方法。
可以看到每跑一次drwaBase
方法,界面上就有一行数据显示出来。
在结合drwaBase
方法没有参数,所以在挂载这个组件前,数据就已经加载出来了。
所以,我需要找到数据何时被初始化。
跟着调用栈往上找。
在wanchang-xxxx.js
的文件中找到函数名为initData
方法,这个引起了我们的注意。可以看到有一个明显的JSON
, 在这一行上打断点。刷新开始调试。
所以,这句代码原本的样子可以还原为:
let _ = JSON.parse($.rot(base64_, STATIC_CONFIG.KST))
其中STATIC_CONFIG.KST
根据多次调试,这个值一直是整数6
let _ = JSON.parse($.rot(base64_, 6))
此时,我们接着看下_
是什么,展开其中一个数组,可以看到这就是我们所需的数据。
那么现在,我们的首要目的就是找到base64_
变量是什么时候赋值的以及$.rot
方法是什么。
在Console
里输入$.rot
在Console
返回的代码是实际执行的。也就是:
$.rot = function (t, e) {
const i = roott(t, e);
return pushmsg(i)
}
点击返回内容可以直接跳转到此方法。
function(t) {
try {
let e = ["t", "ro"];
if (!t || !window.LeisuJS)
return;
t[e[1] + e[0]] = function(t, e) {
const i = roott(t, e);
return pushmsg(i)
}
} catch (t) {
"prod" != STATIC_CONFIG.NODE_ENV && console.error(t)
}
}(jQuery),
简单的分析,传了一个Jquery
进去
然后对其设置了一个方法
t[e[1] + e[0]] = function(t, e) {
const i = roott(t, e);
return pushmsg(i)
}
替换一下就是:
t.rot = function(t, e) {
const i = roott(t, e);
return pushmsg(i)
}
可以看到这就是实际执行的代码。
那么到分析到现在整体流程有个大概的雏形了。
rot
方法;roott
和pushmsg
方法的处理,最终得到有效数据接着我们使用同样的方法找roott
与pushmsg
roott
e.roott = function(t, e) {
for (var i = "", n = 0; n < t.length; n++) {
var a = t.charCodeAt(n)
, o = a;
a >= 65 && a <= 90 && (o = (a - 65 - 1 * e + 26) % 26 + 65),
a >= 97 && a <= 122 && (o = (a - 97 - 1 * e + 26) % 26 + 97),
i += String.fromCharCode(o)
}
return i
}
pushmsg
e.pushmsg = function(t) { let e = ""; if ("undefined" == typeof window) try { e = nodeAtob(t) } catch (t) { console.log(t) } else e = atob(t); const i = e.split("").map(function(t) { return t.charCodeAt(0) }) , n = new Uint8Array(i) , a = pako.inflate(n); return e = function(t) { let e, i, n, a, o = ""; const r = t.length; for (e = 0; e < r; ) switch ((i = t[e++]) >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: o += String.fromCharCode(i); break; case 12: case 13: n = t[e++], o += String.fromCharCode((31 & i) << 6 | 63 & n); break; case 14: n = t[e++], a = t[e++], o += String.fromCharCode((15 & i) << 12 | (63 & n) << 6 | (63 & a) << 0) } return o }(new Uint16Array(a)), unescape(e) } }(),
roott
可以很容易理解,pushmsg
刚开始也能看懂直到pako.inflate(n);
进去看了下这个inflate
,有点复杂不知道这一步是在干什么。
这看着有点像调用其他的工具包,于是我去搜索pake.inflate
一个解压库,将数据解压就可以用到这个pako
base64_
已经是处于加密状态,是一个非常长的字符串,有理由相信实际返回的原始数据就是这个超长字符串。我们的拿出字符串的前几位去全局搜索。
看了下base64_被执行赋值操作的js文件是wc-xxxx.js
然后简单调试发现,请求这个js文件时,无需携带cookie,所以尝试直接请求这个js
文件,成功获取响应。
base64_
是一个全局变量,如果我们能在base64_
被赋值的那一瞬间进入debug状态,然后插卡调用栈即可看到window.base64_
在何时被赋值的。
写一个hook函数
(
function (){
'use strict';
Object.defineProperty(
window, 'base64_', {
set: function(v) {
console.log('window.base64_正在被赋值');
debugger;
return v;
}
}
)
}
)();
找一个在window.base64_
被赋值前就执行的js代码片段,越早越好。
进入此函数,随便打个断点
然后刷新界面,进入断点状态后,在console
粘贴hook代码
按F8
继续运行到下一个断点处
此时查看调用栈
打开webstorm
,新建一个nodejs
项目
pushmsg
中使用了两个包,分别是atob
与pako
,所以安装这两个包并导入。
npm install atob
npm install pako
const pako = require('pako')
const atob = require('atob')
rot
函数function rot (t, e) {
const i = roott(t, e);
return pushmsg(i)
}
roott
函数function roott(t, e) {
for (var i = "", n = 0; n < t.length; n++) {
var a = t.charCodeAt(n)
, o = a;
a >= 65 && a <= 90 && (o = (a - 65 - 1 * e + 26) % 26 + 65),
a >= 97 && a <= 122 && (o = (a - 97 - 1 * e + 26) % 26 + 97),
i += String.fromCharCode(o)
}
return i
}
pushmsg
函数我们导入的是atob
这个包所以,前面的一些判断直接删掉。
function pushmsg (t) { let e = ""; e = atob(t); const i = e.split("").map(function(t) { return t.charCodeAt(0) }) , n = new Uint8Array(i) , a = pako.inflate(n); return e = function(t) { let e, i, n, a, o = ""; const r = t.length; for (e = 0; e < r; ) switch ((i = t[e++]) >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: o += String.fromCharCode(i); break; case 12: case 13: n = t[e++], o += String.fromCharCode((31 & i) << 6 | 63 & n); break; case 14: n = t[e++], a = t[e++], o += String.fromCharCode((15 & i) << 12 | (63 & n) << 6 | (63 & a) << 0) } return o }(new Uint16Array(a)), unescape(e) }
复制一个base64_
进去调用rot
运行试试看,可以看到解密成功。
我们的爬虫是用python写的,那么如何去使用这个nodejs解密的结果呢?
有两种方法,
nodejs有很多框架,目前我对nodejs不太熟悉,去搜了下,express
这个库比较流行。
用什么都可以,因为我们的接口就是在本子自己调用,不必考虑其他情况。
网上抄代码:
server.js
let express = require('express'); // 我们自己写的解密文件 const bb = require('./decode.js') let app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json());//数据JSON类型 app.use(bodyParser.urlencoded({ extended: false }));//解析post请求数据 app.all('*',function(req,res,next){ let origin=req.headers.origin; res.setHeader('Access-Control-Allow-Origin',"*"); res.setHeader('Access-Control-Allow-Headers','Content-Type'); next(); }) app.post('/data',function(req,res){ console.log(req.body); var base = req.body.base // 调用解密方法 var result = bb.rot(base) res.send(result) }) app.listen(8080)
启动服务
node server.js
效果演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sgh0Qybm-1618038234596)(https://cdn.z2blog.com/picgo/20210410135116.gif)]
atob
是将base64字符串解码
pako
是将zlib压缩文件
类似这些功能的包在Python中也用,还都是内置包
atob -> base64.b64decode
pako.inflate -> zlib.decompress
rot
def rot(t, e):
i = roott(t, e)
return pushmsg(i)
roott
# function roott(t, e) { # for (var i = "", n = 0; n < t.length; n++) { # var a = t.charCodeAt(n) # , o = a; # a >= 65 && a <= 90 && (o = (a - 65 - 1 * e + 26) % 26 + 65), # a >= 97 && a <= 122 && (o = (a - 97 - 1 * e + 26) % 26 + 97), # i += String.fromCharCode(o) # } # return i # } def roott(t, e): i = "" for n in range(len(t)): a = ord(n) o = a if 65 <= a <= 90: o = (a - 65 - 1 * e + 26) % 26 + 65 if 97 <= a <= 122: o = (a - 97 - 1 * e + 26) % 26 + 97 i += chr(o) return i
如果嫌麻烦,可以使用pyexecjs
,就像这样
import execjs js = """function roott(t, e) { for (var i = "", n = 0; n < t.length; n++) { var a = t.charCodeAt(n) , o = a; a >= 65 && a <= 90 && (o = (a - 65 - 1 * e + 26) % 26 + 65), a >= 97 && a <= 122 && (o = (a - 97 - 1 * e + 26) % 26 + 97), i += String.fromCharCode(o) } return i }""" s = execjs.compile(js) ... # 调用roott s.call('roott', t, e)
pushmsg
import base64
import zlib
from urllib import parse
def pushmsg(t):
# 对应atob
r = base64.b64decode(t.encode())
# 解压
b = zlib.decompress(r)
p = parse.unquote(b.decode())
return p.replace('%', '\\').encode().decode('unicode-escape')
完整代码
import base64 import zlib from urllib import parse def roott(t, e): i = "" for n in t: a = ord(n) o = a if 65 <= a <= 90: o = (a - 65 - 1 * e + 26) % 26 + 65 if 97 <= a <= 122: o = (a - 97 - 1 * e + 26) % 26 + 97 i += chr(o) return i def pushmsg(t): # 对应atob r = base64.b64decode(t.encode()) # 解压 b = zlib.decompress(r) p = parse.unquote(b.decode()) print(p.replace('%', '\\').encode().decode('unicode-escape')) def rot(t, e): i = roott(t, e) return pushmsg(i) if __name__ == '__main__': base64_ = "kL7ylBr73JgC9w/XFj..." e = 6 print(rot(base64_, e))
效果演示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。