爬虫总结_雷速体育_Canvas字体加密
网址:https://live.leisu.com/wanchang
可以看到这个比分是使用canvas绘制上去的。
了解Canvas
首先了解下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_
已经是处于加密状态,是一个非常长的字符串,有理由相信实际返回的原始数据就是这个超长字符串。我们的拿出字符串的前几位去全局搜索。
看了下base64_被执行赋值操作的js文件是wc-xxxx.js
然后简单调试发现,请求这个js文件时,无需携带cookie,所以尝试直接请求这个js
文件,成功获取响应。
hook方法
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
继续运行到下一个断点处
此时查看调用栈
复现js代码
打开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如何解密
我们的爬虫是用python写的,那么如何去使用这个nodejs解密的结果呢?
有两种方法,
- 在nodejs上起一个API服务,在Python中请求数据解密接口就行了。
- 使用纯Python代码复现解密代码。
借助nodejs API服务
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
效果演示
使用Python复现代码
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))
效果演示
- 0
- 0
-
分享